fix(javascript)!: bind runtime identities to capture evidence (#10063)

fix(javascript)!: bind runtime graph identities to capture Evidence
This commit is contained in:
morluto authored and GitHub committed 2026-11-11 18:18:22 +08:00
1 parent b4d501e714
commit 3eb502ae5d
7 files changed
+258 -4

No files matched your search

@@ -446,6 +446,7 @@ export const applicationNodeIdentitySchema = z.discriminatedUnion("strategy", [
z.strictObject({
strategy: z.literal("runtime-instance"),
stability: z.literal("capture-only"),
capture_evidence_id: prefixedDigestSchema("ev"),
capture_sha256: digestSchema,
runtime_key: boundedTextSchema,
}),
@@ -111,7 +111,7 @@ export const runtimeEvidence = (
extractor: extractor("observe-passive-cdp"),
coverage: completeCoverage,
limitations: ["Only passive CDP metadata was observed."],
evidence_ids: [],
evidence_ids: [`ev_${"c".repeat(64)}`],
});
type FixtureNode = ReturnType<typeof createJavaScriptApplicationNode>;
@@ -271,6 +271,7 @@ const buildNativeRuntimeNodes = (): FixtureNode[] => [
{
strategy: "runtime-instance",
stability: "capture-only",
capture_evidence_id: `ev_${"c".repeat(64)}`,
capture_sha256: APPLICATION_GRAPH_DIGESTS.capture,
runtime_key: "script-preload",
},
@@ -411,6 +411,7 @@ describe("JavaScript Application Graph: identity and evidence", () => {
identity: {
strategy: "runtime-instance",
stability: "capture-only",
capture_evidence_id: `ev_${"c".repeat(64)}`,
capture_sha256: "6".repeat(64),
runtime_key: "script-preload",
},
@@ -811,6 +812,50 @@ describe("JavaScript Application Graph: duplicate and orphan rejection", () => {
});
});
it("rejects runtime identities without an explicit capture Evidence identifier", () => {
expect(() =>
createJavaScriptApplicationNode({
kind: "runtime-script-instance",
identity: {
strategy: "runtime-instance",
stability: "capture-only",
capture_sha256: APPLICATION_GRAPH_DIGESTS.capture,
runtime_key: "script-preload",
},
observations: [
{
label: null,
properties: {},
evidence: runtimeEvidence("script-preload"),
},
],
}),
).toThrow(/capture_evidence_id/u);
});
it("requires runtime identities to retain their capture Evidence provenance", () => {
const runtimeWithUnrelatedEvidence = createJavaScriptApplicationNode({
kind: "runtime-script-instance",
identity: {
strategy: "runtime-instance",
stability: "capture-only",
capture_evidence_id: `ev_${"d".repeat(64)}`,
capture_sha256: APPLICATION_GRAPH_DIGESTS.capture,
runtime_key: "script-preload",
},
observations: [
{
label: null,
properties: {},
evidence: runtimeEvidence("script-preload"),
},
],
});
expect(() => graphForNode(runtimeWithUnrelatedEvidence)).toThrow(
/identity is not supported/u,
);
});
describe("JavaScript Application Graph: identity strategy requirements", () => {
it("requires every identity strategy to have compatible supporting evidence", () => {
const mismatchedPath = createJavaScriptApplicationNode({
@@ -841,6 +886,7 @@ describe("JavaScript Application Graph: identity strategy requirements", () => {
identity: {
strategy: "runtime-instance",
stability: "capture-only",
capture_evidence_id: `ev_${"c".repeat(64)}`,
capture_sha256: APPLICATION_GRAPH_DIGESTS.capture,
runtime_key: "script-preload",
},
@@ -245,6 +245,7 @@ const observationMatchesIdentity = (
return false;
const location = evidence.location.value;
return (
evidence.evidence_ids.includes(identity.capture_evidence_id) &&
location.capture_sha256 === identity.capture_sha256 &&
[location.target_key, location.frame_key, location.script_key].includes(
identity.runtime_key,
@@ -137,7 +137,9 @@ const graphCoverageFacts = (
inspection.completeness.conditions.includes("truncated"),
);
const captureHasUnknownOmissions = input.captures.some(
({ inspection }) => inspection.completeness.unavailable_sections.length > 0,
({ inspection }) =>
inspection.completeness.attach_limited_sections.length > 0 ||
inspection.completeness.unavailable_sections.length > 0,
);
const inputTruncated =
input.layers.some(({ graph }) => graph.coverage.truncated) ||
@@ -102,11 +102,11 @@ const targetEntity = (
label: target.title,
properties: {
runtime_type: target.type,
...(location.kind === "unresolved"
...("unresolved_location" in target
? {
reported_url: location.value,
location_kind: "unresolved",
location_reason: "unverifiable-file-location",
location_reason: target.unresolved_location.reason,
}
: { location: location.value }),
attached: target.attached,
@@ -276,6 +276,7 @@ const runtimeEntity = (
identity: {
strategy: "runtime-instance",
stability: "capture-only",
capture_evidence_id: capture.evidence.evidence_id,
capture_sha256: capture.captureSha256,
runtime_key: input.runtimeKey,
},
@@ -420,6 +421,9 @@ const runtimeSectionComplete = (
if (section === "target") return true;
if (section === "scripts") return capture.scriptsCompleteWithinScope;
return (
!capture.inspection.completeness.attach_limited_sections.includes(
section,
) &&
!capture.inspection.completeness.truncated_sections.includes(section) &&
!capture.inspection.completeness.unavailable_sections.includes(section)
);