Skip to content

Audit fixes: correctness highs (topology cache-staleness + reporting scope/org leaks) - #25

Open
Krishcalin wants to merge 2 commits into
mainfrom
audit-correctness-highs
Open

Krishcalin wants to merge 2 commits into
mainfrom
audit-correctness-highs

Conversation

@Krishcalin

Copy link
Copy Markdown
Owner

Audit fixes — correctness highs: cache-staleness + reporting scope leaks

From the 2026-09-30 adversarial audit (docs/audit/2026-09-30-adversarial-audit.md). Base main; independent of #22/#23/#24.

Topology cache serves a stale graph (de49d17, HIGH)

The graph cache fingerprint keyed its snapshot axis on max(Snapshot.created_at). A re-collection whose config_hash matches updates the existing snapshot row in place — refreshing its NCM/version and moving updated_at, without inserting a row or touching created_at. So the fingerprint didn't move and the cache served the pre-refresh graph: a device that reconverged (or was re-parsed) kept answering path/segmentation queries from stale routes — a false unreachable, or a stale allowed. Fixed by keying on max(Snapshot.updated_at), which moves on both insert and in-place refresh. + regression test.

Reporting scope/org leaks (c9543c8, HIGH ×2 + LOW)

Reports are frozen, hash-signed, downloadable artefacts, so a scope leak keeps disclosing after the scope is corrected.

  • Executive summary (HIGH): devices_total was a global count(Device) — no scope, no org — while findings were scoped, so a caller entitled to 10 devices saw the whole estate's count and a fictional devices_without_findings. Now scoped + org-filtered.
  • Exceptions register (HIGH): queried every FindingException with no filter, handing a group-scoped auditor every accepted risk in the DB (device, justification, approver). Now org-filtered, and for a scoped caller limited to exceptions on a visible device or a group in scope.
  • _require_group (LOW): fetched a group with no org filter, echoing another org's group name via the error path. Now org-filtered.
  • TestReportsRespectCallerScope.

Deferred

The trend report's cross-scope comparison (MEDIUM) — a related but distinct fix — goes in the mediums batch.

Tests

Green across the touched suites: topology-cache (12), reports (73).

🤖 Generated with Claude Code

Krishcalin and others added 2 commits September 30, 2026 07:33
…HIGH)

The graph cache fingerprint keyed the snapshot axis on max(Snapshot.created_at), but a
re-collection whose config_hash matches updates the existing snapshot row in place —
refreshing its NCM and version and moving updated_at, without inserting a row or touching
created_at. So neither the snapshot count nor the created_at max moved, the fingerprint
was unchanged, and the cache served the pre-refresh graph: a device that reconverged (or
was re-parsed by a newer parser) kept answering path/segmentation queries from stale
routes — a false unreachable, or a stale allowed. Key it on max(Snapshot.updated_at),
which moves on both an insert and an in-place refresh.

Adds test_an_in_place_snapshot_refresh_invalidates_it. 12 topology-cache tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three reporting paths ignored the caller's object-level scope and/or the org, disclosing
out-of-scope data — worse than a live view because a report is a frozen, hash-signed,
downloadable artefact that keeps disclosing after the scope is corrected.

- Executive summary (HIGH): `devices_total` was `count(Device)` with no scope and no org
  filter, while the finding counts were scoped — so a caller entitled to 10 devices saw a
  total of every device in every org, and `devices_without_findings` mixed a global total
  with a scoped numerator (500 - 2 = 498). Now scoped and org-filtered like the findings.
- Exceptions register (HIGH): queried every FindingException with no filter at all,
  handing a group-scoped auditor every accepted risk in the database — device, free-text
  justification and approver. Now filtered to org, and for a scoped caller to exceptions
  on a visible device or a device group in scope.
- _require_group (LOW): fetched a DeviceGroup with no org filter, echoing another org's
  group name back through the "contains no devices you can see" error. Now org-filtered.

Adds TestReportsRespectCallerScope: a group-scoped exec summary counts only in-scope
devices, and the register lists only in-scope exceptions (no out-of-scope justification
leaks). 73 report tests pass.

The trend report's cross-scope comparison (MEDIUM) is a related but distinct fix, deferred
to the mediums batch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant