bugclass-detectors/README.md
Elite Agentic Solutions 7ee5300d43 bugclass-detectors v0.1.0 — five silent-failure bug-class checks (MIT)
AST-aware, stdlib-only static checks: swallowed-exception-around-write (C1),
undefined-name (C2), hardcoded-secret-literal (C3), fail-open-flag (C4),
rotated-secret-captured-once (C5). Tests + CI + examples included.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:29:50 -07:00

102 lines
5.5 KiB
Markdown

# bugclass-detectors
**AST-aware static checks for the silent-failure bug classes that AI-generated and fast-moving code keeps reintroducing.**
These are not style nits. They are the bugs that pass code review, pass your tests, keep the process *green* — and silently lose data, fail open, or crash on the one unlucky path. They are exactly the classes an AI coding assistant reintroduces because each instance looks locally reasonable.
`bugclass-detectors` finds them with a real parser (not grep), in pure-stdlib Python, with zero config.
```bash
pip install bugclass-detectors # or: pipx run bugclass-detectors .
bugclass-detectors src/ # human report
bugclass-detectors src/ --lint # exit 1 on any HIGH finding (CI gate)
bugclass-detectors src/ --json # machine-readable
```
---
## The five bug classes
| ID | Class | What it catches | Why it bites |
|----|-------|-----------------|--------------|
| **C1** | `SWALLOWED_EXCEPTION_AROUND_WRITE` | A bare/catch-all `except` that swallows an exception wrapping a **write** (DB / cache / file) with no log and no re-raise. | The producer stays alive, the heartbeat is green, the data is *gone*, and nothing logs. The single most dangerous silent-failure shape there is. |
| **C2** | `UNDEFINED_NAME` | Use of a name never bound in its scope and not a builtin/import/param/closure var. | A `NameError` that only fires on the code path you didn't test. (pyflakes F821 class, no pyflakes dependency.) |
| **C3** | `HARDCODED_SECRET_LITERAL` | A password/token/key assigned to an inline string literal (in code or a systemd `Environment=`). | Diverges silently the moment the real secret rotates → an outage with no obvious cause. Secrets are masked in output. |
| **C4** | `FAIL_OPEN_FLAG` | A safety / kill / arming / feature gate read from a store repointable by one env var, with no presence assertion. | If the flag is **absent** on the repointed store it reads falsy → the gate fails **open** instead of closed. |
| **C5** | `ROTATED_SECRET_CAPTURED_ONCE` | A long-lived consumer (`while True`, a daemon) that captures a secret **once** at module scope. | When the secret rotates the process keeps the stale value and silently 401/403s forever. |
Every check is **AST/tokenizer-aware** (so it doesn't fire on the word in a comment or a string), **read-only**, and emits a uniform finding:
```json
{ "check": "C1_SWALLOWED_WRITE", "severity": "HIGH",
"file": "src/recorder.py", "lineno": 88,
"snippet": "except Exception:",
"why": "catch-all/bare except SILENTLY SWALLOWS a db/cache write `hset` — ..." }
```
Disable any check with `BUGCLASS_DISABLE=C1,C4` or `--checks C2,C3`.
---
## Quick start
```bash
git clone https://github.com/<org>/bugclass-detectors
cd bugclass-detectors
python -m bugclass_detectors examples/buggy_sample.py # see all five fire
python tests/test_detectors.py # smoke tests
```
### As a CI gate (GitHub Actions)
```yaml
- name: bug-class lint
run: pipx run bugclass-detectors . --lint --min-sev HIGH
```
`--lint` returns a non-zero exit on any HIGH finding, so a swallowed write or an undefined name fails the build.
### As a library
```python
from bugclass_detectors import run
for f in run(["./src"], checks=["C1", "C4"], min_sev="HIGH"):
print(f["file"], f["lineno"], f["why"])
```
---
## Why these five (and why a parser, not grep)
This pack started as the in-house lint that runs against a continuously-operating multi-service production estate. Each class on this list earned its place by causing a **real** silent outage in production — a recorder whose only output was swallowed for weeks behind a bare `except`, a feature gate that failed open when its flag-store was repointed, a credential captured once that 403'd silently after a rotation. The fix in every case was one line; the cost of *not* catching it was days.
Grep can't catch these without drowning you in false positives — `except` and `password` and `kill` appear constantly in comments, strings, and legitimate code. These checks parse the AST and tokenize the source, so they fire on the *shape* of the bug, not the word.
---
## Limitations (honest)
- Python only (today). The classes are language-agnostic; ports welcome.
- C1/C4/C5 are heuristic — tuned to favor precision over recall (few false positives, will miss exotic phrasings). C2 is conservative (only flags provably-unbound names).
- This is a *focused* tool, not a replacement for a full linter, type-checker, or SAST suite. Run it alongside them.
---
## Want this run continuously, across every repo, with triage and auto-fix?
This open-source pack is the **engine**. Running it well at scale — across many repos and languages, with severity triage, suppression management, auto-fix PRs, dashboards, and a human in the loop — is an operations problem.
- **[Elite Agentic Solutions](https://eliteagenticsolutions.com)** runs this and a larger battery of production-hardened checks as a **continuous code-health / audit-as-a-service** for teams shipping AI-assisted code. We operate the gate so your pipeline doesn't regress.
- **[CloudHostAI](https://cloudhostai.com)** offers it as a one-click hosted scanner tile for your repos.
The library is, and will stay, free under MIT. The hosted operation is the paid product.
---
## Contributing
Issues and PRs welcome — especially new bug classes that have caused you a real silent outage, and ports to other languages. Keep each check AST-aware, read-only, and dependency-free.
## License
MIT © Elite Agentic Solutions. See [LICENSE](LICENSE).