Merge branch 'main' into codex/rea-2-mcp-contract-hardening

This commit is contained in:
morluto 2026-07-16 20:44:22 +08:00 committed by GitHub
commit 1140d5637b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 382 additions and 13 deletions

View File

@ -61,7 +61,7 @@ See [docs/architecture.mermaid](docs/architecture.mermaid) for a visual architec
- `npm run verify:hopper`: build and run the real-Hopper verifier with two distinct binaries.
- `npm run verify:ghidra`: build and run the real-Ghidra verifier against `GHIDRA_INSTALL_DIR` and optional `GHIDRA_TARGET_PATH`.
- `npm run verify:browser`: build and run the real Chrome verifier against `REA_BROWSER_EXECUTABLE` or a platform-default Chrome-family executable.
- `npm run verify:managed`: build and run the source-owned managed PE/CLI conformance verifier for artifact triage, member inspection, managed/native boundary declarations, token drift comparison, malformed metadata, and non-managed degradation.
- `npm run verify:managed`: build and run the source-owned managed PE/CLI conformance verifier for artifact triage, member inspection, managed/native boundary declarations, token drift comparison, malformed metadata, non-managed degradation, and manifest-verifier self-test; set `REA_MANAGED_APP_MANIFEST_PATH` to verify an operator-local managed app manifest.
- `npm run verify:replay`: build and run the real Linux Bubblewrap/seccomp/cgroup verifier against source-owned replay fixtures; set `REA_REPLAY_INPUT_PATH` to verify an operator-local manifest.
- `npm run verify:package`: pack and test the CLI, setup transaction, skill, and canonical target-free MCP catalog in an isolated environment.
- `npm run docs:generate`: generate API documentation from JSDoc comments into `docs/api/` using TypeDoc.

View File

@ -248,34 +248,53 @@ acceptance does not automatically make the bytes valid.
## Operator-local osu! benchmark
The optional osu! benchmark accepts only paths and expectations supplied by the
operator. A compact manifest contains:
operator. Run it with:
```sh
REA_MANAGED_APP_MANIFEST_PATH=/absolute/managed-app-manifest.json npm run verify:managed
```
`verify:managed` always runs a source-owned manifest-verifier self-test first.
When `REA_MANAGED_APP_MANIFEST_PATH` is set, the manifest's target path may be
absolute or relative to the manifest file. A compact manifest contains:
```json
{
"schema_version": 1,
"label": "operator-local osu!stable semantic slice",
"target": {
"path": "./osu!.exe",
"sha256": "<exact target digest>",
"mvid": "<exact module MVID>",
"assembly_name": "<expected simple name>",
"architecture": "<expected classification>"
"runtime_family": "dotnet-framework",
"managed_architecture": "x86"
},
"methods": [
{
"label": "operator-local semantic label",
"token": "0x06000000",
"signature": "<normalized signature>",
"il_length": 0,
"signature_sha256": "<raw signature blob digest>",
"il_size": 0,
"normalized_il_sha256": "<digest>"
}
]
}
```
Before evaluating methods, the verifier fails closed on any target commitment
mismatch. Output contains assertion status and compact identities only. It does
not print method bodies, reconstructed C#, application data, runtime telemetry,
account/service details, or full filesystem inventories. The manifest and
target remain outside git.
Before evaluating methods, the verifier fails closed on target SHA-256, MVID,
assembly name, runtime-family, and managed-architecture mismatches whenever the
manifest supplies those fields. It then pages each declared MethodDef token
directly by row, so selected methods do not need to appear in the first member
page of a large application. `il_length` remains accepted as a legacy alias for
`il_size`.
Output contains assertion status and compact identities only: file name,
target SHA-256, MVID, assembly name, runtime-family, managed architecture,
method tokens, method names, signature digests, IL sizes, and normalized IL
digests. It does not print method bodies, reconstructed C#, application data,
runtime telemetry, account/service details, full filesystem inventories, or
the target's absolute path. The manifest and target remain outside git.
The benchmark may prove that REA reproduces selected facts for that exact local
build. It cannot establish general support on its own; source-built conformance

View File

@ -1,7 +1,7 @@
import { createHash } from "node:crypto";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
import assert from "node:assert/strict";
import { parseBinaryTarget } from "../dist/domain/binaryTarget.js";
@ -64,6 +64,27 @@ const nativeBoundaryLimits = {
maxHeapItemBytes: 1024 * 1024,
};
const appManifestInspectionLimits = {
...inspectionLimits,
maxMetadataBytes: 64 * 1024 * 1024,
maxTableRows: 250_000,
maxHeapItemBytes: 16 * 1024 * 1024,
};
const appManifestMemberLimits = {
...memberLimits,
typeLimit: 1,
methodLimit: 1,
fieldLimit: 1,
memberRefLimit: 1,
edgeLimit: 1,
maxMetadataBytes: 64 * 1024 * 1024,
maxTableRows: 250_000,
maxHeapItemBytes: 16 * 1024 * 1024,
maxMethodBodyBytes: 4 * 1024 * 1024,
maxMethodInstructions: 20_000,
};
const comparisonLimits = {
max_method_matches: 100,
max_field_matches: 100,
@ -389,9 +410,12 @@ try {
);
assert.equal(malformedResult.classification.status, "malformed");
const manifestSelfTest = await runManagedAppManifestSelfTest();
const operatorManifest = await runOptionalManagedAppManifest();
process.stdout.write(
`${JSON.stringify({
verified: 10,
verified: 11 + (operatorManifest === null ? 0 : 1),
managedSurfaces: [
"inspect_managed_artifact",
"inspect_managed_members",
@ -412,7 +436,13 @@ try {
"mvid-and-token-drift",
"not-managed",
"malformed-metadata",
"operator-local-managed-manifest",
],
managedAppManifest: {
env: "REA_MANAGED_APP_MANIFEST_PATH",
selfTest: manifestSelfTest,
operator: operatorManifest ?? { configured: false },
},
})}\n`,
);
} finally {
@ -435,6 +465,326 @@ function sha256(bytes) {
return createHash("sha256").update(bytes).digest("hex");
}
async function runManagedAppManifestSelfTest() {
const sample = await fixture("manifest-self-test.exe", {
methodName: "PinnedSemanticSlice",
ilBody: defaultIlBody,
});
const artifact = inspectManagedArtifactBytes(
sample.bytes,
sample.target,
inspectionLimits,
);
const members = inspectManagedMembersBytes(
sample.bytes,
sample.target,
memberLimits,
);
const method = members.methods.items[0];
assert.ok(method);
const summary = await verifyManagedAppManifest(
{
schema_version: 1,
label: "source-owned manifest verifier self-test",
target: {
path: sample.path,
sha256: sample.target.sha256,
mvid: artifact.module?.mvid,
assembly_name: artifact.assembly?.name,
runtime_family: artifact.classification.runtime_family,
managed_architecture: artifact.classification.managed_architecture,
},
methods: [
{
label: "pinned-semantic-slice",
token: method.token,
signature_sha256: method.signature.raw_sha256,
il_size: method.body.il_size,
normalized_il_sha256: method.body.normalized_il_sha256,
},
],
},
join(workspace, "source-owned-managed-app-manifest.json"),
);
return {
verified: true,
assertions: summary.assertions,
methods: summary.methods.length,
target: summary.target,
};
}
async function runOptionalManagedAppManifest() {
const manifestPath = process.env.REA_MANAGED_APP_MANIFEST_PATH;
if (manifestPath === undefined) return null;
if (manifestPath.trim().length === 0)
throw new Error("REA_MANAGED_APP_MANIFEST_PATH is set but empty");
const resolvedManifestPath = resolve(process.cwd(), manifestPath);
const text = await readFile(resolvedManifestPath, "utf8");
let manifest;
try {
manifest = JSON.parse(text);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(
`managed app manifest ${resolvedManifestPath} is not valid JSON: ${message}`,
);
}
return {
configured: true,
...(await verifyManagedAppManifest(manifest, resolvedManifestPath)),
};
}
async function verifyManagedAppManifest(rawManifest, manifestPath) {
const manifest = parseManagedAppManifest(rawManifest);
const targetPath = isAbsolute(manifest.target.path)
? manifest.target.path
: resolve(dirname(manifestPath), manifest.target.path);
const parsedTarget = await parseBinaryTarget(targetPath);
ensure(
parsedTarget.ok,
`target ${targetPath} could not be parsed as a binary target`,
);
const target = parsedTarget.value;
ensure(
target.sha256 === manifest.target.sha256,
`target sha256 mismatch: expected ${manifest.target.sha256}, observed ${target.sha256}`,
);
const bytes = await readFile(targetPath);
const artifact = inspectManagedArtifactBytes(
bytes,
target,
appManifestInspectionLimits,
);
ensure(
artifact.classification.status === "managed",
`target is ${artifact.classification.status}, expected managed`,
);
if (manifest.target.mvid !== undefined)
ensure(
artifact.module?.mvid === manifest.target.mvid,
`target MVID mismatch: expected ${manifest.target.mvid}, observed ${artifact.module?.mvid ?? "null"}`,
);
if (manifest.target.assembly_name !== undefined)
ensure(
artifact.assembly?.name === manifest.target.assembly_name,
`assembly name mismatch: expected ${manifest.target.assembly_name}, observed ${artifact.assembly?.name ?? "null"}`,
);
if (manifest.target.runtime_family !== undefined)
ensure(
artifact.classification.runtime_family === manifest.target.runtime_family,
`runtime family mismatch: expected ${manifest.target.runtime_family}, observed ${artifact.classification.runtime_family}`,
);
if (manifest.target.managed_architecture !== undefined)
ensure(
artifact.classification.managed_architecture ===
manifest.target.managed_architecture,
`managed architecture mismatch: expected ${manifest.target.managed_architecture}, observed ${artifact.classification.managed_architecture}`,
);
const methods = [];
let assertions = 1;
for (const expectedMethod of manifest.methods) {
const method = inspectManagedMethodByToken(bytes, target, expectedMethod);
ensure(
method.signature.raw_sha256 === expectedMethod.signature_sha256,
`method ${expectedMethod.token} signature sha256 mismatch: expected ${expectedMethod.signature_sha256}, observed ${method.signature.raw_sha256}`,
);
ensure(
method.body.il_size === expectedMethod.il_size,
`method ${expectedMethod.token} IL size mismatch: expected ${String(expectedMethod.il_size)}, observed ${String(method.body.il_size)}`,
);
ensure(
method.body.normalized_il_sha256 === expectedMethod.normalized_il_sha256,
`method ${expectedMethod.token} normalized IL sha256 mismatch: expected ${expectedMethod.normalized_il_sha256}, observed ${method.body.normalized_il_sha256 ?? "null"}`,
);
assertions += 4;
methods.push({
label: expectedMethod.label ?? null,
token: method.token,
declaring_type: method.declaring_type,
name: method.name,
signature_sha256: method.signature.raw_sha256,
il_size: method.body.il_size,
normalized_il_sha256: method.body.normalized_il_sha256,
});
}
return {
label: manifest.label ?? null,
assertions,
target: {
file_name: basename(targetPath),
sha256: target.sha256,
mvid: artifact.module?.mvid ?? null,
assembly_name: artifact.assembly?.name ?? null,
runtime_family: artifact.classification.runtime_family,
managed_architecture: artifact.classification.managed_architecture,
},
methods,
};
}
function inspectManagedMethodByToken(bytes, target, expectedMethod) {
const row = methodTokenRow(expectedMethod.token);
const members = inspectManagedMembersBytes(bytes, target, {
...appManifestMemberLimits,
methodOffset: row - 1,
});
const method = members.methods.items.find(
({ token }) => token === expectedMethod.token,
);
ensure(
method !== undefined,
`method ${expectedMethod.token} was not returned by the member inspector`,
);
ensure(
method.body.status === "present",
`method ${expectedMethod.token} body status is ${method.body.status}, expected present`,
);
return method;
}
function parseManagedAppManifest(rawManifest) {
const manifest = object(rawManifest, "manifest");
ensure(
manifest.schema_version === 1,
"manifest.schema_version must be exactly 1",
);
const target = object(manifest.target, "manifest.target");
const methods = array(manifest.methods, "manifest.methods");
ensure(manifest.methods.length > 0, "manifest.methods must not be empty");
return {
schema_version: 1,
label: optionalString(manifest.label, "manifest.label"),
target: {
path: string(target.path, "manifest.target.path"),
sha256: digest(target.sha256, "manifest.target.sha256"),
mvid: optionalUuid(target.mvid, "manifest.target.mvid"),
assembly_name: optionalString(
target.assembly_name,
"manifest.target.assembly_name",
),
runtime_family: optionalString(
target.runtime_family,
"manifest.target.runtime_family",
),
managed_architecture: optionalString(
target.managed_architecture,
"manifest.target.managed_architecture",
),
},
methods: methods.map((method, index) =>
parseManagedAppManifestMethod(method, index),
),
};
}
function parseManagedAppManifestMethod(rawMethod, index) {
const prefix = `manifest.methods[${String(index)}]`;
const method = object(rawMethod, prefix);
const token = metadataToken(method.token, `${prefix}.token`);
ensure(
token.startsWith("0x06"),
`${prefix}.token must be a MethodDef token beginning with 0x06`,
);
return {
label: optionalString(method.label, `${prefix}.label`),
token,
signature_sha256: digest(
method.signature_sha256,
`${prefix}.signature_sha256`,
),
il_size: ilSize(method, prefix),
normalized_il_sha256: digest(
method.normalized_il_sha256,
`${prefix}.normalized_il_sha256`,
),
};
}
function ilSize(method, prefix) {
const hasIlSize = method.il_size !== undefined;
const hasIlLength = method.il_length !== undefined;
ensure(
hasIlSize || hasIlLength,
`${prefix}.il_size is required; il_length is accepted as a legacy alias`,
);
if (hasIlSize && hasIlLength)
ensure(
method.il_size === method.il_length,
`${prefix}.il_size and ${prefix}.il_length disagree`,
);
const value = hasIlSize ? method.il_size : method.il_length;
ensure(
Number.isInteger(value) && value >= 0,
`${prefix}.il_size must be a non-negative integer`,
);
return value;
}
function object(value, name) {
ensure(
value !== null && typeof value === "object" && !Array.isArray(value),
`${name} must be an object`,
);
return value;
}
function array(value, name) {
ensure(Array.isArray(value), `${name} must be an array`);
return value;
}
function string(value, name) {
ensure(
typeof value === "string" && value.length > 0,
`${name} must be a non-empty string`,
);
return value;
}
function optionalString(value, name) {
if (value === undefined) return undefined;
return string(value, name);
}
function digest(value, name) {
const text = string(value, name);
ensure(/^[a-f0-9]{64}$/u.test(text), `${name} must be a lowercase sha256`);
return text;
}
function optionalUuid(value, name) {
if (value === undefined) return undefined;
if (value === null) return null;
const text = string(value, name);
ensure(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.test(
text,
),
`${name} must be a lowercase GUID`,
);
return text;
}
function metadataToken(value, name) {
const text = string(value, name);
ensure(/^0x[0-9a-f]{8}$/u.test(text), `${name} must be a metadata token`);
return text;
}
function methodTokenRow(token) {
const row = Number.parseInt(token.slice(4), 16);
ensure(row > 0, `method token ${token} has an invalid row id`);
return row;
}
function ensure(condition, message) {
if (!condition)
throw new Error(`managed app manifest verification failed: ${message}`);
}
function functionDossier(name) {
const emptyPage = {
items: [],