Summary
The canonical TypeScript contract loader accepts a finding location whose endLine is less than its startLine, even though the bundled Python producer rejects that same location before sealing.
This creates a producer/consumer validation mismatch: an externally constructed, correctly resealed completed-scan bundle can load through loadContract() with a line range that Codex Security itself would never finalize.
Reproduction / evidence
Current upstream main at 37bf87a692fc72d41f7312cc48808d699d204fba validates ordinary finding locations in two different ways.
The Python finalizer uses _validate_location() and requires:
end = location.get("endLine", start)
...
if not isinstance(end, int) or end < start:
raise ContractError(...)
The shared JSON schema only constrains startLine and endLine independently to positive integers. The TypeScript validateCanonicalContract() then validates each location path but does not compare the two line numbers.
A deterministic sealed-contract reproduction is:
- copy the bundled
examples/completed-scan fixture;
- change the first finding location to
startLine: 41, endLine: 40;
- recompute the sealed
findings.json SHA-256 in scan-manifest.json;
- call
loadContract().
Current behavior: the bundle loads successfully.
Expected behavior: the SDK loader rejects the same reversed line range the producer rejects.
Root cause
The relational endLine >= startLine invariant lives in Python finalization but was not mirrored in the TypeScript canonical-validation pass. JSON Schema Draft 2020-12 does not express this sibling-value comparison in the existing schema without a nonstandard extension.
Suggested fix
During validateCanonicalContract(), reject any ordinary finding location whose defined endLine is less than startLine.
Add a focused regression that mutates and reseals the real bundled completed-scan fixture, verifies the reversed range is rejected, and retains a valid multi-line range control.
Impact
This is contract-integrity correctness. Invalid ranges can reach SDK consumers and downstream source-link/reporting logic even though the canonical producer rejects them, so producer and consumer disagree about what constitutes a valid sealed finding.
Summary
The canonical TypeScript contract loader accepts a finding location whose
endLineis less than itsstartLine, even though the bundled Python producer rejects that same location before sealing.This creates a producer/consumer validation mismatch: an externally constructed, correctly resealed completed-scan bundle can load through
loadContract()with a line range that Codex Security itself would never finalize.Reproduction / evidence
Current upstream
mainat37bf87a692fc72d41f7312cc48808d699d204fbavalidates ordinary finding locations in two different ways.The Python finalizer uses
_validate_location()and requires:The shared JSON schema only constrains
startLineandendLineindependently to positive integers. The TypeScriptvalidateCanonicalContract()then validates each location path but does not compare the two line numbers.A deterministic sealed-contract reproduction is:
examples/completed-scanfixture;startLine: 41, endLine: 40;findings.jsonSHA-256 inscan-manifest.json;loadContract().Current behavior: the bundle loads successfully.
Expected behavior: the SDK loader rejects the same reversed line range the producer rejects.
Root cause
The relational
endLine >= startLineinvariant lives in Python finalization but was not mirrored in the TypeScript canonical-validation pass. JSON Schema Draft 2020-12 does not express this sibling-value comparison in the existing schema without a nonstandard extension.Suggested fix
During
validateCanonicalContract(), reject any ordinary finding location whose definedendLineis less thanstartLine.Add a focused regression that mutates and reseals the real bundled completed-scan fixture, verifies the reversed range is rejected, and retains a valid multi-line range control.
Impact
This is contract-integrity correctness. Invalid ranges can reach SDK consumers and downstream source-link/reporting logic even though the canonical producer rejects them, so producer and consumer disagree about what constitutes a valid sealed finding.