import { afterEach, describe, it } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { Identity } from "@commonfabric/identity"; import { StorageManager } from "@commonfabric/runner/storage/cache.deno"; import { Runtime } from "../src/runtime.ts"; import { resolvePolicyFacingImplementationIdentity } from "../src/cfc/implementation-identity.ts"; import { getVerifiedProvenance, recordVerifiedProvenance, } from "../src/harness/verified-provenance.ts"; import { VERIFIED_BINDING_METADATA_FIELD } from "@commonfabric/utils/sandbox-contract"; import { brandTrustedBuilderArtifact, isTrustedBuilderArtifact, } from "../src/builder/pattern-metadata.ts"; import { ExecutableRegistry } from "../src/harness/executable-registry.ts"; import type { JSONSchema, Module, Pattern } from "../src/builder/types.ts"; import type { HarnessedFunction } from "../src/harness/types.ts"; /** * C5 red-team gate for PR C (content-addressed `$implRef` + CFC provenance) of * docs/specs/content-addressed-action-identity-implementation-plan.md. * * Each test is an attack on a fail-closed property; the assertion is the * defended outcome. The mirror style is test/cfreg-security.test.ts (pin the * runtime trust seams) and test/content-addressed-identity.test.ts (the happy * path these attacks try to subvert). * * The trust model these attacks probe: * - A function is "verified" ONLY via a provenance WeakMap entry, written by * two runner-owned channels (trust-gated module indexing, and the in-action * registrar). An attacker-supplied object never traverses either, so it has * no entry — there is no string key to collide. * - `$implRef` in serialized data is a HINT for WHICH genuine indexed artifact * to run. It can never name a forged executable (only trust-gated artifacts * are indexed) and it never feeds the CFC identity: identity is computed from * the RESOLVED function's provenance, so editing the ref cannot borrow * another module's authority. * - writeAuthorizedBy is an ownership gate: a verified-binding claim verifies * only when the resolved identity's moduleIdentity (or legacy bundleId), * file, AND path all match. Any mismatch — or a claim with neither id field — * fails closed. */ const signer = await Identity.fromPassphrase("ca-identity-adversarial"); // A handler program with TWO module-scope handlers, so we have two distinct // genuine artifacts/identities to cross-wire in the $implRef-confusion attacks. const PROGRAM = { main: "/main.tsx", files: [{ name: "/main.tsx", contents: `/// import { handler, pattern, Writable } from "commonfabric"; const setName = handler<{ name?: string }, { name: Writable }>( (event, state) => { state.name.set(event.name ?? ""); }, ); const setLabel = handler<{ label?: string }, { label: Writable }>( (event, state) => { state.label.set(event.label ?? "L:" + (event.label ?? "")); }, ); export default pattern(() => { const name = new Writable("").for("name"); const label = new Writable("").for("label"); return { name, label, setName: setName({ name }), setLabel: setLabel({ label }), }; }); `, }], }; describe("content-addressed identity — adversarial (C5 red-team gate)", () => { let storageManager: ReturnType | undefined; let runtime: Runtime | undefined; afterEach(async () => { await runtime?.dispose(); await storageManager?.close(); runtime = undefined; storageManager = undefined; }); const setup = async () => { storageManager = StorageManager.emulate({ as: signer }); runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager, cfcEnforcementMode: "observe", }); const pattern = await runtime.patternManager.compilePattern(PROGRAM); await runtime.idle(); return pattern as Pattern; }; const handlerModules = (pattern: Pattern): Module[] => pattern.nodes .filter((n) => (n.module as Module).type === "javascript" && (n.module as Module).wrapper === "handler" ) .map((n) => n.module as Module); // --------------------------------------------------------------------------- // Attack 1 — forged function with byte-identical source, built outside eval. // --------------------------------------------------------------------------- describe("attack 1: byte-identical forged function", () => { it("a forged twin (identical source, no eval registration) has no provenance and resolves unsupported", async () => { const pattern = await setup(); const real = handlerModules(pattern)[0] .implementation as HarnessedFunction; expect(getVerifiedProvenance(real)).toBeDefined(); // Reconstruct the exact source bytes OUTSIDE any verified evaluation. const forged = new Function( `return ${Function.prototype.toString.call(real)}`, )() as HarnessedFunction; expect(getVerifiedProvenance(forged)).toBeUndefined(); const identity = resolvePolicyFacingImplementationIdentity( handlerModules(pattern)[0], { implementation: forged }, ); // Never "verified": no provenance, no legacy registry entry. expect(identity?.kind).not.toBe("verified"); }); it("a forged twin that ALSO steals the real fn's canonical src still gets no provenance", async () => { const pattern = await setup(); const real = handlerModules(pattern)[0] .implementation as HarnessedFunction; const stolenSrc = (real as { src?: string }).src!; expect(stolenSrc).toContain("cf:module/"); // The forged fn carries the genuine canonical src — but src is just an // own-property; verification keys on the provenance WeakMap, which has no // entry for this object. const forged = Object.assign( new Function(`return ${Function.prototype.toString.call(real)}`)(), { src: stolenSrc }, ) as HarnessedFunction; expect(getVerifiedProvenance(forged)).toBeUndefined(); const identity = resolvePolicyFacingImplementationIdentity( handlerModules(pattern)[0], { implementation: forged }, ); expect(identity?.kind).not.toBe("verified"); }); }); // --------------------------------------------------------------------------- // Attack 2 — $implRef replay / confusion. State graphs are attacker data. // --------------------------------------------------------------------------- describe("attack 2: $implRef replay/confusion", () => { it("$implRef at a non-existent or host: identity resolves nothing executable (miss → fallback)", async () => { await setup(); const pm = runtime!.patternManager; // Never-evaluated identity. expect(pm.artifactFromIdentitySync("not-a-real-identity", "default")) .toBeUndefined(); // host:-shaped identity — there is no entry, so a miss. expect(pm.artifactFromIdentitySync("host:evil", "setName")) .toBeUndefined(); }); it("$implRef pointing at a real identity with the WRONG symbol misses (never a forged fn)", async () => { const pattern = await setup(); const pm = runtime!.patternManager; const mod = handlerModules(pattern)[0]; const prov = getVerifiedProvenance( mod.implementation as HarnessedFunction, )!; // The genuine identity, but a symbol that was never registered under it. expect( pm.artifactFromIdentitySync(prov.identity, "symbol-not-registered"), ) .toBeUndefined(); // Sanity: the genuine symbol DOES resolve, to a builder artifact (never raw data). const ok = pm.artifactFromIdentitySync(prov.identity, prov.symbol!); expect(ok).toBeDefined(); expect(isTrustedBuilderArtifact(ok)).toBe(true); }); it("CFC identity is computed from the RESOLVED fn's provenance, NOT from $implRef in the data", async () => { const pattern = await setup(); const [modA, modB] = handlerModules(pattern); const fnA = modA.implementation as HarnessedFunction; const provA = getVerifiedProvenance(fnA)!; const provB = getVerifiedProvenance( modB.implementation as HarnessedFunction, )!; // The two handlers live in the SAME module, so they share a moduleIdentity; // their distinct `symbol`s are the per-binding discriminator (and what a // verified-binding writeAuthorizedBy claim keys on via bindingPath). expect(provA.identity).toBe(provB.identity); expect(provA.symbol).not.toBe(provB.symbol); // Forge module A's serialized graph so its $implRef borrows B's // {identity, symbol} (the classic authority-borrow). State graphs are // attacker-influençable — assume the attacker rewrote the ref freely. const forgedModuleA = { ...modA, $implRef: { identity: provB.identity, symbol: provB.symbol }, } as unknown as Module; // CFC identity is resolved from the function actually invoked (fnA), never // from the ref in the data: the resolver reads fnA's provenance, so the // policy-facing identity carries A's symbol — the forged ref to B is inert. const identity = resolvePolicyFacingImplementationIdentity( forgedModuleA, { implementation: fnA, }, ); expect(identity?.kind).toBe("verified"); const v = identity as { kind: "verified"; moduleIdentity?: string; symbol?: string; }; expect(v.moduleIdentity).toBe(provA.identity); expect(v.symbol).toBe(provA.symbol); expect(v.symbol).not.toBe(provB.symbol); }); it("a bad $implRef cannot make a __cf_data-forged value executable: the index never holds one", async () => { const pattern = await setup(); const pm = runtime!.patternManager; const prov = getVerifiedProvenance( handlerModules(pattern)[0].implementation as HarnessedFunction, )!; // Whatever the (genuine) identity+symbol resolve to, it is a trusted // builder artifact, never a plain/forged value — that is the only thing a // resolved $implRef can ever hand back. const artifact = pm.artifactFromIdentitySync(prov.identity, prov.symbol!); expect(isTrustedBuilderArtifact(artifact)).toBe(true); }); }); // --------------------------------------------------------------------------- // Attack 3 — __cf_data-forged factory shapes through the indexing sink. // --------------------------------------------------------------------------- describe("attack 3: __cf_data-forged factory shapes", () => { it("an unbranded {implementation, __cfVerifiedBindingIdentity} shape is dropped by the trust gate", () => { const forgedFactory = { implementation: function forgedImpl() {}, [VERIFIED_BINDING_METADATA_FIELD]: { sourceFile: "/victim.tsx", bindingPath: ["ownerOnlyHandler"], }, }; // The gate that indexArtifact consults before recording provenance. expect(isTrustedBuilderArtifact(forgedFactory)).toBe(false); // And it carries no provenance (it never went through indexArtifact). expect(getVerifiedProvenance(forgedFactory.implementation)) .toBeUndefined(); }); it("even a branded look-alike's nested implementation gains nothing without going through indexArtifact", () => { // brandTrustedBuilderArtifact would let the OBJECT pass the gate, but // provenance for the IMPLEMENTATION fn is written only by indexArtifact / // the in-action registrar — never by branding alone. const fn = function notVerified() {}; const branded = brandTrustedBuilderArtifact({ implementation: fn, [VERIFIED_BINDING_METADATA_FIELD]: { sourceFile: "/victim.tsx", bindingPath: ["ownerOnlyHandler"], }, }); expect(isTrustedBuilderArtifact(branded)).toBe(true); expect(getVerifiedProvenance(fn)).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // Attack 4 — bindingIdentity spoofing (highest-value: ownership gate). // --------------------------------------------------------------------------- describe("attack 4: bindingIdentity spoofing of writeAuthorizedBy ownership", () => { it("an attacker-chosen __cfVerifiedBindingIdentity on an unbranded factory yields no verified binding", () => { // The factory carries a binding annotation pointing at a victim's // owner-protected cell. Without the trust brand it never reaches // indexArtifact, so readBindingIdentity is never consulted FOR PROVENANCE // and the fn stays unverified. const fn = function attacker() {}; const factory = Object.assign(fn, { [VERIFIED_BINDING_METADATA_FIELD]: { sourceFile: "/victim.tsx", bindingPath: ["victimOwnedField"], }, }); expect(isTrustedBuilderArtifact(factory)).toBe(false); expect(getVerifiedProvenance(factory)).toBeUndefined(); }); it("a verified-binding writeAuthorizedBy claim fails closed when the resolved identity's bindingPath differs", async () => { storageManager = StorageManager.emulate({ as: signer }); runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager, cfcEnforcementMode: "enforce-explicit", trustSnapshotProvider: () => ({ id: "ts-attack4", actingPrincipal: signer.did(), }), }); const tx = runtime.edit(); const schema = { type: "object", properties: { owned: { type: "string", ifc: { writeAuthorizedBy: { __ctWriterIdentityOf: { // Claim is owned by VICTIM module + path. moduleIdentity: "victim-module-identity", file: "/victim.tsx", path: ["victimOwnedField"], }, }, }, }, }, required: ["owned"], } as unknown as JSONSchema; const cell = runtime.getCell(signer.did(), "attack4-binding", schema, tx); // Attacker's verified identity claims a DIFFERENT module/path (their own). tx.setCfcImplementationIdentity({ kind: "verified", moduleIdentity: "attacker-module-identity", sourceFile: "/attacker.tsx", bindingPath: ["attackerOwnedField"], }); cell.set({ owned: "stolen" }); // moduleIdentity, file, AND path must all match; none do → fail closed. const digest = tx.prepareCfc(); expect(digest).toBe(""); const result = await tx.commit(); expect(result.error).toBeDefined(); }); it("the claim's bindingPath cannot be satisfied by a matching moduleIdentity alone (path must also match)", async () => { storageManager = StorageManager.emulate({ as: signer }); runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager, cfcEnforcementMode: "enforce-explicit", trustSnapshotProvider: () => ({ id: "ts-attack4b", actingPrincipal: signer.did(), }), }); const tx = runtime.edit(); const schema = { type: "object", properties: { owned: { type: "string", ifc: { writeAuthorizedBy: { __ctWriterIdentityOf: { moduleIdentity: "shared-module-identity", file: "/shared.tsx", path: ["ownerHandler"], }, }, }, }, }, required: ["owned"], } as unknown as JSONSchema; const cell = runtime.getCell( signer.did(), "attack4b-binding", schema, tx, ); // Same module + file, but a DIFFERENT binding (a sibling handler in the // same module must not be able to write the owner's field). tx.setCfcImplementationIdentity({ kind: "verified", moduleIdentity: "shared-module-identity", sourceFile: "/shared.tsx", bindingPath: ["someOtherHandler"], }); cell.set({ owned: "stolen" }); const digest = tx.prepareCfc(); expect(digest).toBe(""); const result = await tx.commit(); expect(result.error).toBeDefined(); }); it("an already-stamped claim is NOT re-stamped by a different verified writer (no rebind on stamped claims)", async () => { // The rebind that stamps an unstamped claim no-ops once a claim already // carries an id field. So a stored owner-protected field (claim already // stamped with the OWNER's moduleIdentity) cannot be re-stamped — and thus // cannot be written — by a different verified handler. This is the durable // ownership boundary for pre-existing fields. storageManager = StorageManager.emulate({ as: signer }); runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager, cfcEnforcementMode: "enforce-explicit", trustSnapshotProvider: () => ({ id: "ts-attack4d", actingPrincipal: signer.did(), }), }); const tx = runtime.edit(); const schema = { type: "object", properties: { owned: { type: "string", ifc: { writeAuthorizedBy: { __ctWriterIdentityOf: { // ALREADY stamped to the owner module — not unstamped. moduleIdentity: "owner-module", file: "/owner.tsx", path: ["ownerHandler"], }, }, }, }, }, required: ["owned"], } as unknown as JSONSchema; const cell = runtime.getCell( signer.did(), "attack4d-binding", schema, tx, ); // Different verified writer — the rebind cannot overwrite the owner's stamp. tx.setCfcImplementationIdentity({ kind: "verified", moduleIdentity: "attacker-module", sourceFile: "/attacker.tsx", bindingPath: ["attackerHandler"], }); cell.set({ owned: "stolen" }); const digest = tx.prepareCfc(); expect(digest).toBe(""); const result = await tx.commit(); expect(result.error).toBeDefined(); }); it("a fully matching verified-binding identity is accepted (defense is not vacuous)", async () => { storageManager = StorageManager.emulate({ as: signer }); runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager, cfcEnforcementMode: "enforce-explicit", trustSnapshotProvider: () => ({ id: "ts-attack4c", actingPrincipal: signer.did(), }), }); const tx = runtime.edit(); const schema = { type: "object", properties: { owned: { type: "string", ifc: { writeAuthorizedBy: { __ctWriterIdentityOf: { moduleIdentity: "match-module-identity", file: "/match.tsx", path: ["ownerHandler"], }, }, }, }, }, required: ["owned"], } as unknown as JSONSchema; const cell = runtime.getCell( signer.did(), "attack4c-binding", schema, tx, ); tx.setCfcImplementationIdentity({ kind: "verified", moduleIdentity: "match-module-identity", sourceFile: "/match.tsx", bindingPath: ["ownerHandler"], }); cell.set({ owned: "authorized" }); const result = await tx.commit(); // The writeAuthorizedBy arm passes; any remaining reason would be unrelated // to identity-borrowing. Assert specifically no writeAuthorizedBy failure. if (result.error) { expect(String(result.error)).not.toContain("writeAuthorizedBy"); } }); }); // --------------------------------------------------------------------------- // Attack 5 — claim forgery in stored schemas (hand-built __ctWriterIdentityOf). // --------------------------------------------------------------------------- describe("attack 5: forged stored writeAuthorizedBy claims", () => { const driveClaim = async ( claim: Record, identity: Record, name: string, ) => { storageManager = StorageManager.emulate({ as: signer }); runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager, cfcEnforcementMode: "enforce-explicit", trustSnapshotProvider: () => ({ id: `ts-${name}`, actingPrincipal: signer.did(), }), }); const tx = runtime.edit(); const schema = { type: "object", properties: { owned: { type: "string", ifc: { writeAuthorizedBy: claim } }, }, required: ["owned"], } as unknown as JSONSchema; const cell = runtime.getCell(signer.did(), name, schema, tx); tx.setCfcImplementationIdentity(identity as never); cell.set({ owned: "x" }); const digest = tx.prepareCfc(); const result = await tx.commit(); return { digest, result }; }; it("a claim with moduleIdentity matching but file/path NOT fails closed", async () => { const { digest, result } = await driveClaim( { __ctWriterIdentityOf: { moduleIdentity: "m1", file: "/owner.tsx", path: ["ownerHandler"], }, }, { kind: "verified", moduleIdentity: "m1", // matches sourceFile: "/attacker.tsx", // does NOT bindingPath: ["attackerHandler"], // does NOT }, "attack5-id-match-pathmismatch", ); expect(digest).toBe(""); expect(result.error).toBeDefined(); }); it("a claim with file/path matching but moduleIdentity NOT fails closed", async () => { const { digest, result } = await driveClaim( { __ctWriterIdentityOf: { moduleIdentity: "owner-module", file: "/owner.tsx", path: ["ownerHandler"], }, }, { kind: "verified", moduleIdentity: "attacker-module", // does NOT sourceFile: "/owner.tsx", // matches bindingPath: ["ownerHandler"], // matches }, "attack5-pathmatch-idmismatch", ); expect(digest).toBe(""); expect(result.error).toBeDefined(); }); it("a claim carrying NEITHER id field (no moduleIdentity, no bundleId) fails closed against a non-rebinding writer", async () => { // NOTE: a VERIFIED writer would trigger rebindWriteAuthorizedByClaims, // which stamps an UNSTAMPED claim with the writer's own moduleIdentity — // the by-design self-authoring path (a handler authoring its OWN fresh // claim), NOT an attack. To isolate the fail-closed property of a claim // that genuinely carries no id field, use a builtin writer: the rebind // does not fire for non-verified writers, so the verified-binding claim is // checked as-is and cannot match (it demands a verified writer anyway). const { digest, result } = await driveClaim( { __ctWriterIdentityOf: { // No moduleIdentity, no bundleId. file: "/owner.tsx", path: ["ownerHandler"], }, }, { kind: "builtin", builtinId: "someBuiltin" }, "attack5-no-id-field", ); expect(digest).toBe(""); expect(result.error).toBeDefined(); }); it("an unstamped claim authored by a verified writer is stamped to THAT writer (self-authoring, not borrowing)", async () => { // The flip side of the note above, pinned explicitly: when a verified // handler writes a field whose claim has no id field yet, the claim is // bound to the writer's own moduleIdentity/file/path. This is the // legitimate creation step; it does NOT let the writer borrow a DIFFERENT // owner's authority, because the stamp is the writer's own identity. (The // borrow attempts — wrong moduleIdentity / wrong path against an ALREADY // STAMPED claim — are the failing cases above.) const { digest, result } = await driveClaim( { __ctWriterIdentityOf: { file: "/self.tsx", path: ["selfHandler"] }, }, { kind: "verified", moduleIdentity: "self-module", sourceFile: "/self.tsx", bindingPath: ["selfHandler"], }, "attack5-self-authoring", ); // Accepted: the writer authored its own claim (file/path agree with the // writer's identity, and the missing id field is stamped to self). if (result.error) { expect(String(result.error)).not.toContain("writeAuthorizedBy"); } expect(typeof digest).toBe("string"); }); it("a legacy claim (bundleId only) does NOT match a moduleIdentity-only identity (no cross-arm confusion)", async () => { const { digest, result } = await driveClaim( { __ctWriterIdentityOf: { bundleId: "legacy-bundle", // legacy arm only file: "/owner.tsx", path: ["ownerHandler"], }, }, { kind: "verified", // moduleIdentity present but the identity carries NO bundleId, so the // legacy (bundleId) arm the claim selects cannot be satisfied. moduleIdentity: "legacy-bundle", // must not be read as a bundleId sourceFile: "/owner.tsx", bindingPath: ["ownerHandler"], }, "attack5-bundleid-arm-no-confusion", ); expect(digest).toBe(""); expect(result.error).toBeDefined(); }); }); // --------------------------------------------------------------------------- // Attack 6 — fn.src spoofing vs the provenance identity. // --------------------------------------------------------------------------- describe("attack 6: fn.src spoofing", () => { // These probe resolveProvenanceImplementationIdentity directly: we fabricate // a provenance entry (the trusted channel's effect) on a function WE control, // then mismatch its src. recordVerifiedProvenance is the exact write the real // indexer performs; here we use it to isolate the src↔provenance check. it("a verified fn whose src names a DIFFERENT module than its provenance fails closed", () => { const fn = Object.assign(() => undefined, { src: "cf:module/REAL_MODULE_A/main.tsx:4:12", }); recordVerifiedProvenance(fn, { identity: "REAL_MODULE_A", symbol: "ownerHandler", }); // Now spoof src to claim it belongs to a different (victim) module. (fn as { src: string }).src = "cf:module/VICTIM_MODULE_B/main.tsx:4:12"; const identity = resolvePolicyFacingImplementationIdentity( { type: "javascript" } as Module, { implementation: fn }, ); expect(identity?.kind).toBe("unsupported"); }); it("a verified fn whose src OMITS the cf:module prefix fails closed (no identity to extract)", () => { const fn = Object.assign(() => undefined, { src: "/main.tsx:4:12", // no cf:module// prefix }); recordVerifiedProvenance(fn, { identity: "SOME_MODULE", symbol: "ownerHandler", }); const identity = resolvePolicyFacingImplementationIdentity( { type: "javascript" } as Module, { implementation: fn }, ); // identityFromCanonicalSource("/main.tsx...") is undefined ≠ provenance id. expect(identity?.kind).toBe("unsupported"); }); it("a verified fn whose src matches its provenance resolves verified (check is not vacuous)", () => { const fn = Object.assign(() => undefined, { src: "cf:module/CONSISTENT_MODULE/main.tsx:7:3", }); recordVerifiedProvenance(fn, { identity: "CONSISTENT_MODULE", symbol: "ownerHandler", }); const identity = resolvePolicyFacingImplementationIdentity( { type: "javascript" } as Module, { implementation: fn }, ); expect(identity?.kind).toBe("verified"); const v = identity as { kind: "verified"; moduleIdentity?: string }; expect(v.moduleIdentity).toBe("CONSISTENT_MODULE"); }); }); // --------------------------------------------------------------------------- // Attack 7 — dynamic (in-action-minted) artifact path. // --------------------------------------------------------------------------- describe("attack 7: dynamic in-session artifacts", () => { it("a dynamic-provenance fn with consistent src resolves verified but carries no symbol (no cross-session ref)", () => { const fn = Object.assign(() => undefined, { src: "cf:module/DYN_MODULE/main.tsx:2:1", }); // Mirrors the in-action registrar: identity from canonical src, dynamic, // NO symbol. recordVerifiedProvenance(fn, { identity: "DYN_MODULE", dynamic: true }); const identity = resolvePolicyFacingImplementationIdentity( { type: "javascript" } as Module, { implementation: fn }, ); // It is verified IN SESSION (the provenance exists and src matches)... expect(identity?.kind).toBe("verified"); const v = identity as { kind: "verified"; symbol?: string }; // ...but carries no symbol, so it serializes WITHOUT a resolvable $implRef // — i.e. no cross-session authority (it cannot be re-resolved on reload). expect(v.symbol).toBeUndefined(); expect(getVerifiedProvenance(fn)!.symbol).toBeUndefined(); }); it("a dynamic-provenance fn whose src disagrees with its identity still fails closed", () => { const fn = Object.assign(() => undefined, { src: "cf:module/OTHER_MODULE/main.tsx:2:1", }); recordVerifiedProvenance(fn, { identity: "DYN_MODULE", dynamic: true }); const identity = resolvePolicyFacingImplementationIdentity( { type: "javascript" } as Module, { implementation: fn }, ); expect(identity?.kind).toBe("unsupported"); }); it("a dynamic artifact (no symbol) serializes without $implRef", async () => { // moduleToJSON only emits $implRef when provenance.symbol is present, so a // dynamic (symbol-less) artifact never gets a serialized, reload-resolvable // reference — confirming in-session-only authority on the serialization seam. const pattern = await setup(); const mod = handlerModules(pattern)[0]; const fn = mod.implementation as HarnessedFunction; // Drop the real (symbol-bearing) entry by GC isn't possible; instead build // a fresh fn with only dynamic provenance and a module shaped like the real // one, then serialize it. const dyn = Object.assign(function dynImpl() {}, { src: (fn as { src?: string }).src, implementationRef: "dyn-ref", }); recordVerifiedProvenance(dyn, { identity: getVerifiedProvenance(fn)!.identity, dynamic: true, }); const dynModule = { type: "javascript" as const, implementation: dyn, implementationRef: "dyn-ref", toJSON: undefined as unknown, }; // moduleToJSON is reached via the builder; call the same path the real // module uses. We re-import it lazily to avoid widening the import surface. const { moduleToJSON } = await import("../src/builder/json-utils.ts"); const json = moduleToJSON(dynModule as unknown as Module) as Record< string, unknown >; expect(json.$implRef).toBeUndefined(); }); }); // =========================================================================== // E2 red-team gate. The attacks below target the surfaces PR E2 introduced // (provenance-only verified identity, the now-LIVE bundleId verification arm, // the strong content-addressed implementation index, and the loadId-less // dynamic registrar). They are the negative complement to the happy-path // tests in content-addressed-identity.test.ts. // =========================================================================== // A claim/identity driver matching attack 5's, lifted to the outer scope so // the E2 bundleId-arm attacks can reuse it. const driveE2Claim = async ( claim: Record, identity: Record, name: string, ) => { storageManager = StorageManager.emulate({ as: signer }); runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager, cfcEnforcementMode: "enforce-explicit", trustSnapshotProvider: () => ({ id: `ts-${name}`, actingPrincipal: signer.did(), }), }); const tx = runtime.edit(); const schema = { type: "object", properties: { owned: { type: "string", ifc: { writeAuthorizedBy: claim } }, }, required: ["owned"], } as unknown as JSONSchema; const cell = runtime.getCell(signer.did(), name, schema, tx); tx.setCfcImplementationIdentity(identity as never); cell.set({ owned: "x" }); const digest = tx.prepareCfc(); const result = await tx.commit(); return { digest, result }; }; // --------------------------------------------------------------------------- // Attacks 8 + 9 (historical) — the legacy bundleId verification arm and the // dynamic-artifact registrar are GONE (identity E5): a claim without a // moduleIdentity fails closed, and minting builder artifacts inside an // action throws at creation time (test/dynamic-builder-call-throw.test.ts). // What remains to pin is the fail-closed floor for stored bundleId-only // claims: they never verify, against ANY identity. // --------------------------------------------------------------------------- describe("attacks 8+9 (retired arms): bundleId-only claims always fail closed", () => { it("a bundleId-only claim is rejected even when the writer is fully verified", async () => { const { digest, result } = await driveE2Claim( { __ctWriterIdentityOf: { bundleId: "ANY_BUNDLE", file: "/main.tsx", path: ["localFunction"], }, }, { kind: "verified", moduleIdentity: "module-hash-1", sourceFile: "/main.tsx", bindingPath: ["localFunction"], }, "attack8-bundle-only-rejected", ); expect(digest).toBe(""); expect(result.error).toBeDefined(); }); it("a claim carrying BOTH ids verifies on moduleIdentity alone (bundleId is inert)", async () => { const { digest, result } = await driveE2Claim( { __ctWriterIdentityOf: { bundleId: "IGNORED", moduleIdentity: "module-hash-1", file: "/main.tsx", path: ["localFunction"], }, }, { kind: "verified", moduleIdentity: "module-hash-1", sourceFile: "/main.tsx", bindingPath: ["localFunction"], }, "attack8-both-ids-module-arm", ); expect(typeof digest).toBe("string"); if (result.error) { expect(String(result.error)).not.toContain("writeAuthorizedBy"); } }); }); // --------------------------------------------------------------------------- // Attack 10 — the strong content-addressed implementation index // (verifiedImplementationsByEntryRef, surfaced as // harness.getVerifiedImplementation). A replayed/forged $implRef must MISS, // and a genuine one returns the REAL recorded function — never attacker data. // --------------------------------------------------------------------------- describe("attack 10: $implRef replay against the strong implementation index", () => { it("a forged identity, or the genuine identity with an unregistered symbol, misses; the genuine pair returns the real fn", async () => { const pattern = await setup(); const mod = handlerModules(pattern)[0]; const fn = mod.implementation as HarnessedFunction; const prov = getVerifiedProvenance(fn)!; const harness = runtime!.harness as unknown as { getVerifiedImplementation?: ( identity: string, symbol: string, ) => unknown; }; // Never-evaluated identity → miss. expect( harness.getVerifiedImplementation?.("forged-identity", prov.symbol!), ).toBeUndefined(); // Genuine identity, symbol that was never registered under it → miss. expect( harness.getVerifiedImplementation?.( prov.identity, "unregistered-symbol", ), ).toBeUndefined(); // The genuine pair returns the EXACT recorded function (a trust-gated // artifact's implementation), never attacker-controlled data. const resolved = harness.getVerifiedImplementation?.( prov.identity, prov.symbol!, ); expect(resolved).toBe(fn); // And that resolved fn is itself verified (its provenance is intact). expect(getVerifiedProvenance(resolved)).toBeDefined(); }); }); // --------------------------------------------------------------------------- // Attack 11 — host pseudo-module separation (identity E5). Host-trusted // functions register into the SAME content-addressed implementation index // as verified modules (under minted `host:` identities), so the residual // invariant is: that registration makes them EXECUTABLE by `$implRef`, but // never VERIFIED — no provenance is recorded, and the CFC policy-facing // resolution fails closed on its absence. // --------------------------------------------------------------------------- describe("attack 11: host pseudo-module registration grants execution, never verification", () => { it("a host entry resolves by { identity, symbol } but carries no provenance", () => { const reg = new ExecutableRegistry(); const fn = (() => {}) as unknown as HarnessedFunction; reg.trustHostValue({ implementation: fn }, { reason: "adversarial" }); const resolved = reg.getVerifiedImplementation("host:0", "fn0"); expect(resolved).toBe(fn); expect(getVerifiedProvenance(resolved as never)).toBeUndefined(); }); it("a forged $implRef naming a host identity cannot manufacture verification", () => { const reg = new ExecutableRegistry(); const fn = (() => {}) as unknown as HarnessedFunction; reg.trustHostValue({ implementation: fn }, { reason: "adversarial" }); // The attacker writes `$implRef: { identity: "host:0", symbol: "fn0" }` // into stored data: resolution executes the host fn, but its CFC // identity stays undefined — `writeAuthorizedBy` sees `unsupported`. const resolved = reg.getVerifiedImplementation("host:0", "fn0"); const identity = resolvePolicyFacingImplementationIdentity( { type: "javascript" } as unknown as Module, { implementation: resolved }, ); expect(identity).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // Attack 12 — host-artifact escalation. A host-trusted callable EXECUTES but // yields NO `kind:"verified"` CFC identity, by two independent defenses: the // `unsafe-host:` debugName short-circuit AND the absence of provenance. A // genuine canonical `fn.src` on a host fn does NOT manufacture verification. // --------------------------------------------------------------------------- describe("attack 12: host artifacts execute but never resolve verified", () => { it("a host fn with an EMPTY debugName falls into the provenance arm and resolves undefined (src alone is not proof)", () => { const hostFn = Object.assign(() => 42, { src: "cf:module/SOME_MODULE/main.tsx:1:1", }); const identity = resolvePolicyFacingImplementationIdentity( { type: "javascript", debugName: "" } as unknown as Module, { implementation: hostFn }, ); // No provenance WeakMap entry → undefined, NOT verified — the canonical // src is an own-property the resolver never trusts on its own. expect(identity).toBeUndefined(); }); it("a host fn given a forged BUILTIN debugName resolves builtin (not verified) and so cannot satisfy a verified-binding claim", async () => { const hostFn = Object.assign(() => 42, { src: "cf:module/SOME_MODULE/main.tsx:1:1", }); const identity = resolvePolicyFacingImplementationIdentity( { type: "javascript", debugName: "forgedBuiltin" } as unknown as Module, { implementation: hostFn }, ); expect(identity?.kind).toBe("builtin"); // A builtin identity is rejected by a verified-BINDING writeAuthorizedBy // claim (it demands `identity.kind === "verified"`), so the forged-builtin // dodge buys no ownership. const { digest, result } = await driveE2Claim( { __ctWriterIdentityOf: { moduleIdentity: "owner-module", file: "/owner.tsx", path: ["ownerHandler"], }, }, { kind: "builtin", builtinId: "forgedBuiltin" }, "attack12-builtin-vs-binding", ); expect(digest).toBe(""); expect(result.error).toBeDefined(); }); }); });