fix(snapshot): decline ambiguous exact evidence replay (#7037)

This commit is contained in:
Wenyu Chiou authored and GitHub committed 2026-10-25 06:35:27 +08:00
1 parent 238914974d
commit 07eb044f77
3 files changed
+169 -14

No files matched your search

+45
View File
@@ -422,6 +422,51 @@ describe("analysis snapshot canonical ordering and scope", () => {
});
});
it("does not select arbitrary provenance when multiple authenticated records match one replay entry", () => {
const { current, snapshot } = snapshotWithHistoricalEvidence();
const source = snapshot.evidence_bundle.records.find(
(record) => record.operation === "unprofiled_query",
)!;
const alternative = createEvidence(
ANALYSIS_SNAPSHOT_TARGET,
ANALYSIS_SNAPSHOT_PROVIDER,
{
operation: current.operation,
parameters: current.parameters,
result: current.normalized_result,
analysisProfile: ANALYSIS_SNAPSHOT_PROFILE,
evidenceLinks: [source.evidence_id],
},
);
expect(alternative.evidence_id).not.toBe(current.evidence_id);
const ambiguous = parseAnalysisSnapshot({
...snapshot,
evidence_bundle: createEvidenceBundle([
...snapshot.evidence_bundle.records,
alternative,
]),
});
expect(ambiguous.entries).toEqual(snapshot.entries);
const reversedInsertion = parseAnalysisSnapshot({
...ambiguous,
evidence_bundle: createEvidenceBundle(
[...ambiguous.evidence_bundle.records].reverse(),
),
});
expect(reversedInsertion).toEqual(ambiguous);
for (const candidate of [ambiguous, reversedInsertion])
expect(
snapshotEvidenceForQuery(candidate, {
target: ANALYSIS_SNAPSHOT_TARGET,
bindingProfile: ANALYSIS_SNAPSHOT_PROFILE,
operation: current.operation,
parameters: current.parameters,
provider: ANALYSIS_SNAPSHOT_PROVIDER,
evidenceProfile: ANALYSIS_SNAPSHOT_PROFILE,
}),
).toBeUndefined();
});
describe("analysis snapshot Evidence binding", () => {
it("finds only Evidence committed to the exact binding and profile", () => {
const { current, snapshot } = snapshotWithHistoricalEvidence();
+25 -14
View File
@@ -502,20 +502,31 @@ export const snapshotEvidenceForQuery = (
(candidate) => candidate.query_id === queryId,
);
if (entry === undefined && workflowEntry === undefined) return undefined;
const evidence = snapshot.evidence_bundle.records.find(
(record) =>
record.subject?.digest.sha256 === target.sha256 &&
record.operation === operation &&
record.provider.id === provider.id &&
record.provider.name === provider.name &&
record.provider.version === provider.version &&
record.analysis_profile !== null &&
analysisProfilesEqual(record.analysis_profile, evidenceProfile) &&
canonicalJson(record.parameters) === encodedParameters &&
((entry !== undefined && evidenceMatchesEntry(record, entry, snapshot)) ||
(workflowEntry !== undefined &&
evidenceMatchesWorkflowEntry(record, workflowEntry, snapshot))),
);
let evidence: Evidence | undefined;
for (const record of snapshot.evidence_bundle.records) {
if (
!(
record.subject?.digest.sha256 === target.sha256 &&
record.operation === operation &&
record.provider.id === provider.id &&
record.provider.name === provider.name &&
record.provider.version === provider.version &&
record.analysis_profile !== null &&
analysisProfilesEqual(record.analysis_profile, evidenceProfile) &&
canonicalJson(record.parameters) === encodedParameters &&
((entry !== undefined &&
evidenceMatchesEntry(record, entry, snapshot)) ||
(workflowEntry !== undefined &&
evidenceMatchesWorkflowEntry(record, workflowEntry, snapshot)))
)
)
continue;
// Entries bind the result, not every part of the Evidence commitment
// (for example, upstream links). Do not choose provenance by ID sort order.
if (evidence !== undefined && evidence.evidence_id !== record.evidence_id)
return undefined;
evidence = record;
}
return evidence === undefined
? undefined
: rebindEvidenceToTargetPath(evidence, target);