Skip to content

fix(findings): route every group id through one definition - #2359

Open
Subramaniyajothi6 wants to merge 2 commits into
utksh1:mainfrom
Subramaniyajothi6:fix/unify-finding-group-keys-1834
Open

fix(findings): route every group id through one definition#2359
Subramaniyajothi6 wants to merge 2 commits into
utksh1:mainfrom
Subramaniyajothi6:fix/unify-finding-group-keys-1834

Conversation

@Subramaniyajothi6

Copy link
Copy Markdown
Contributor

Closes #1834

Problem

Group identity was computed three different ways:

Where Key material
generate_finding_key() plugin_id, asset_id, signature, owner_id
normalize_and_correlate_findings() plugin_id, asset_id, signature
build_finding_groups() fallback title, target

The third is the one the issue reports. An uncorrelated finding grouped on title + target alone, so two genuinely distinct issues sharing a title on the same target merged into one — two different open ports both reported as "Open port", for instance.

Worth scoping precisely: that fallback fires only when a finding carries neither finding_group_id nor id, i.e. was never persisted or correlated. build_finding_groups prefers both of those first.

Changes

compute_finding_group_id() is now the single definition of group identity — (plugin, asset, signature) — and all three producers route through it.

The persisted digest is unchanged

normalize_and_correlate_findings keeps passing its already-computed asset_id, so the id it writes is byte-identical. This matters: finding_group_id is persisted under a unique index on (owner_id, finding_group_id) (migration 008), and the upsert keys on that same pair — changing the hash would orphan every existing row.

TestPersistedGroupIdIsStable pins three known digests so a future edit cannot drift them without someone noticing a migration is needed.

owner_id is dropped rather than added to the others

Owner scoping already lives in the storage layer — that same unique index, plus ON CONFLICT (owner_id, finding_group_id) in the upsert. Folding the owner into the digest was therefore redundant, and it meant generate_finding_key could never reproduce an id that was actually stored.

generate_finding_key has no callers anywhere in the repo, so nothing observes the change. Its owner_id parameter is kept for compatibility and documented as unused. Happy to remove the function outright instead if you would prefer — it is dead code either way.

Verification

  • 12 new tests; test_finding_intelligence_groups.py26 passed
  • Finding-intelligence suites — 76 passed
  • Full testing/backend/unit2333 passed, 21 skipped, 0 failed
  • ruff check backend testing/backend — clean

Mutation-checked: reverting the fallback to title + target fails four tests; folding owner_id back into the digest fails the pinned-digest test.

Behaviour, before → after:

two open ports (80, 443), same title/target   1 group  → 2 groups
identical findings                            1 group  → 1 group
same port, two plugins                        1 group  → 2 groups

Note

I was not able to run the integration suite locally on this machine. The change touches build_finding_groups, which routes.py calls in three places, so that CI job is the one worth watching.

Closes utksh1#1834

Group identity was computed three different ways:

  generate_finding_key()              plugin, asset, signature, owner_id
  normalize_and_correlate_findings()  plugin, asset, signature
  build_finding_groups() fallback     title, target

The third is the one the issue reports: an uncorrelated finding grouped
on title+target alone, so two genuinely distinct issues sharing a title
on the same target merged into one — two different open ports both
reported as "Open port", for instance. That path fires only when a
finding carries neither finding_group_id nor id, i.e. was never
persisted or correlated.

compute_finding_group_id() is now the single definition and all three
callers go through it. The correlate path keeps passing its
already-computed asset_id, so the digest it writes is unchanged — this
matters because finding_group_id is persisted under a unique index on
(owner_id, finding_group_id) from migration 008, and changing the hash
would orphan every stored row. TestPersistedGroupIdIsStable pins three
known digests so a future edit cannot drift them without a migration.

owner_id is dropped from the key rather than added to the others. Owner
scoping already lives in the storage layer — that same unique index, and
the ON CONFLICT (owner_id, finding_group_id) upsert — so hashing it in
was redundant and made generate_finding_key unable to reproduce any id
actually stored. generate_finding_key has no callers anywhere in the
repo, so nothing observes the change; its parameter is kept for
compatibility and documented as unused.

Verified by mutation: reverting the fallback to title+target fails four
tests, and folding owner_id back into the digest fails the pinned-digest
test.
@utksh1 utksh1 added area:backend Backend API, database, or service work level:intermediate 35 pts difficulty label for moderate contributor PRs type:bug Bug fix work category bonus label labels Aug 4, 2026

@utksh1 utksh1 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Request changes: removing owner_id from the finding-group digest changes every persisted group key, despite the PR describing the digests as stable. Existing rows and cross-task correlation need a migration/backfill or an explicit compatibility strategy, plus collision tests for old and new data. Do not merge a hash-material change that silently orphanes existing group identities.

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

Labels

area:backend Backend API, database, or service work level:intermediate 35 pts difficulty label for moderate contributor PRs type:bug Bug fix work category bonus label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[#60] Finding grouping logic inconsistent between two functions

2 participants