Summary
Canonical finding locations are required to use safe repository-relative POSIX paths, but codeEvidence[].path is only constrained to a nonempty string.
A sealed finding can therefore claim source-code evidence at an absolute, traversal, Windows-style, or colon-bearing path even though the same path would be rejected in locations[].path.
Reproduction / evidence
Current upstream main at 37bf87a692fc72d41f7312cc48808d699d204fba has two different validation paths:
contract.ts explicitly runs every locations[].path through safeRelativePath();
findings.schema.json defines codeEvidence[].path as only { "type": "string", "minLength": 1 };
- the Python finalizer's
_validate_finding() validates each ordinary location with _validate_location(), but for code evidence it only requires an id and nonempty code before later schema validation.
A deterministic sealed-contract reproduction is:
- copy the bundled completed-scan example;
- add otherwise valid
codeEvidence to a finding with path: "../../outside.ts";
- update the sealed
findings.json digest in scan-manifest.json;
- call
loadContract().
On current main, the traversal path satisfies the findings schema and no canonical code-evidence path check rejects it.
Expected behavior: canonical codeEvidence[].path should obey the same repository-relative safety boundary as source locations and artifact paths.
Root cause
The path-safety rule was implemented for locations in code, while the optional code-evidence path was added to the schema without an equivalent path constraint. Because the shared schema is consumed by both the TypeScript loader and Python finalizer, the omission exists on both validation surfaces.
Suggested fix
Add the canonical safe-path pattern to codeEvidence[].path, rejecting:
- absolute paths;
.. traversal segments;
- backslashes / Windows-style paths;
- colon-bearing path components;
- the standalone
. path;
- NUL characters.
Add a focused sealed-contract regression that reseals the fixture after inserting ../../outside.ts and requires loadContract() to reject it.
Impact
This does not provide arbitrary file reads by itself because codeEvidence.code is already embedded in the finding. It is an integrity issue: a sealed Codex Security finding can currently attribute canonical source evidence to a path outside the repository/path model that the rest of the contract enforces, and downstream report/publication adapters can present that misleading path as evidence.
Summary
Canonical finding locations are required to use safe repository-relative POSIX paths, but
codeEvidence[].pathis only constrained to a nonempty string.A sealed finding can therefore claim source-code evidence at an absolute, traversal, Windows-style, or colon-bearing path even though the same path would be rejected in
locations[].path.Reproduction / evidence
Current upstream
mainat37bf87a692fc72d41f7312cc48808d699d204fbahas two different validation paths:contract.tsexplicitly runs everylocations[].paththroughsafeRelativePath();findings.schema.jsondefinescodeEvidence[].pathas only{ "type": "string", "minLength": 1 };_validate_finding()validates each ordinary location with_validate_location(), but for code evidence it only requires anidand nonemptycodebefore later schema validation.A deterministic sealed-contract reproduction is:
codeEvidenceto a finding withpath: "../../outside.ts";findings.jsondigest inscan-manifest.json;loadContract().On current
main, the traversal path satisfies the findings schema and no canonical code-evidence path check rejects it.Expected behavior: canonical
codeEvidence[].pathshould obey the same repository-relative safety boundary as source locations and artifact paths.Root cause
The path-safety rule was implemented for
locationsin code, while the optional code-evidence path was added to the schema without an equivalent path constraint. Because the shared schema is consumed by both the TypeScript loader and Python finalizer, the omission exists on both validation surfaces.Suggested fix
Add the canonical safe-path pattern to
codeEvidence[].path, rejecting:..traversal segments;.path;Add a focused sealed-contract regression that reseals the fixture after inserting
../../outside.tsand requiresloadContract()to reject it.Impact
This does not provide arbitrary file reads by itself because
codeEvidence.codeis already embedded in the finding. It is an integrity issue: a sealed Codex Security finding can currently attribute canonical source evidence to a path outside the repository/path model that the rest of the contract enforces, and downstream report/publication adapters can present that misleading path as evidence.