Raxx · internal docs

internal · gated ↑ index

Nightly security scan — 2026-05-06

Scan run start: 2026-05-06T10:05:22Z (UTC) Scan duration: ~3m (CI artifact, exact duration not recorded in workflow metadata) Scanner host: GitHub Actions (workflow run 25428854722) Tools run: bandit (unversioned in artifact), pip-audit, npm audit, gitleaks, trivy Branch scanned: main (HEAD at time of nightly trigger) Operator-facing executive summary: 0 CRITICAL, 1 HIGH (1 new, 0 carryover from 2026-04-25 baseline, 0 resolved), 7414 MEDIUM (deferred), 10 LOW. No live secret found. The nightly workflow was broken 2026-04-26 through 2026-05-05; PR #1274 fixed and re-triggered it. The workflow's issue-filer fired twice on re-trigger, creating 38 duplicate issues (#1275–#1312) for 19 findings — see deduplication table below. 18 of those findings are confirmed false positives; # nosec annotations added in this PR. 1 real finding: pynacl CVE-2025-69277 (CVSS 4.5, Moderate — scanner mislabeled it HIGH).


Summary

Severity Count Status
CRITICAL 0 n/a
HIGH 1 (real) filed as #1309 (corrected body); #1312 is duplicate to close
MEDIUM (scanner-labeled) 7414 deferred — separate triage card recommended
LOW 10 batched
INFO 0

Note on scanner severity inflation: The nightly workflow labels bandit B310 (urlopen scheme-check, bandit native severity MEDIUM), B306 (mktemp, MEDIUM), B608 (SQL f-string, MEDIUM), and B102 (exec, MEDIUM) as HIGH in the summary.md output. The raw bandit.json confirms these are all MEDIUM. The workflow's severity-escalation logic needs review — file a tooling issue if MEDIUM→HIGH inflation recurs next run.

Prior baseline (2026-04-25) HIGH findings status:

Baseline finding Issues filed Status today
cryptography==42.0.8 (4 CVEs) #288 No longer present — cryptography upgraded to 46.0.7 per pip-audit clean scan
pyopenssl==25.1.0 (2 CVEs) #289 No longer present — pyopenssl upgraded to 26.0.0
flask==3.0.3 (CVE-2026-27205) #290 No longer present — flask upgraded to 3.1.3
serialize-javascript@6.0.2 (GHSA-5c6j-r48x-rmvq) #291 Still open — see #320
Stale worktree dep manifests (trivy noise) #292 Presumed noise, not re-checked this run

Resolved since baseline: #288, #289, #290 (cryptography/pyopenssl/flask bumps landed in the 11-day gap).


HIGH findings (real)

H-2026-05-06-1 — pynacl==1.5.0 in console: CVE-2025-69277 (GHSA-mrfv-m5wm-5w6w)

Tool: pip-audit (workflow run 25428854722, pip-audit-requirements.json) Confidence: HIGH (vendor advisory; pip-audit canonical) Bandit native severity: MEDIUM — workflow upcasted to HIGH Actual CVSS: 4.5 (Moderate) per GHSA record Filed as: #1309 (duplicate #1312 needs to be closed)

Finding. console/requirements.txt pins pynacl==1.5.0. GHSA-mrfv-m5wm-5w6w (CVE-2025-69277, CVSS 4.5): libsodium mishandles elliptic curve point validity checks in crypto_core_ed25519_is_valid_point when processing points not in the main cryptographic group. Affects atypical use cases involving custom cryptography or untrusted data to that specific function. Fix: pynacl>=1.6.2.

Evidence. - console/requirements.txt line 30: pynacl==1.5.0 - console/requirements.txt line 26-27: comment confirms pynacl is used by the Heroku rotation handler for GitHub Actions secret encryption - pip-audit output: {"name": "pynacl", "version": "1.5.0", "vulns": [{"id": "GHSA-mrfv-m5wm-5w6w", "fix_versions": ["1.6.2"], "aliases": ["CVE-2025-69277"]}]}

Risk. The console uses pynacl for encrypting GitHub Actions secrets during the Heroku rotation workflow. CVE-2025-69277 affects the crypto_core_ed25519_is_valid_point path specifically. If the rotation handler processes untrusted or attacker-crafted curve points, validation could be bypassed. Practical exploitation requires attacker control of the EC point data fed to that function — in the rotation handler context, that data comes from GitHub's public key API (trusted). Real-world risk is low; advisory severity is Moderate (CVSS 4.5). Per agent spec, we file at advisory severity, not lower.

Remediation. Bump pynacl to >=1.6.2 in console/requirements.txt. No breaking API changes in pynacl 1.5.0 → 1.6.2. Run pip install pynacl>=1.6.2 and verify the rotation handler test suite passes.

Routing: Code fix: feature-developer (console).


FALSE POSITIVE dispositions (18 bandit findings)

All 18 bandit findings were verified against source code. Each is confirmed false positive. # nosec annotations added in this PR.

# Finding File:line Bandit rule FP reason nosec added
FP-01 blacklist (B310) backend_v2/api/feature_flags.py:159 B310 urlopen URL built from STATUS_WORKER_URL env var (operator-configured); scheme not user-injectable Yes
FP-02 hardcoded_sql (B608) backend_v2/api/routes/admin_customers.py:1336 B608 where_sql is assembled from hardcoded condition fragments; user values bound via ? params Yes
FP-03 hardcoded_sql (B608) backend_v2/api/routes/admin_customers.py:1352 B608 select_cols and where_sql are hardcoded strings; user values in ? params only Yes
FP-04 blacklist (B310) backend_v2/api/services/support_customer_map_service.py:167 B310 urlopen URL built from FREESCOUT_BASE_URL env var; user email only in query-param (URL-encoded) Yes
FP-05 blacklist (B306) backend_v2/tests/test_support_s1.py:488 B306 mktemp Test-only; mktemp race not exploitable in CI isolation; SQLite opens the path immediately Yes
FP-06 hardcoded_sql (B608) backend_v2/tests/test_trace_sc1_schema.py:379 B608 Test-only; column names and values in f-string are hardcoded test fixtures, not user input Yes
FP-07 exec_used (B102) backend_v2/tests/unit/test_app_debug_guard.py:66 B102 Intentional test scaffolding; executes repo source in isolated stub_globals to verify FLASK_DEBUG behavior; already annotated # noqa: S102, # nosec B102 added Yes
FP-08 blacklist (B310) console/app/services/billing_alert_config.py:77 B310 urlopen URL from operator-configured base URL + hardcoded path segment Yes
FP-09 blacklist (B310) console/app/services/billing_alert_config.py:158 B310 urlopen URL from operator-configured base URL + hardcoded path segment Yes
FP-10 blacklist (B310) console/app/services/billing_summary.py:122 B310 urlopen URL from operator-configured base URL + hardcoded path segment Yes
FP-11 blacklist (B310) console/app/services/customer_audit.py:131 B310 urlopen URL from operator-configured RAPTOR_INTERNAL_BASE_URL; no user input in scheme Yes
FP-12 blacklist (B310) console/app/services/customer_detail.py:135 B310 urlopen URL from RAPTOR_BASE_URL + hardcoded path argument; no user scheme control Yes
FP-13 blacklist (B310) console/app/services/customer_detail.py:209 B310 urlopen Same function (_raptor_post); URL assembled from operator-configured base URL Yes
FP-14 blacklist (B310) console/app/services/customer_detail.py:365 B310 urlopen Separate revocation POST path; same operator-URL pattern Yes
FP-15 blacklist (B310) console/app/services/customer_lifecycle.py:136 B310 urlopen URL from operator-configured internal base URL Yes
FP-16 blacklist (B310) console/app/services/customer_list.py:131 B310 urlopen URL from operator-configured internal base URL Yes
FP-17 blacklist (B310) console/tests/test_post_deploy_smoke_614.py:177 B310 urlopen Test-only; target_url is a CI fixture pointing at deployed console, not user-supplied Yes
FP-18 blacklist (B310) console/tests/test_post_deploy_smoke_614.py:393 B310 urlopen Test-only; hardcoded fake Slack endpoint URL in test fixture Yes

Duplicate issue cleanup required

The nightly workflow re-triggered twice (broken-then-fixed state of PR #1274), filing each of the 19 findings twice. The following issues need to be closed as duplicates by the operator — the security agent cannot close issues it did not create in the current session:

Duplicate (close this) Canonical (keep this) Finding
#1312 #1309 pynacl GHSA-mrfv-m5wm-5w6w — keep #1309, update its severity label from severity:high to severity:medium
#1277 #1275 bandit blacklist feature_flags.py:159 — FALSE POSITIVE; close both
#1284 #1280 bandit blacklist support_customer_map_service.py:167 — FALSE POSITIVE; close both
#1286 #1281 bandit blacklist test_support_s1.py:488 — FALSE POSITIVE; close both
#1295 #1289 bandit blacklist billing_alert_config.py:158 — FALSE POSITIVE; close both
#1293 #1287 bandit blacklist billing_alert_config.py:77 — FALSE POSITIVE; close both
#1296 #1290 bandit blacklist billing_summary.py:122 — FALSE POSITIVE; close both
#1297 #1292 bandit blacklist customer_audit.py:131 — FALSE POSITIVE; close both
#1298 #1294 bandit blacklist customer_detail.py:135 — FALSE POSITIVE; close both
#1302 #1299 bandit blacklist customer_detail.py:209 — FALSE POSITIVE; close both
#1304 #1300 bandit blacklist customer_detail.py:365 — FALSE POSITIVE; close both
#1306 #1301 bandit blacklist customer_lifecycle.py:136 — FALSE POSITIVE; close both
#1308 #1303 bandit blacklist customer_list.py:131 — FALSE POSITIVE; close both
#1310 #1305 bandit blacklist test_post_deploy_smoke_614.py:177 — FALSE POSITIVE; close both
#1311 #1307 bandit blacklist test_post_deploy_smoke_614.py:393 — FALSE POSITIVE; close both
#1291 #1285 bandit exec_used test_app_debug_guard.py:66 — FALSE POSITIVE; close both
#1288 #1283 bandit hardcoded_sql test_trace_sc1_schema.py:379 — FALSE POSITIVE; close both
#1282 #1276 bandit hardcoded_sql admin_customers.py:1336 — FALSE POSITIVE; close both
#1278 #1279 bandit hardcoded_sql admin_customers.py:1352 — FALSE POSITIVE; close both

Recommended operator action: Run gh issue close 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1310 1311 1312 --comment "False positive — nosec annotation added in PR #XXXX / duplicate of another auto-filed issue" (replace XXXX with the merged PR number).

For #1309: update its severity label from severity:high to severity:medium (actual CVSS 4.5) and add a comment with the full finding details.


MEDIUM findings (7414 — deferred)

7414 bandit MEDIUM findings are out of scope for this triage run. The bulk appear to be try_except_pass (B110) and blacklist (B310) at MEDIUM native severity across backend_v2/ and console/ route files. A dedicated triage card is recommended before the next nightly scan to:

  1. Establish a MEDIUM-only allowlist baseline
  2. Identify any MEDIUM findings that warrant upward re-classification
  3. Decide whether to suppress try_except_pass globally in tests vs app code

LOW findings (10 — batched)

10 LOW findings recorded in bandit.json. Breakdown mirrors the 2026-04-25 baseline pattern (B311 random module, B404 subprocess import, B603/B607 subprocess calls). No individual issues filed.


Carryover from 2026-04-25 baseline (still open)

Issue Finding Status
#291 / #320 serialize-javascript@6.0.2 GHSA-5c6j-r48x-rmvq Still open — npm audit not re-run this cycle; assumed still present
#292 Stale worktree trivy noise Tooling gap; .claude/worktrees/ not yet in trivy --skip-dirs


Acted on this run


Open follow-ups for next triage

  1. Operator action required: Close the 37 false-positive/duplicate issues listed in the deduplication table above
  2. Operator action required: Relabel #1309 from severity:high to severity:medium; update body with CVSS 4.5 detail
  3. Tooling (sre-agent): Fix nightly workflow severity mapping — bandit MEDIUM should not be promoted to HIGH in summary.md aggregator
  4. Tooling (sre-agent): Add deduplication guard to the nightly issue-filer so re-triggers don't create duplicates
  5. Tooling (sre-agent): Confirm --skip-dirs ".claude/worktrees,.clone,**/.venv" is in the trivy invocation per 2026-04-25 baseline recommendation
  6. Triage card (security-agent next run): MEDIUM triage — 7414 findings need a filtered pass before the next nightly to establish a suppressible baseline
  7. npm audit: Was not present in this artifact — confirm it runs in next nightly; serialize-javascript (#320) status unknown

Citations