feat(security): SEC014/SEC015/SEC016 — second B-class batch#8
Merged
Conversation
Cross-language text scan for `-----BEGIN ... PRIVATE KEY-----` in any source file. α-class — language-agnostic, runs on every file regardless of grammar. Precision is essentially perfect: the PEM header is unique enough that no legitimate code embeds it as a string literal. Public keys (`-----BEGIN PUBLIC KEY-----`) are intentionally excluded — they aren't credentials. Tests: 3 — Python triple-string positive, JS escaped-string positive, public-key negative. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Catches the slightly more sophisticated form of the bare-except
antipattern: agent picked an exception type but chose the broadest
one and then silently swallowed it.
Two language shapes:
- **Python**: `except Exception: pass` or `except BaseException: pass`
— broad type, body is `pass` only / empty / single comment / a
bare `return` / `return None`.
- **JavaScript / TypeScript**: `catch (e) {}` or `catch (e) { /* nothing */ }`
— catch is implicitly broad in JS, so any catch with an empty /
comment-only body fires.
Excludes (FP control):
- Bare `except:` — already SEC013's territory
- Specific types: `except ValueError: pass` — developer expected
this case
- Body that logs / re-raises / does anything substantive
Body-emptiness logic mirrors `signals/smells.rs::handler_body_is_empty`
so behaviour stays consistent with the existing
`empty_handler_count` signal.
Tests: 6 — Python broad+pass / Python broad+log negative / Python
specific-type negative / Python BaseException positive / JS empty
positive / JS log+throw negative.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… URL
AST rule on call expressions. Fires when an outbound HTTP call
(`requests.get`, `httpx.X`, `aiohttp.X`, `urllib.urlopen`, `fetch`,
`axios.X`, `http.Get`) is given a URL argument that obviously came
from request input.
Per-language receiver gating keeps `dict.get()` / `cache.get()` /
`Map.get()` from firing — only http-shaped receivers count.
User-input shapes (conservative needle list):
- Flask / Django / FastAPI: `request.args`, `request.form`,
`request.json`, `request.values`, `request.params`
- Express / Koa: `req.body`, `req.params`, `req.query`,
`req.headers`, `ctx.request`, `ctx.params`
- Subscript access: `params[...]`, `query[...]`, `body[...]`
- Generic: `user_input`, `user_url`, `input(...)`
Tests: 5 — Python `requests.get(req.params.url)` positive, Python
`request.args.get('url')` positive, JS `fetch(req.query.url)`
positive, static URL negative, `dict.get(req.params.url)` negative
(receiver-name disambiguator).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three more B-class rules following the α/β/γ pattern split from
PR #7. Each closes a real false-negative class observed in the
agent comparison study, all kept under FP rate < 1% via narrow
disambiguator filters.
-----BEGIN ... PRIVATE KEY-----PEM blockexcept Exception: pass, JScatch (e) {}Commits (review in order)
feat(security): SEC014 (α) — hardcoded PEM private key—text scan; public keys excluded.
feat(security): SEC015 (β) — silent broad except / empty catch—AST on
except_clause(Python) /catch_clause(JS / TS).Body-emptiness logic mirrors the existing
empty_handler_countsignal in
signals/smells.rs. Bareexcept:stays SEC013's job.feat(security): SEC016 (β) — SSRF marker on HTTP call—per-language receiver gating filters
dict.get/cache.getnoise. URL-arg pattern list covers Flask / Django / FastAPI /
Express / Koa request shapes.
docs(readme): bump SEC rule count 13 → 16 + name new rules—EN + ZH README tables.
Skipped (deferred or out of scope)
These were on the backlog but excluded from this PR because they
can't hit FP rate < 1% without dataflow analysis:
../in path-like contexts) — too many legitrelative imports / paths.
/api/x/<id>without owner check) — needs route + authcontext.
price/amount) — heuristictoo broad, lots of legit non-money use.
If we later add a dataflow / type pass, those become viable.
Test results
cargo test --workspace— 130 / 130 pass (was 116 baseline; +14new tests covering positive and must-NOT-fire negatives per rule).
Live MCP sanity-check on a single Python file with all 3 patterns:
All three patterns were silent before this PR.
Test plan
cargo test --workspace— 130 / 130 passcargo install --path crates/aegis-mcp --forcesucceeds-----BEGIN PUBLIC KEY-----→ no SEC014except ValueError: pass→ no SEC015 (specific type, narrow)except Exception as e: log(e); raise→ no SEC015 (body has work)requests.get("https://api.example.com/data")→ no SEC016 (static URL)data.get(req.params.url)→ no SEC016 (dict, not HTTP client)🤖 Generated with Claude Code