Skip to content

fix(recon): close silent false-negatives + ledger completeness gaps (11 fixes)#1

Merged
raccioly merged 1 commit into
mainfrom
claude/determined-wiles-04b08f
Jun 10, 2026
Merged

fix(recon): close silent false-negatives + ledger completeness gaps (11 fixes)#1
raccioly merged 1 commit into
mainfrom
claude/determined-wiles-04b08f

Conversation

@raccioly

Copy link
Copy Markdown
Owner

Summary

Audit of the recon engine surfaced one critical silent false-negative plus a set of completeness/robustness gaps. All fixes are test- or run-verified — 80 → 88 tests, byte-compile clean, full pipeline + reproductions verified.

🔴 Critical

  • SKIP_DIRS now matched relative to the scan root (new base.path_in_skip_dir). routes._in_skip_dir / scanners._in_skip_dir split the absolute noir/scanner path and checked every segment, so a repo living under a skip-named ancestor (.claude/worktrees/<id>, vendor/, target/, build/, out/, ~/.cache, security/) had its ancestor segment match and every route + finding silently dropped — the tool reported the app as empty / "nothing wrong". Recurrence of bug-005 in two post-filters the original fix missed. Proven: identical fixture → 2 routes at a clean path, 0 under a target/ ancestor; now 2 at both. Fails open when a path can't be made relative (a silent drop is the dangerous direction for a security tool).

🟠 High — completeness on large / scan-heavy repos

  • Findings ledger consumes the full ranked static set (all), not the briefing's top-15 slice → no HIGH/CRITICAL CVE/secret dropped at rank #16+ from the ledger + calibration.
  • 12k-file walk truncation surfaced (files_truncated in FACTS + CLI warning + briefing banner) instead of silently analyzing an arbitrary subset.
  • endpoint_guards cap 400 → 5000 with disclosure (endpoint_guards_truncated) so the missing-auth ledger isn't silently capped on big monorepos.

🟡 Medium

  • Unverified inbound webhooks now enter the ledger (new webhook-forgery class, CWE-345) — previously surfaced only in the briefing, never ranked/calibrated.
  • integrations.SIG_VERIFY no longer matches the bare word signature (a comment like "no signature verification" suppressed the finding). Found during validation.
  • Dynamic probe caps (50/60/80) report endpoints_over_cap in their summaries.

🟢 Low / robustness / polish

  • cross_tenant_bola coerces str(tenant) → no crash on numeric tenant ids.
  • noir-present-but-empty falls back to the regex pass (no silent route blind spot).
  • findings._sql counts sql-orm / prisma(sql) labels → SQL-ORM+Mongo apps not mis-down-ranked.
  • Dockerfile pins the Trivy installer to a tagged ref + version (drops main | sh).
  • __init__.py docstring lists the real 7 run artifacts.

Tests

+8 regressions: skip-dir relative-to-root, normalize keeps findings under a skip-named ancestor, full static set into ledger, webhook→ledger, SIG_VERIFY tightening, numeric tenant id, SQL-ORM datastore vocab. 88 tests green.

⚠️ Caveat

The Dockerfile Trivy pin follows the documented installer interface but was not docker build-verified in this environment — smoke-test before relying on it.

🤖 Generated with Claude Code

…11 fixes)

Audit of the recon engine surfaced one critical silent false-negative plus a set of
completeness/robustness gaps. All fixes are test- or run-verified (80 -> 88 tests).

CRITICAL
- SKIP_DIRS now matched RELATIVE to the scan root (new base.path_in_skip_dir).
  routes._in_skip_dir / scanners._in_skip_dir split the ABSOLUTE noir/scanner path and
  checked every segment, so a repo living under a skip-named ANCESTOR
  (.claude/worktrees/<id>, vendor/, target/, build/, out/, ~/.cache, security/) had its
  ancestor segment match and EVERY route + finding silently dropped -> the tool reported
  the app as empty / "nothing wrong". Recurrence of bug-005 in two post-filters the
  original fix missed. Proven: identical fixture -> 2 routes at a clean path, 0 under a
  `target/` ancestor; now 2 at both. Fails OPEN when a path can't be made relative.

HIGH (completeness on large / scan-heavy repos)
- Findings ledger consumes the FULL ranked static set (`all`), not the briefing's top-15
  slice -> no HIGH/CRITICAL CVE/secret dropped at rank #16+ from ledger + calibration.
- 12k-file walk truncation surfaced (files_truncated in FACTS + CLI warning + briefing
  banner) instead of silently analyzing an arbitrary subset.
- endpoint_guards cap 400 -> 5000 WITH disclosure (endpoint_guards_truncated) so the
  missing-auth ledger isn't silently capped on big monorepos.

MEDIUM
- Unverified inbound webhooks now enter the ledger (new webhook-forgery class, CWE-345);
  previously surfaced only in the briefing, never ranked/calibrated.
- integrations.SIG_VERIFY no longer matches the bare word `signature` (a comment like
  "no signature verification" suppressed the finding); keeps crypto/header/verb-prefixed
  patterns. Found during validation.
- Dynamic probe caps (50/60/80) now report endpoints_over_cap in their summaries.

LOW / robustness / polish
- cross_tenant_bola coerces str(tenant) -> no crash on numeric tenant ids.
- noir-present-but-empty now falls back to the regex pass (no silent route blind spot).
- findings._sql counts sql-orm / prisma(sql) labels -> SQL-ORM+Mongo apps not mis-down-ranked.
- Dockerfile pins the Trivy installer to a tagged ref + version (drops mutable-main | sh).
- __init__.py docstring lists the real 7 run artifacts.

Tests: +8 regressions (skip-dir-relative-to-root, normalize keeps findings under a
skip-named ancestor, full static set into ledger, webhook->ledger, SIG_VERIFY tightening,
numeric tenant id, SQL-ORM datastore vocab). 88 tests green; byte-compile + end-to-end
pipeline verified.

NOTE: the Dockerfile Trivy pin follows the documented installer interface but was not
docker-build-verified in this environment — smoke-test with `docker build` before relying on it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@raccioly raccioly merged commit 7d0f242 into main Jun 10, 2026
3 checks passed
raccioly added a commit that referenced this pull request Jun 10, 2026
…k CWE mislabel (#2)

Follow-up to PR #1 (audit items deliberately deferred from the first batch).

1. dynamic.write_auth_enforcement treated HTTP 500 as "no-auth-gate (reached
   handler/validation)". A 500 is ambiguous — it can be the AUTH layer itself
   throwing, not the handler running unauthenticated — so it must NOT escalate to a
   HIGH missing-auth finding, and (worse) it was being recorded by
   calibration.samples_from_dynamic as a CONFIRMED-real missing-auth sample, poisoning
   the self-improving oracle. 500 now falls to the inconclusive `http-500` verdict,
   matching the forged-token engine which already excludes 500 from "reached handler".

2. Attack-surface sinks cited the wrong CWE. surface.py emits the key `sql-injection`
   but STANDARDS keys it `sqli`, so SQLi sinks fell back to attack_class `sast`
   (CWE-710 generic) instead of `sqli` (CWE-89). Added a `_SINK_ATTACK` alias
   (sql-injection -> sqli) and STANDARDS + REMEDIATION entries for nosql-injection
   (CWE-943), redos (CWE-1333), and eval-injection (CWE-95) so every sink class cites
   its specific CWE with a concrete fix.

Tests: 500-inconclusive (+ oracle records nothing) with a 400-still-no-auth-gate
regression guard; sink classes map to their specific CWE + a non-default remediation.
88 -> 91 tests, all green; end-to-end pipeline verified.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
raccioly added a commit that referenced this pull request Jun 10, 2026
…tection classes

The retest on the same app both confirmed 0.3.0's catches and exposed where it was
wrong or incomplete. This corrects the former and adds the latter. 11 -> 15 extractors.

Two false positives 0.3.0 shipped (corrected — the same scrutiny applied inward):
  - graphql.py: AppSync introspection IS disablable engine-level
    (`introspectionConfig: IntrospectionConfig.DISABLED`) — recognize it and flag ONLY
    when not set, instead of always crying wolf with "can't be disabled" advice.
  - iac_ci.py / client_integrity.py: API_KEY default is anonymous/over-permissive auth
    (missing-auth), NOT CSWSH. CSWSH needs ambient-cookie WS auth — now a real client-side
    determinant (withCredentials on a WebSocket), so the tool can ANSWER the flag, not guess.
    Also: a WAF byte-match on an app-layer token (`__schema`, SQL) is flagged as a bypassable
    band-aid, never a fix.

Four new classes (each with the retest's static signal + a probe, vuln-fires/fix-clean tests):
  - surface.py: redirect-SSRF — an outbound client that follows redirects (axios/requests do
    by default) with no per-hop guard re-validates only hop 0 (#1's actual residual).
  - upload_security.py (new): unrestricted upload (deny-list-only, stored-name-from-filename,
    trust-client-MIME, accept-SVG) + serve-side stored XSS (no `nosniff`) (#2b).
  - policy_consistency.py: password REUSE/history — hashing a new password with no comparison
    to current/previous (the control #6 was actually about, distinct from complexity).
  - pii_exposure.py (new): unmasked PII at the output boundary — `res.json(rawEntity)` + a
    masking control defined but with ZERO live call sites; verify by VALUE SHAPE not field
    name (catches indirect carriers like a phone embedded in a composed id) (#8).

Plus: findings ledger gains the new attack classes (CWE/ASVS/OWASP-API + remediation), 3 new
probe drafts (upload matrix + fetch-back, value-shape PII diff, reuse on every set-password path),
briefing/methodology surface it all, and the README §11 retest meta-lessons ("a WAF is never the
remediation"; "read the finding precisely"; "assert by value shape"; "validate the whole chain").

Verification: 92 tests (12 new, incl. FP-gone regressions) on py3.12+3.13; wheel builds + installs
+ smoke-runs (0.4.0, calibration + rules ship); self-scan dogfoods clean (1 Dockerfile LOW, 30
self-referential FPs suppressed); real CLI run confirms a fixed AppSync produces ZERO introspection
findings and all 4 new classes reach the ledger.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
raccioly added a commit that referenced this pull request Jun 10, 2026
…ative) that 0.4.0 missed

0.4.0 was tagged from a STALE local main and published to PyPI WITHOUT the two fixes already
merged to origin/main (#1, #2) — most importantly #1's CRITICAL fix where a repo living under a
skip-named ANCESTOR dir had every route + finding silently dropped (the tool reported a vulnerable
app as clean). This rebases the 0.4.0 retest work onto #1 + #2 and ships the complete set to PyPI.

  - #1: skip-dir matched relative to the scan root (no silent empty-scan), full ranked static set
        into the ledger, walk-truncation disclosure, webhook-forgery -> ledger; +8 regressions.
  - #2: HTTP 500 no longer escalated to missing-auth (nor recorded as a confirmed oracle sample);
        sink classes cite their specific CWE (sqli / nosql / redos / eval), not generic sast.
  - 0.4.0 retest work (15 extractors, the two FP corrections, four new classes) — carried forward.
  - .websec-ignore: skip the maintainer's gitignored base-research/ on self-scan.

103 tests green on the integrated tree; wheel builds + installs + smoke-runs (reports 0.4.1,
calibration + bundled rules ship); self-scan dogfoods clean (1 Dockerfile LOW, 36 suppressed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@raccioly raccioly deleted the claude/determined-wiles-04b08f branch June 10, 2026 21:06
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