Skip to content

feat(locations): LocationData.code factory + get_locations() hash symmetry + location docs#15171

Draft
Maffooch wants to merge 5 commits into
devfrom
feature/code-location-data-and-hash-symmetry
Draft

feat(locations): LocationData.code factory + get_locations() hash symmetry + location docs#15171
Maffooch wants to merge 5 commits into
devfrom
feature/code-location-data-and-hash-symmetry

Conversation

@Maffooch

@Maffooch Maffooch commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Shortcut: sc-13523

What & why

Two changes that let static-analysis code coordinates participate in the Locations model, plus documentation for the location-tracking features built on top.

LocationData.code() factory (dojo/tools/locations.py)

Parsers can emit static-analysis code coordinates as first-class locations: identity is file_path (+ line), while volatile context (snippet, end_line, sink/source metadata) is carried separately and omitted when unset, so downstream location subtypes can route it onto the finding reference instead of the location identity.

Finding.get_locations() hash symmetry fix (dojo/finding/models.py)

The saved path filters a finding's location references to URL type before feeding the "endpoints" hash ingredient, but the unsaved path hashed every unsaved location type. A finding whose hash was computed before save could therefore never match its own hash recomputed after save. The unsaved path now applies the same URL-type filter.

Stored-hash impact: none. Audited every producer of unsaved_locations: no current import path places non-URL location objects there at hash-computation time (URL conversions only; dependency location data is carried as LocationData wrappers that the type registry does not convert; code locations are routed separately). The fix therefore changes no hash any production flow computes today — it is purely protective against future location types leaking into the endpoints ingredient.

Parsers emit LocationData.code (the #14395 method, applied to code)

Following the approach #14395 established for dependencies, static parsers now emit their code coordinates as LocationData.code() into unsaved_locations — gated on V3_FEATURE_LOCATIONS, scalars unchanged. 45 parser directories converted (~60 emission sites): SAST, secret scanners, and IaC scanners. Deliberate exclusions: SCA parsers (their file_path is a dependency manifest and they already emit LocationData.dependency), dynamic findings, and non-source paths (container-image filesystem hits, issue-tracker URLs, cloud service names). Mixed parsers emit only for their code-shaped findings. Context the parser already extracts rides along (snippet, end line, source/sink objects, taint source coordinates); fabricated line numbers (e.g. trufflehog's line=0 placeholder) are never propagated, and secret scanners never pass matched secret content as snippets.

Documentation

New pages for Source Code Locations and location drift matching (tracking findings whose location moves between reimports), plus updates to the Locations overview, deduplication tuning, and reimport pages.

Testing

  • unittests/test_location_data.py (new, 7 tests): factory identity/context semantics and pre/post-save hash symmetry, including the mixed-location case.
  • unittests/test_code_location_emission.py (new, 12 tests): nine representative parsers against real fixtures — emission coherence with the finding scalars, snippet context, SARIF per-location behavior, and a gate-off class proving the sweep is inert without V3.
  • Full unittests.tools suite: 1531 tests green with V3 off (the sweep changes nothing behind the gate).
  • Validated for downstream compatibility: a full dependent-plugin test suite was run against this branch and against dev under identical conditions with identical results.

Maffooch and others added 2 commits July 2, 2026 20:05
…metry

Adds a LocationData.code() factory so parsers can emit static-analysis code
coordinates as first-class locations: identity is file_path (+ line), while
volatile context (snippet, end_line, sink/source metadata) is carried
separately and omitted when unset, so downstream location subtypes can route
it onto the finding reference instead of the location identity.

Also fixes an asymmetry in Finding.get_locations(): the saved path filters a
finding's location references to URL type before feeding the "endpoints"
hash ingredient, but the unsaved path hashed every unsaved location type
(dependencies included). A finding whose hash was computed before save could
therefore never match its own hash recomputed after save. The unsaved path
now applies the same URL-type filter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tching

Two new pages plus cross-links from the existing docs:

- asset_modelling/locations/PRO__source_code_locations.md — the Code
  location subtype: file+line identity, scan-managed lifecycle, the
  All Source Code / asset-scoped views, the movement-history trail,
  and the toggle-independent reference status sync.
- triage_findings/finding_deduplication/PRO__location_drift_matching.md —
  the full feature guide: the churn problem, enabling the per-tool
  toggle, the two-stage matching model (stable identity + evidence
  passes per location type), severity re-score tolerance, scan-owned
  field refresh with human-edit protection, scan-pure hash adoption,
  location history, upgrade/enablement guidance for existing data,
  hash-field guidance (all-volatile fallback, location-in-title),
  the dependency dedupe stable-key interaction, the historical churn
  consolidation command, and the safeguards/limits.
- PRO__deduplication_tuning.md — Content Fingerprint hash field (with
  the backfill-before-select ordering) and the Track Findings as
  Locations Change toggle, plus best-practice entries.
- PRO__locations_overview.md — Source Code Locations join the subtype
  list.
- import_data/import_intro/reimport.md — cross-link from the "line
  number shift" troubleshooting note to the drift matching page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Maffooch and others added 3 commits July 7, 2026 14:43
Follows the method PR #14395 established for dependencies: rather than
downstream synthesis from finding scalars alone, static parsers now emit
their code coordinates as LocationData.code() into unsaved_locations,
gated on V3_FEATURE_LOCATIONS, alongside the unchanged scalar fields.
Context the parser already extracts rides along (snippet, end_line,
source/sink objects, taint source file/line) so location subtypes can
route it onto the finding reference instead of the location identity.

Coverage: 45 parser directories converted (~60 emission sites) — SAST,
secret scanners, and IaC scanners. SCA parsers are deliberately skipped
(their file_path is a dependency manifest; they already emit
LocationData.dependency), as are dynamic findings and non-source paths
(container-image filesystem hits, JIRA/Confluence URLs, cloud service
names). Mixed parsers (trivy, checkmarx, aws_inspector2, wizcli, xygeni,
mobsf, deepfence, rusty_hog, snyk_issue_api) emit only for their
code-shaped findings. Multi-location: SARIF explodes results into one
finding per location, each with its own emission; xygeni secrets emits
one location per leaked line. Fabricated line numbers (trufflehog's
line=0 dedup placeholder) are never propagated — real coordinates only.
Secret scanners never pass matched secret content as snippets.

unittests/test_code_location_emission.py exercises nine representative
parsers against real fixtures: emission coherence with the finding
scalars, snippet context, SARIF per-location behavior, and a gate-off
class proving the sweep is inert without V3 (full unittests.tools suite:
1531 tests green with V3 off).

Known follow-ups (out of scope): mend and meterian have no dependency
emission from #14395 either; github_vulnerability's code-scanning branch
parses no location fields at all today.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ilot scalars

CI (the V3-on rest-framework matrix variant) caught two coupled defects the
local V3-off run could not, since the emission only fires under V3:

- LocationData.code filtered unset context with `value not in {"", None}` — a
  SET membership test that hashes `value`, so any unhashable value crashed with
  "cannot use 'list' as a set element". Switched to tuple membership (`in
  ("", None)`), which compares by equality and never hashes — robust for every
  call site, present and future.
- progpilot reports taint-source fields as single-element arrays
  (source_name=["$sql"], source_line=[610]); the sweep passed those lists
  straight into the context kwargs. Now collapsed to scalars via a _first()
  helper, with str()/int guards.

Audited every other converted parser's context kwargs: all pass .text/int/
string scalars (checkmarx pathnode elements, sarif get_snippet, etc.) — progpilot
was the only list-valued source. Verified against the full unittests.tools suite
run with V3_FEATURE_LOCATIONS=True.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the crash-prone set-membership filter with a truthiness dict
comprehension: never hashes the value (crash-robust for any parser input),
and satisfies the repo's ruff rules (no manual loop, no != "" comparison).
Drops None/""/0/empty — all "no data" for the code location's context
fields. Same fix, lint-clean form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

1 similar comment
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant