Skip to content

fix(security): preserve exact finding identity - #409

Open
Spectorian wants to merge 11 commits into
mainfrom
codex/security-finding-integrity
Open

fix(security): preserve exact finding identity#409
Spectorian wants to merge 11 commits into
mainfrom
codex/security-finding-integrity

Conversation

@Spectorian

@Spectorian Spectorian commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • derive finding identity from complete analyzer matches before retaining bounded display previews
  • select deterministic severity-first representatives while preserving distinct occurrences and source scope
  • keep full match content ephemeral across static, YARA, MCP tool-poisoning, and rug-pull findings
  • bound public context and evidence surfaces, and report partial analysis when YARA fingerprint work reaches its limit

Validation

  • long-prefix collision, exact-duplicate, severity, source-binding, occurrence, and idempotence regressions
  • terminal, JSON, Markdown, SARIF, Python, and MCP serialization checks
  • branch-wide non-integration/non-provider suite
  • Ruff lint, format, targeted mypy, and git diff --check

@rng1995
rng1995 marked this pull request as ready for review August 24, 2026 18:15

@rng1995 rng1995 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes because the new identity path still collapses distinct findings and can discard an already-known YARA detection. All three cases reproduce on this exact head; the focused relevant suites pass, which indicates missing regression coverage.


# --- Data URIs (check first) ---
for m in _DATA_URI_RE.finditer(text):
complete_match = m.group()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Include the data-URI payload in the fingerprint. _DATA_URI_RE ends at the comma, so m.group() is only the data:text/...;base64, prefix. Two URIs with different payloads therefore receive the same fingerprint and final deduplication reports only one. Capture/hash the complete URI token (while keeping the displayed preview bounded) and mark its complete range as covered.

end_lineno: int | None,
msg_override: str | None = None,
) -> None:
complete_match = get_complete_source_segment(lines, lineno, end_lineno)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Fingerprint the AST node, not its entire line range. get_complete_source_segment(lines, lineno, end_lineno) has no column offsets, so exec("first"); exec("second") produces two findings with the same location and complete match; final deduplication drops one. Use the node's start/end columns (for example ast.get_source_segment) or include column-aware evidence in identity.

data,
fingerprint_budget,
)
except _YaraFingerprintLimitError as exc:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Do not discard the current YARA detection when fingerprint hashing reaches its budget. At this point the rule has already matched and preview evidence is available, but this return omits that match entirely. A valid bounded-budget repro returns findings=[] with SIZE_LIMIT. Emit the finding with conservative/incomplete identity, then record the partial-coverage ledger status.

Signed-off-by: Nir Paz <npaz@nvidia.com>
@mohgupta-ship-it

mohgupta-ship-it commented Aug 25, 2026

Copy link
Copy Markdown
Member

Powered by Codex: correction after a five-lens GPT-5.6-sol council on current head 863c31d.

  • Rating: critical fix
  • Disposition: NEEDS_CHANGES
  • Critical gap: yes — a reproduced identity collision changes the published install recommendation.
  • CI/live state: all five hosted checks are green; mergeable is true, but review state is blocked.
  • Council method: independent spec/regression, security/trust-boundary, runtime/architecture, standards/YAGNI, and focused-test lenses; an evidence-only judge then adjudicated reproduced claims.

This supersedes the prior mergeable assessment. New, non-duplicate findings:

  1. Distinct same-category YARA rules matching identical bytes share a fingerprint and dedup 2→1; the harmless reproduction changed 67/HIGH/DO_NOT_INSTALL to 45/MEDIUM/CAUTION.
  2. Same-line AST extraction is approximately quadratic because full-source segmentation runs once per finding outside an interruptible deadline.
  3. Identical same-line regex matches lose occurrence identity because the key has no column/offset.

New anchored review: #409 (review)

Recommended action: do not merge until YARA rule identity is part of the normal fingerprint; address the bounded runtime/occurrence regressions in the same identity path.

@Spectorian

Copy link
Copy Markdown
Collaborator Author

Fixes pushed. please review and confirm.

@mohgupta-ship-it mohgupta-ship-it left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Powered by Codex: five-lens GPT-5.6-sol delta council — REQUEST CHANGES on 863c31d. The prior mergeable assessment is superseded by a reproduced YARA identity collision that changes the published install recommendation; two bounded identity/runtime issues are also anchored below.

digest.update(len(value).to_bytes(8, "big"))
digest.update(value)

update_framed(rule_id.encode("utf-8", errors="surrogatepass"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Powered by Codex: [P1] The normal fingerprint omits match.namespace and match.rule, although the fallback includes both. Two distinct same-category custom rules matching identical harmless bytes therefore received the same fingerprint and deduped 2→1; the reproduced score dropped from 67/HIGH/DO_NOT_INSTALL to 45/MEDIUM/CAUTION. Include namespace and rule name in this domain-separated digest and assert both rule messages and the blocking score survive compaction.

) -> None:
lineno = getattr(ast_node, "lineno", 1)
end_lineno = getattr(ast_node, "end_lineno", None)
complete_match = ast.get_source_segment(python_ast.content, ast_node)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Powered by Codex: [P2] Calling ast.get_source_segment once per finding re-splits/reconstructs source and is measurably quadratic for many calls on one line: 500/1,000/2,000/4,000 calls took 0.028/0.108/0.420/1.682 seconds. The deadline check cannot interrupt this extraction. Precompute line/byte offsets once per ParsedPythonFile, slice spans from that index, and add a structural large-N regression.

identity = (
(
finding.location.file,
finding.location.start_line,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Powered by Codex: [P2] Location identity is line-only here. Two identical P1 matches on the same line are emitted twice by the analyzer but compact to one finding with no distinguishable second occurrence. Preserve a start column or absolute offset in occurrence identity and test two identical same-line matches; no scoring impact is claimed, but the occurrence-preservation contract is currently violated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants