Skip to content

fix(security): clear the scheduled fuzz OOM and the 3 Semgrep SAST findings at base - #36

Closed
seonghobae wants to merge 3 commits into
mainfrom
claude/contextualwisdomlab-audit-governance-qyxe67
Closed

fix(security): clear the scheduled fuzz OOM and the 3 Semgrep SAST findings at base#36
seonghobae wants to merge 3 commits into
mainfrom
claude/contextualwisdomlab-audit-governance-qyxe67

Conversation

@seonghobae

@seonghobae seonghobae commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Two base-branch check failures on main, both surfaced by the current-HEAD audit and fixed here.

1. Scheduled Atheris fuzz OOM

Red daily since the fuzz workflow landed. Not a per-input defect: no AssertionError/uncaught exception — libFuzzer reports out-of-memory (~2049/2069Mb vs 2048Mb rss_limit) and uploads two oom-* reproducers.

Root cause: execute_query appends to catalog._AUDIT_LOG + evidence._POLICY_DECISION_LOG every call, draft_sql appends a PolicyDecision every call — unbounded module lists. A coverage-guided harness runs ~10⁶ times/process → the lists grow until OOM (measured: 50k iters → _AUDIT_LOG=50000, _POLICY_DECISION_LOG=100000, RSS 257Mb climbing).

Fix: invariants.reset_accumulating_state() (clears the two logs + _SCHEMA_HISTORY, keeps seed _DATA), called each iteration in the two state-mutating harnesses; the read-only harnesses are untouched. Mirrors the isolate_in_memory_app_state test fixture.

Verified: with the reset, 50k iters keep both logs at 0 / RSS flat 36Mb; fuzz_execute_query under -rss_limit_mb=350 runs 747,663 iters in 46s, RSS flat 67Mb, exit 0 (was OOM within seconds). tests/fuzz/test_fuzz_properties.py: 10 passed.

2. Semgrep (multi-language SAST) — 3 Medium+ findings

p/default (fail on WARNING/ERROR) reports 3 pre-existing blocking findings on every PR (Semgrep runs on pull_request, so main never surfaces them). It does not gate auto-merge, but these are real Medium+ findings.

File Rule Resolution
authz.py _load_jwks_from_url dynamic-urllib-use (warning) Real fix — validate the operator JWKS URL scheme to http/https before urlopen (rejects file:// LFI etc.)
observability.py _export_to_sink dynamic-urllib-use (warning) Reviewed-safe # nosemgrepurlopen reached only after scheme is validated to http/https above
graph_store.py (AGE cypher exec) sqlalchemy-execute-raw-query (error) False positive # nosemgrep — psycopg sql.Composed of sql.Literal(...) + an allowlisted fragment, params bound as %s; no string concat

Suppressions use the exact reported rule ids with one-line justifications; only 2 of 3 are suppressed (both reviewed-safe), the third is a genuine code fix — the gate is not weakened. Added tests/test_authz_jwks.py (4 rejected schemes); it + test_api.py pass. (Local p/default repro is blocked by the proxy — registry unreachable — so suppression rule ids are taken from the failing run's logged findings.)

Note: tests/test_graph_security.py failures reproduce identically on unmodified main (they need sqlalchemy, absent locally, present in CI) — unrelated to this PR.

…harness OOM

The scheduled Atheris fuzz job on the base branch fails: libFuzzer reports
out-of-memory (used ~2049Mb / 2069Mb vs the 2048Mb rss_limit) and uploads two
`oom-*` reproducers. The run itself completes without any AssertionError or
uncaught exception — it is a memory-growth crash, not a per-input defect.

Root cause: `orchestrator.execute_query` appends an `AuditEvent` to
`catalog._AUDIT_LOG` (via `ingest_event`) and a `PolicyDecision` to
`evidence._POLICY_DECISION_LOG` (via `policy.evaluate` ->
`record_policy_decision`) on every call, and `orchestrator.draft_sql` appends a
`PolicyDecision` on every call. These are unbounded module-level lists. A
coverage-guided harness runs the target ~10^6 times in one process, so the
lists grow without bound until the process OOMs. Measured: 50k
execute+draft iterations -> _AUDIT_LOG=50000, _POLICY_DECISION_LOG=100000,
RSS 257Mb and climbing.

Fix (harness hygiene, mirroring the `isolate_in_memory_app_state` test
fixture): add `invariants.reset_accumulating_state()` — clears
`catalog._AUDIT_LOG`, `catalog._SCHEMA_HISTORY`, and
`evidence._POLICY_DECISION_LOG` while leaving seed data (`catalog._DATA`)
intact — and call it each iteration in the two state-mutating harnesses
(`fuzz_draft_sql`, `fuzz_execute_query`). The read-only harnesses
(`fuzz_resolve_terms`, `fuzz_search_catalog`) do not accumulate and are
unchanged.

Verification:
- With the reset, 50k execute+draft iterations keep both logs at 0 and RSS
  flat at 36Mb (was 257Mb and growing).
- `fuzz_execute_query` under `-rss_limit_mb=350` now completes 747,663 runs
  in 46s with RSS flat at 67Mb and exit 0 (previously OOM within seconds).
- `tests/fuzz/test_fuzz_properties.py` (which shares invariants.py): 10 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msz8Dni5zRaqPatNCmZJZ8
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c498255f-7cc4-4cb6-b2f2-160d3c9c8e8b

📥 Commits

Reviewing files that changed from the base of the PR and between 9a7088e and 3dc7b88.

📒 Files selected for processing (8)
  • fuzz-requirements.txt
  • src/sdp/authz.py
  • src/sdp/graph_store.py
  • src/sdp/observability.py
  • tests/fuzz/atheris/fuzz_draft_sql.py
  • tests/fuzz/atheris/fuzz_execute_query.py
  • tests/fuzz/invariants.py
  • tests/test_authz_jwks.py

Comment @coderabbitai help to get the list of available commands.

…ings

The central "Semgrep (multi-language SAST)" gate (p/default, fail on
WARNING/ERROR) reports 3 pre-existing blocking findings on every SDP PR (it
runs on pull_request, so `main` never surfaces them). Resolved all three:

- src/sdp/authz.py:_load_jwks_from_url — REAL hardening. `urlopen(jwks_url)`
  had no scheme check, so a misconfigured SDP_OIDC_JWKS_URL could turn it into
  a local-file read (urllib honours file://). Now the scheme is validated to
  http/https and anything else (file/ftp/data/empty) raises ValueError before
  urlopen. (rule dynamic-urllib-use-detected)

- src/sdp/observability.py:_export_to_sink — reviewed-safe suppression. The
  urlopen is only reached after the scheme is validated to http/https above
  (file:// takes the file-sink branch; every other scheme raises), so the
  file:// concern cannot apply. Scoped `# nosemgrep` with that rationale.

- src/sdp/graph_store.py (AGE cypher exec) — false positive. The statement is a
  psycopg `sql.Composed` built from `sql.Literal(graph_name)` +
  `sql.Literal(query)` and an allowlisted result-columns fragment, with params
  bound as a `%s` argument — no untrusted string concatenation. Scoped
  `# nosemgrep` with that rationale. (rule sqlalchemy-execute-raw-query)

Suppressions use the exact reported rule ids and each carries a one-line
justification; only two of three findings are suppressed and both are
reviewed-safe (the third is a genuine code fix), so the gate is not weakened.

Verification: added tests/test_authz_jwks.py (4 rejected schemes); the new
test plus tests/test_api.py pass. Local semgrep repro of p/default is blocked
by the proxy (registry unreachable), so the suppression match is asserted
against the exact rule ids logged by the failing CI run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msz8Dni5zRaqPatNCmZJZ8
@seonghobae seonghobae changed the title fix(fuzz): reset in-memory audit/decision logs per iteration to stop the scheduled fuzz OOM fix(security): clear the scheduled fuzz OOM and the 3 Semgrep SAST findings at base Jul 30, 2026
atheris 3.0.0 ships cp311/cp312/cp313 manylinux x86_64 wheels but no cp314
wheel, so any wheel-only CPython 3.14 environment (e.g. the central
coverage-evidence trusted image) cannot install atheris==3.0.0. atheris 3.1.0
ships cp312/cp313/cp314 wheels (it dropped cp311 and ships no sdist). This
repo's fuzz image and clusterfuzzlite run on CPython 3.12, where 3.1.0 has a
wheel too, so the bump is safe here and stays installable on newer interpreters.
Listed hashes cover the cp312/cp313/cp314 Linux x86_64 wheels for --require-hashes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msz8Dni5zRaqPatNCmZJZ8

Copy link
Copy Markdown
Contributor Author

Diagnosis for the blocked opencode-review (drive-to-green posture): its coverage-evidence job is failing on the central CPython-3.14 coverage-evidence harness — the same root cause documented on contextual-orchestrator#78 and newsdom-api#467, not this PR's diff.

The cp314 image upgrade breaks coverage-evidence in two ways, both applicable to a repo that pins atheris and targets 3.11/3.12:

  • Image build: the trusted image is built from the base branch (main) locks (pull_request_target trust model); main still pins atheris==3.0.0 (no cp314 wheel), which fails the base-lock preflight before PR execution. This PR's bump to atheris==3.1.0 lands only when main carries it.
  • Test run: the suite is executed under CPython 3.14, where a 3.11/3.12-targeted dependency graph can fail collection (as it does on newsdom-api#467).

Neither is fixable from this PR branch. My per-branch atheris==3.1.0 fix is correct and clears the image-build side once main updates. Central remediation is on #78. (I'll pull the exact failing sub-step and confirm which mechanism once the GitHub log endpoint recovers — it's intermittently 403-ing right now.)


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #34, which contains the same scheduled-fuzz OOM remediation and the same 3 base Semgrep fixes (authz JWKS scheme allow-list, observability/graph_store documented suppressions) plus crash-log reproducer visibility and the coverage test suite. Consolidating on one head frees the OpenCode review-dispatch queue for the surviving PR. The branch is preserved; reopen if #34 stalls.


Generated by Claude Code

@seonghobae seonghobae closed this Aug 3, 2026
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.

2 participants