Files
reverse-engineering-anything/src/dotnet/ManagedStaticProvider.ts
T
morluto cb4e5cfbff feat(analysis): make resource budgets recoverable across CLI and MCP (#9706)
* refactor(analysis): centralize resource budgets and recovery facts

* feat(analysis): expose ASAR and Ghidra runtime admission controls

* feat(artifacts): expose reader admission and preserve limited recognition

* feat(javascript): expose recoverable semantic precision budgets

* feat(web): add recoverable source-map budgets and compact retained views

* feat(web): make historical capture decoding budgets recoverable

* fix(desktop): enforce selected view policies across capture and export

* feat(runtime): make Frida and LLDB retention budgets selectable

* feat(native): expose diagnostic budgets and preserve exhaustion causes

* feat(managed): expose recoverable snapshot and representation budgets

* feat(metadata): make Go and Rust inspection budgets recoverable

* feat(wasm): expose representation and producer admissions

* feat(evm): expose per-call worker resource budgets

* feat(android): make resource and string-pool omissions recoverable

* feat(producers): expose command and recorded-capture budgets

* docs(analysis): record recovery contracts and integrated validation

* fix(docs): link calibration oracle from published verification pages

* fix(docs): restore desktop task anchors and receipt links

* test(analysis): align fixtures with selectable budget contracts

* fix(analysis): remove source-location projector dependency cycle

* test(analysis): refresh budget recovery and verifier regressions

* test(analysis): preserve format and publication recovery expectations

* test(windows): align conformance with owned runtime admission

* fix(desktop): preserve selected alias kinds in exported state

* test(analysis): align provider fixtures with recoverable budgets

* test(desktop): validate emitted alias metadata structurally

* fix(ghidra): classify bounded Windows endpoint refusals

* test(cli): assert selectable artifact admission recovery

* fix(cli): preserve capture provenance in environment builds
2026-11-10 05:11:20 -05:00

304 lines
9.8 KiB
TypeScript

import {
createAnalysisExecution,
type AnalysisClient,
type AnalysisOperation,
type AnalysisProvider,
type CapabilityDescriptor,
type ExecutionOptions,
type ProviderIdentity,
} from "../application/AnalysisProvider.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { managedStaticCapabilities } from "./ManagedStaticProviderMetadata.js";
import { MANAGED_STATIC_PROVIDER } from "../domain/investigationProviders.js";
import {
MANAGED_TOOL_CONTRACTS,
managedMembersInputSchema,
managedTargetInputSchema,
type ManagedToolName,
} from "../contracts/managed/managedToolContracts.js";
import { AnalysisError } from "../domain/analysisErrorBase.js";
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
import {
AnalysisCancelledError,
AnalysisCapabilityUnavailableError,
AnalysisUnsupportedTargetError,
} from "../domain/analysisErrorCore.js";
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
import type { EvidenceLocation } from "../domain/evidence.js";
import type { JsonValue } from "../domain/jsonValue.js";
import type {
ManagedArtifactInspection,
ManagedMemberInspection,
ManagedNativeBoundaryInspection,
} from "../domain/managed/managedArtifact.js";
import { err, ok, type Result } from "../domain/result.js";
import { inspectManagedArtifactBytes } from "./ManagedArtifactInspector.js";
import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
import { inspectManagedNativeBoundariesBytes } from "./ManagedNativeBoundaryInspector.js";
import { RegularFileCleanupFailure } from "../filesystem/OwnedRegularFile.js";
import { managedInspectionInputFailure } from "./ManagedReaderFailure.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import type { z } from "zod";
import {
hashManagedSource,
managedCleanupError,
readManagedSnapshot,
verifyManagedSourceIdentity,
} from "./ManagedSnapshot.js";
/** Execution-free managed PE/CLI auxiliary provider. */
export class ManagedStaticProvider implements AnalysisProvider {
readonly #capabilities: readonly CapabilityDescriptor[] =
managedStaticCapabilities();
identity(): ProviderIdentity {
return MANAGED_STATIC_PROVIDER;
}
capabilities(): readonly CapabilityDescriptor[] {
return this.#capabilities;
}
createClient(target: BinaryTarget): AnalysisClient {
return new ManagedStaticClient(target);
}
}
class ManagedStaticClient implements AnalysisClient {
#snapshotBytes: Buffer | undefined;
readonly #resources = new ArtifactResourceScope();
constructor(private readonly target: BinaryTarget) {}
async execute(
operation: AnalysisOperation,
parameters: Readonly<Record<string, JsonValue>>,
options?: ExecutionOptions,
) {
if (operation === "health")
return ok(createAnalysisExecution(null, MANAGED_STATIC_PROVIDER));
if (!isManagedOperation(operation))
return err(
new AnalysisCapabilityUnavailableError(
MANAGED_STATIC_PROVIDER.id,
operation,
"Operation is not implemented by the managed static provider.",
),
);
// A non-PE target is outside this operation's formats, not a missing
// provider capability; the unimplemented-operation refusal above is.
if (this.target.format !== "pe")
return err(
new AnalysisUnsupportedTargetError(
operation,
this.target.path,
`Managed static triage requires a PE target; observed ${this.target.format}.`,
),
);
try {
const input =
operation === "inspect_managed_members"
? managedMembersInputSchema.safeParse(parameters)
: managedTargetInputSchema.safeParse(parameters);
if (!input.success)
return err(
analysisInputErrorFromIssues(
operation,
input.error.issues,
parameters,
),
);
const snapshotOptions = {
expectedSha256: this.target.sha256,
signal: options?.signal,
resources: this.#resources,
...(input.data.max_snapshot_bytes === undefined
? {}
: { maxBytes: input.data.max_snapshot_bytes }),
};
if (options?.signal?.aborted === true)
return err(
new AnalysisCancelledError(operation, {
cause: options.signal.reason,
}),
);
const snapshot = this.#snapshotBytes;
const observed =
snapshot === undefined
? await readManagedSnapshot(
this.target.path,
operation,
snapshotOptions,
)
: await hashManagedSource(
this.target.path,
operation,
snapshotOptions,
);
const admitted = verifyManagedSourceIdentity(
this.target,
observed,
operation,
);
if (!admitted.ok) return admitted;
const bytes = snapshot ?? observed.bytes;
if (bytes === undefined)
throw new TypeError("Managed snapshot bytes are unavailable");
this.#snapshotBytes = bytes;
const result = inspectManagedOperation(
operation,
input.data,
bytes,
this.target,
);
return ok(
createAnalysisExecution(result, MANAGED_STATIC_PROVIDER, {
rawResult: null,
limitations: result.limitations,
subject: this.target,
locations: managedLocations(result),
}),
);
} catch (cause: unknown) {
if (isCleanupFailure(cause))
return err(this.#cleanupError(operation, cause));
if (cause instanceof AnalysisError) return err(cause);
if (options?.signal?.aborted === true)
return err(
new AnalysisCancelledError(operation, {
cause: options.signal.reason,
}),
);
const inputFailure = managedInspectionInputFailure(
operation,
this.target.path,
cause,
);
if (inputFailure !== undefined) return err(inputFailure);
return err(
new ProviderAdapterError(MANAGED_STATIC_PROVIDER.id, operation, {
cause,
}),
);
}
}
async close(): Promise<Result<null, AnalysisError>> {
this.#snapshotBytes = undefined;
try {
await this.#resources.close();
return ok(null);
} catch (cause: unknown) {
return err(this.#cleanupError("close", cause));
}
}
#cleanupError(operation: string, cause: unknown): ProviderCleanupError {
const failure = managedCleanupError(cause, this.target.path, operation);
if (failure instanceof ProviderCleanupError) return failure;
const cleanup =
cause instanceof ArtifactReaderFailure ? cause.cleanup : undefined;
return new ProviderCleanupError(
MANAGED_STATIC_PROVIDER.id,
cleanup?.resources ?? [this.target.path],
{
reason:
cleanup?.reason ??
(cause instanceof Error ? cause.message : String(cause)),
},
{ cause, operation },
);
}
}
const isCleanupFailure = (cause: unknown): boolean =>
cause instanceof RegularFileCleanupFailure ||
(cause instanceof ArtifactReaderFailure && cause.cleanup !== undefined);
const isManagedOperation = (
operation: AnalysisOperation,
): operation is ManagedToolName =>
MANAGED_TOOL_CONTRACTS.some(({ name }) => name === operation);
const inspectManagedOperation = (
operation: ManagedToolName,
parameters: Omit<z.output<typeof managedMembersInputSchema>, "detail">,
bytes: Buffer,
target: BinaryTarget,
):
| ManagedArtifactInspection
| ManagedMemberInspection
| ManagedNativeBoundaryInspection => {
if (operation === "inspect_managed_members") {
const { type_names: typeNames } = parameters;
return inspectManagedMembersBytes(bytes, target, {
...(typeNames === undefined ? {} : { typeNames }),
...(parameters.max_metadata_representation_bytes === undefined
? {}
: {
maxRepresentationBytes:
parameters.max_metadata_representation_bytes,
}),
});
}
if (operation === "inspect_managed_artifact") {
return inspectManagedArtifactBytes(
bytes,
target,
parameters.max_metadata_representation_bytes,
);
}
return inspectManagedNativeBoundariesBytes(
bytes,
target,
parameters.max_metadata_representation_bytes,
);
};
const managedLocations = (
result:
| ManagedArtifactInspection
| ManagedMemberInspection
| ManagedNativeBoundaryInspection,
): readonly EvidenceLocation[] => {
if ("pe" in result) {
if (result.pe.cli === null) return [{ kind: "file-offset", offset: 0 }];
return [
{
kind: "file-offset-range",
start: result.pe.cli.header_offset,
end: result.pe.cli.header_offset + result.pe.cli.header_size,
},
...[result.module, result.assembly]
.filter((value) => value !== null)
.map((value) => ({
kind: "file-offset" as const,
offset: value.row_offset,
})),
];
}
if ("methods" in result)
return [
...(result.module === null
? [{ kind: "file-offset" as const, offset: 0 }]
: [{ kind: "file-offset" as const, offset: result.module.row_offset }]),
...result.methods
.filter((method) => method.body.file_offset !== null)
.map((method) => ({
kind: "file-offset" as const,
offset: method.body.file_offset ?? 0,
})),
];
return [
...(result.module === null
? [{ kind: "file-offset" as const, offset: 0 }]
: [{ kind: "file-offset" as const, offset: result.module.row_offset }]),
...result.pinvoke_imports.map((mapping) => ({
kind: "file-offset" as const,
offset: mapping.row_offset,
})),
];
};