fix(javascript): correct lexical and dynamic scope analysis (#538)

* fix(javascript): gate CommonJS exports on unshadowed globals and record dynamic scope

* fix(javascript): correct dynamic and switch scope boundaries
This commit is contained in:
morluto authored and GitHub committed 2026-10-05 23:42:48 +08:00
1 parent 00fa97422f
commit 6fa6f1f9b1
13 files changed
+337 -28

No files matched your search

+2 -2
View File
@@ -545,9 +545,9 @@
"mcp_prompts": 6
},
"digests": {
"tools_sha256": "e14cff8d23b94644dda1c1d0c28dbcf3e8140f05daa06f4bd5f1bde1f1e2ec9b",
"tools_sha256": "d898fbdf87a2684a0d7b7cffe0a5ae0b29c7f09651be912b152783738c997373",
"prompts_sha256": "4762db348821fec787a5b8c6423f2c48a71f6f0ec2afee97b16ad69030ec504b",
"combined_sha256": "37bbb06484eee3010b083c66fdb38e3f90c770d0b9bccb603e25078538635790",
"combined_sha256": "3efc2688f505dd5fee02c730efddb4fbeba79db0ab34c40494a5a1fc55be308b",
"providers_sha256": "2ae8b85b20be338739e500549d275e78e875aaa10235d2ca283f0ade7787da90"
}
}
@@ -1,5 +1,5 @@
{
"manifest_id": "ecm_333150d508ef63182ea3c8caed5abb0a360497e977d47bf7c7cf8eaa5d29da6d",
"manifest_id": "ecm_9218fd2c70d82c981443b36551dfb24e045dcc9811db71a540b1cf6a37651767",
"verifier": {
"id": "rea-managed-conformance",
"version": "1"
@@ -13,7 +13,7 @@
"skill_digests": [
{
"skill_id": "reverse-engineer-anything",
"sha256": "2e36435a6234f09024fb43dd17b8a32c813876e1105dde8a675ae647597ad48d"
"sha256": "316944ca527c869a3d1b1d1af58b79386d057d8cdd47eb163805b665492f6a55"
}
],
"claims": [
+1 -1
View File
@@ -4,7 +4,7 @@ description: Reverse engineer native, managed, Electron/JavaScript, packaged, an
metadata:
version: "23"
tool_count: 116
catalog_digest: "37bbb06484eee3010b083c66fdb38e3f90c770d0b9bccb603e25078538635790"
catalog_digest: "3efc2688f505dd5fee02c730efddb4fbeba79db0ab34c40494a5a1fc55be308b"
---
# REA
@@ -255,22 +255,32 @@ export const projectSemanticClosureCaptures = (
});
};
/** Retain bounded unresolved dynamic-call and dynamic-property frontiers. */
/** Retain bounded unresolved dynamic-call, dynamic-property and dynamic-scope frontiers. */
export const projectSemanticFrontiers = (
context: SemanticFlowProjectionContext,
): void => {
for (const frontier of context.ir.frontiers) {
// Dynamic environments leave identifier reads and writes unresolved.
const family =
frontier.kind === "dynamic-call"
? "call-flow"
: frontier.kind === "dynamic-scope"
? "data-flow"
: "object-flow";
const relationKinds =
frontier.kind === "dynamic-call"
? (["calls"] as const)
: frontier.kind === "dynamic-scope"
? (["reads", "writes"] as const)
: (["reads-property", "writes-property"] as const);
const unknown = createJavaScriptSemanticGraphUnknown({
node_id:
frontier.callableId === null
? context.moduleNode.node_id
: (context.callableNodes.get(frontier.callableId)?.node_id ??
context.moduleNode.node_id),
family: frontier.kind === "dynamic-call" ? "call-flow" : "object-flow",
relation_kinds:
frontier.kind === "dynamic-call"
? ["calls"]
: ["reads-property", "writes-property"],
family,
relation_kinds: [...relationKinds],
reason: frontier.kind,
detail: frontier.reason,
candidate_node_ids: [],
@@ -0,0 +1,147 @@
import { expect, it } from "vitest";
import { buildJavaScriptSemanticGraph } from "./JavaScriptSemanticGraphBuilder.js";
import type { JavaScriptArtifactAnalysis } from "./JavaScriptArtifactAnalysisTypes.js";
import type { JavaScriptArtifactFile } from "./JavaScriptArtifactFiles.js";
import { analyzeJavaScriptSemantics } from "../domain/javascriptSemanticAnalysis.js";
import { topLevelBinding } from "../domain/javascriptSemanticAnalysis.fixture.js";
const SHA256 = "a".repeat(64);
const GRAPH_ID = `jag_${"b".repeat(64)}`;
const graphFor = (source: string) => {
const file: JavaScriptArtifactFile = {
path: "app.js",
container_sha256: SHA256,
sha256: SHA256,
bytes: Buffer.byteLength(source),
inventory_artifact_id: `art_${SHA256}`,
kind: "javascript",
unpacked: false,
text: { included: true, value: source },
};
const analysis: JavaScriptArtifactAnalysis = {
files: [
{
file,
javascript: null,
semantic: { ir: analyzeJavaScriptSemantics(source) },
},
],
packages: [],
json_modules: [],
html_scripts: [],
source_maps: [],
visited_ast_nodes: 0,
findings: 0,
modules: 0,
parse_failures: 0,
truncated_scopes: 0,
limitations: [],
};
return buildJavaScriptSemanticGraph({
rootArtifactSha256: SHA256,
applicationGraph: { graph_id: GRAPH_ID, nodes: [] },
analysis,
});
};
const dynamicScopeUnknowns = (source: string) =>
graphFor(source).unknowns.filter(({ reason }) => reason === "dynamic-scope");
it("exposes direct eval and with as dynamic-scope unknowns in the public graph", () => {
const unknowns = dynamicScopeUnknowns(`
function inspect(host, source) {
with (host) { consume(target); }
eval(source);
}
`);
expect(unknowns).toHaveLength(2);
expect(unknowns.map(({ detail }) => detail).join("\n")).toEqual(
expect.stringContaining("with"),
);
expect(unknowns.map(({ detail }) => detail).join("\n")).toEqual(
expect.stringContaining("eval"),
);
});
it("does not mark shadowed, optional, or indirect eval as dynamic scope", () => {
const unknowns = dynamicScopeUnknowns(`
function shadowed(eval, source) { eval(source); }
function optional(source) { eval?.(source); }
function indirect(source) { (0, eval)(source); }
`);
expect(unknowns).toEqual([]);
});
it("keeps switch discriminants outside the shared case lexical scope", () => {
const ir = analyzeJavaScriptSemantics(`
const selected = choose();
switch (selected) {
case 1: let selected = 1; let local = 1; break;
case 2: local = 2; break;
}
`);
const selectedBinding = topLevelBinding(ir, "selected");
const localBindings = ir.bindings.filter(({ name }) => name === "local");
expect(ir.references.find(({ name }) => name === "selected")?.bindingId).toBe(
selectedBinding.bindingId,
);
expect(localBindings).toHaveLength(1);
expect(localBindings[0]?.scopeId).not.toBe(selectedBinding?.scopeId);
expect(
ir.references.filter(
({ name, role }) => name === "selected" && role === "read",
),
).toHaveLength(1);
expect(
ir.references.filter(
({ name, role }) => name === "local" && role === "write",
),
).toHaveLength(1);
});
it("leaves with-object resolution intact and marks only body name resolution unknown", () => {
const ir = analyzeJavaScriptSemantics(`
const host = getHost();
const outer = 3;
function inspect() {
with (host) {
const local = 1;
consume(local, outer);
}
}
`);
const host = topLevelBinding(ir, "host");
const hostRead = ir.references.find(
({ name, role }) => name === "host" && role === "read",
);
const outerRead = ir.references.find(
({ name, role }) => name === "outer" && role === "read",
);
const localRead = ir.references.find(
({ name, role }) => name === "local" && role === "read",
);
expect(hostRead?.bindingId).toBe(host.bindingId);
expect(hostRead?.resolution).toBe("resolved");
expect(outerRead?.resolution).toBe("unknown");
expect(localRead?.resolution).toBe("resolved");
expect(outerRead?.bindingId).toBeNull();
});
it("keeps var declarations in each static block from leaking to another block", () => {
const ir = analyzeJavaScriptSemantics(`
class Example {
static { var first = 1; }
static { var second = 2; consume(first); }
}
`);
const first = ir.bindings.filter(({ name }) => name === "first");
const second = ir.bindings.filter(({ name }) => name === "second");
expect(ir.references.find(({ name }) => name === "first")?.resolution).toBe(
"unbound",
);
expect(first).toHaveLength(1);
expect(second).toHaveLength(1);
expect(first[0]?.scopeId).not.toBe(second[0]?.scopeId);
});
@@ -382,6 +382,64 @@ describe("JavaScript semantic analysis: structure 2", () => {
"read", // const observed = total
]);
});
});
describe("JavaScript semantic analysis: dynamic and case scopes", () => {
it("marks with and eval as unresolved dynamic scope instead of staying silent", () => {
const ir = analyzeJavaScriptSemantics(`
function load(source) {
with (host) { consume(target); }
eval("var injected = 1;");
}
`);
const reasons = ir.frontiers
.filter(({ kind }) => kind === "dynamic-scope")
.map(({ reason }) => reason);
expect(reasons).toHaveLength(2);
expect(reasons.join("\n")).toContain("`with`");
expect(reasons.join("\n")).toContain("`eval`");
});
it("does not assign CommonJS exports to lexically shadowed module or exports", () => {
const ir = analyzeJavaScriptSemantics(`
function shadowed(module, exports) {
module.exports.value = 1;
exports.other = 2;
exports[key] = 3;
module.exports[key] = 4;
unrelated[key] = 5;
}
`);
expect(
ir.moduleLinks.filter(({ kind }) => kind === "commonjs-export"),
).toEqual([]);
});
it("shares one lexical scope across switch cases and isolates static blocks", () => {
const switchIr = analyzeJavaScriptSemantics(`
const kind = select();
switch (kind) {
case "a": let shared = 1; break;
case "b": shared = 2; break;
}
`);
// One declaration, one binding — and it belongs to a scope of its own, so
// it cannot leak past the switch statement.
const shared = switchIr.bindings.filter(({ name }) => name === "shared");
expect(shared).toHaveLength(1);
expect(shared[0]?.scopeId).not.toBe(
topLevelBinding(switchIr, "kind").scopeId,
);
const classIr = analyzeJavaScriptSemantics(`
class Holder {
static { let isolated = 1; }
static { let isolated = 2; }
}
`);
expect(
classIr.bindings.filter(({ name }) => name === "isolated"),
).toHaveLength(2);
});
it("keeps function, class, and method identities separate from bindings", () => {
const ir = analyzeJavaScriptSemantics(`
+42 -6
View File
@@ -141,15 +141,34 @@ const collectDefinitions = (
const stack: JavaScriptSemanticScopeState[] = [
currentSemanticScope(state.scopes),
];
const openedScopes = new WeakSet<t.Node>();
const openedScopes = new WeakMap<t.Node, number>();
traverseJavaScriptAst(program, {
enter: (node, parent) => {
const parentScope = currentSemanticScope(stack);
let parentScope = currentSemanticScope(stack);
if (
parent !== null &&
t.isWithStatement(parent) &&
parent.body === node
) {
const dynamicScope: JavaScriptSemanticScopeState = {
scopeId: `${semanticScopeId("block", parent)}:with`,
parentScopeId: parentScope.scopeId,
kind: "block",
location: range(parent),
bindingsComplete: false,
bindings: new Map(),
};
state.scopes.push(dynamicScope);
state.scopesById.set(dynamicScope.scopeId, dynamicScope);
stack.push(dynamicScope);
openedScopes.set(node, 1);
parentScope = dynamicScope;
}
bindOuterDeclaration(node, parentScope, state);
const nested = nestedScope(node, parent, parentScope, state);
if (nested !== undefined) {
stack.push(nested);
openedScopes.add(node);
openedScopes.set(node, (openedScopes.get(node) ?? 0) + 1);
}
const scope = currentSemanticScope(stack);
state.scopeByNode.set(node, scope);
@@ -163,7 +182,8 @@ const collectDefinitions = (
bindInnerDeclaration(node, parent, scope, state);
},
exit: (node) => {
if (openedScopes.has(node)) stack.pop();
for (let count = openedScopes.get(node) ?? 0; count > 0; count--)
stack.pop();
},
});
};
@@ -243,13 +263,23 @@ const nestedScope = (
parentScope: JavaScriptSemanticScopeState,
state: JavaScriptSemanticAnalysisState,
): JavaScriptSemanticScopeState | undefined => {
const switchOwner =
t.isSwitchCase(node) && parent !== null && t.isSwitchStatement(parent)
? parent
: undefined;
if (switchOwner !== undefined) {
const existing = state.scopesById.get(
semanticScopeId("block", switchOwner),
);
if (existing !== undefined) return existing;
}
const kind = scopeKind(node, parent);
if (kind === undefined) return undefined;
const scope: JavaScriptSemanticScopeState = {
scopeId: semanticScopeId(kind, node),
scopeId: semanticScopeId(kind, switchOwner ?? node),
parentScopeId: parentScope.scopeId,
kind,
location: range(node),
location: range(switchOwner ?? node),
bindingsComplete: true,
bindings: new Map(),
};
@@ -271,6 +301,12 @@ const scopeKind = (
t.isForInStatement(node)
)
return "block";
// A switch body is one lexical scope shared by all its cases, so `let` in
// two cases must resolve to one binding rather than collide or leak.
if (t.isSwitchCase(node)) return "block";
// Each `static {}` block is its own lexical scope; otherwise a `let` in two
// static blocks would collide in the enclosing class scope.
if (t.isStaticBlock(node)) return "static-block";
if (
t.isBlockStatement(node) &&
!(parent !== null && t.isFunction(parent) && parent.body === node)
+32
View File
@@ -16,6 +16,7 @@ import {
} from "./javascriptSemanticProjection.js";
import {
resolveSemanticBindingState,
isUnshadowedGlobal,
type JavaScriptSemanticAnalysisState,
type JavaScriptSemanticBindingState,
} from "./javascriptSemanticState.js";
@@ -91,6 +92,7 @@ export const collectJavaScriptSemanticCalls = (
const owner = enclosingFunction(callableStack);
collectCapture(node, parent, owner, context);
collectDynamicProperty(node, owner, state, output);
collectDynamicScope(node, owner, state, output);
if (
t.isCallExpression(node) ||
t.isOptionalCallExpression(node) ||
@@ -365,6 +367,36 @@ const collectDynamicProperty = (
);
};
/**
* `with (o) { … }` and `eval("…")` decide at runtime which bindings exist, so
* every name inside them is unresolvable statically. Recording the frontier
* is what keeps the analyzer from presenting an empty or partial resolution as
* an observed fact — silence here would read as "nothing to resolve".
*/
const collectDynamicScope = (
node: t.Node,
owner: JavaScriptSemanticCallable | undefined,
state: JavaScriptSemanticAnalysisState,
output: MutableCallAnalysis,
): void => {
const reason = t.isWithStatement(node)
? "`with` introduces a runtime binding environment; names inside are not statically resolvable."
: t.isCallExpression(node) && isUnshadowedGlobal(node.callee, state, "eval")
? "`eval` can declare bindings at runtime; names inside the evaluated source are not statically resolvable."
: null;
if (reason === null) return;
addFrontier(
{
kind: "dynamic-scope",
callableId: owner?.callableId ?? null,
location: range(node),
reason,
},
state,
output,
);
};
const addFrontier = (
frontier: JavaScriptSemanticFrontier,
state: JavaScriptSemanticAnalysisState,
@@ -246,6 +246,7 @@ export const JAVASCRIPT_SEMANTIC_UNKNOWN_REASONS = [
"bound-reached",
"dynamic-call",
"dynamic-property",
"dynamic-scope",
"eval-or-generated-code",
"incomplete-module",
"missing-source",
+13 -2
View File
@@ -44,7 +44,13 @@ export type JavaScriptBindingProvenance =
export interface JavaScriptSemanticScope {
readonly scopeId: string;
readonly parentScopeId: string | null;
readonly kind: "program" | "function" | "block" | "class" | "catch";
readonly kind:
| "program"
| "function"
| "block"
| "static-block"
| "class"
| "catch";
readonly location: JavaScriptSourceRange;
readonly bindingsComplete: boolean;
readonly bindingIds: readonly string[];
@@ -369,7 +375,12 @@ export interface JavaScriptSemanticObjectOperation {
/** One syntax location where exact local semantic continuation is unavailable. */
export interface JavaScriptSemanticFrontier {
readonly kind: "dynamic-call" | "dynamic-property";
/**
* `dynamic-call`/`dynamic-property` mark a callee or key the analyzer could
* not resolve. `dynamic-scope` marks `with`/`eval`, where the binding
* environment itself is not statically knowable.
*/
readonly kind: "dynamic-call" | "dynamic-property" | "dynamic-scope";
readonly callableId: string | null;
readonly location: JavaScriptSourceRange;
readonly reason: string;
+12 -8
View File
@@ -10,6 +10,7 @@ import type {
import {
resolveSemanticBindingState,
semanticResolutionBlocked,
isUnshadowedGlobal,
type JavaScriptSemanticAnalysisState,
type JavaScriptSemanticScopeState,
} from "./javascriptSemanticState.js";
@@ -357,7 +358,7 @@ const collectCommonJsExport = (
node: t.AssignmentExpression,
state: JavaScriptSemanticAnalysisState,
): void => {
const exportedName = commonJsExportName(node.left);
const exportedName = commonJsExportName(node.left, state);
if (exportedName === undefined) return;
const origin = semanticModuleOrigin(node.right, [], state);
addModuleLink(state, {
@@ -610,24 +611,27 @@ const defaultDeclarationName = (
? declaration.id.name
: null;
const commonJsExportName = (node: t.Node): string | undefined => {
if (t.isIdentifier(node, { name: "exports" })) return "default";
const commonJsExportName = (
node: t.Node,
state: JavaScriptSemanticAnalysisState,
): string | undefined => {
if (isUnshadowedGlobal(node, state, "exports")) return "default";
if (!t.isMemberExpression(node) && !t.isOptionalMemberExpression(node))
return undefined;
const key = memberKey(node);
// `exports[key]`/`module.exports[key]` assign an export whose name is not
// knowable. Report the wildcard rather than the variable name, and never
// collapse it into `default`, which would claim a real default export.
if (key === null) return "*";
if (t.isIdentifier(node.object, { name: "exports" })) return key || "*";
if (isUnshadowedGlobal(node.object, state, "exports"))
return key === null ? "*" : key || "*";
if (
t.isMemberExpression(node.object) &&
t.isIdentifier(node.object.object, { name: "module" }) &&
isUnshadowedGlobal(node.object.object, state, "module") &&
memberKey(node.object) === "exports"
)
return key || "default";
return key === null ? "*" : key || "default";
if (
t.isIdentifier(node.object, { name: "module" }) &&
isUnshadowedGlobal(node.object, state, "module") &&
memberKey(node) === "exports"
)
return "default";
+10
View File
@@ -138,3 +138,13 @@ export const semanticVariableScope = (
}
return candidate;
};
/** Identify a global name only when no lexical or dynamic environment shadows it. */
export const isUnshadowedGlobal = (
node: t.Node,
state: JavaScriptSemanticAnalysisState,
name: string,
): boolean =>
t.isIdentifier(node, { name }) &&
resolveSemanticBindingState(state, node, name) === undefined &&
!semanticResolutionBlocked(state, node, name);
+1 -1
View File
@@ -30,7 +30,7 @@ interface GeneratedPayload {
}
const GENERATED_PAYLOAD_JSON =
'{"auxiliaryProviders":[{"capabilities":[{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inventory_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"extract_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":false,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"decode_interface_builder","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":false,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_keyed_archive","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_asset_catalog","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null}],"identity":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"}},{"capabilities":[{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"observe_native_ui","provider":{"id":"native-macos","name":"macOS native inspection utilities","version":null},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":true,"mayShowUi":true,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"capture_native_ui_scenario","provider":{"id":"native-macos","name":"macOS native inspection utilities","version":null},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"inspect_macho","provider":{"id":"native-macos","name":"macOS native inspection utiliLine truncated
'{"auxiliaryProviders":[{"capabilities":[{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inventory_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"extract_artifact","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":false,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"decode_interface_builder","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":false,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_keyed_archive","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["DMG child inventory automatically uses a read-only native macOS mount; PKG remains root-hash-only.","ASAR files discovered in filesystem-backed inventories are expanded without bulk extraction; other nested containers remain recorded only."],"operation":"inspect_asset_catalog","provider":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"},"reason":null}],"identity":{"id":"rea-artifact-graph","name":"REA safe artifact graph provider","version":"1"}},{"capabilities":[{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"observe_native_ui","provider":{"id":"native-macos","name":"macOS native inspection utilities","version":null},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":true,"mayShowUi":true,"mayWriteFilesystem":true,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"capture_native_ui_scenario","provider":{"id":"native-macos","name":"macOS native inspection utilities","version":null},"reason":null},{"available":true,"effects":{"changesPermissions":false,"launchesProcess":true,"mayAccessNetwork":false,"mayShowUi":false,"mayWriteFilesystem":false,"mutatesArtifact":false,"requiresRoot":false},"limitations":["Availability and textual formats depend on the installed macOS/Xcode toolchain."],"operation":"inspect_macho","provider":{"id":"native-macos","name":"macOS native inspection utiliLine truncated
const generatedPayload: unknown = JSON.parse(GENERATED_PAYLOAD_JSON);
if (!isGeneratedPayload(generatedPayload))
throw new TypeError("Generated MCP catalog payload is invalid");