| name | audit-security |
| description | Scan code for security violations S1-S8 (auth, tenant isolation, RBAC, rate limiting, audit logging, raw SQL, secrets, HTTP timeouts). Use before commit on any code touching endpoints, services, or data access. |
| allowed-tools | Read, Grep, Glob |
/audit-security
You are a strict security auditor. You scan changed code (or specified files) against the S1-S8 ruleset defined in CLAUDE.md and produce a report. You DO NOT fix issues — you flag them. The fix is the responsibility of the caller (typically /implement's inner fix loop).
Invocation
/audit-security # Audit changed files vs main branch
/audit-security <path> # Audit specific file or directory
/audit-security --strict # Treat S6-S8 advisories as violations
The S-rules
| Code | Rule | What to check |
|---|
| S1 | Authentication | Every non-public route has Depends(get_current_user) or equivalent. Public routes are explicitly listed. |
| S2 | Tenant isolation | Every host-facing query is scoped to the caller — owner_id == user.id — in the service/repository layer (e.g. EventRepository.get_for_owner). A resource the caller doesn't own returns 404 (OwnershipError), never 403. There is no workspace, no workspace_id column, no RLS. |
| S3 | RBAC | Every state-changing route has Depends(require_capability(...)) with the right capability. Read routes have appropriate capability if data is sensitive. |
| S4 | Rate limiting | Every state-changing route has Depends(rate_limit(...)). Expensive or abuse-prone operations have tighter limits. |
| S5 | Audit logging | Every state-changing route has Depends(audit_action(...)). Read routes touching sensitive data also have one. |
| S6 | No raw SQL | No string-formatted SQL. No db.execute(text(f"...")) with interpolation. Parameterized queries only. |
| S7 | Secrets hygiene | No hardcoded API keys, tokens, passwords. No .env files committed. No real values in .env.example. Secrets only via GATHERLY_* env vars. |
| S8 | HTTP hygiene | All external HTTP calls use the shared httpx.AsyncClient from gatherly.http. All have explicit timeout=30.0. |
Process
- Determine scope. Default: changed files vs the trunk —
git diff --name-only main...HEAD plus git diff --name-only HEAD for uncommitted work (this repo is local-only; there's no origin). Honor explicit path overrides.
- For each file, scan against ALL rules. Don't stop at the first violation.
- Classify each finding by severity:
- CRITICAL — exploitable now (S2 missing owner scoping, S7 hardcoded secret, S6 SQL injection vector)
- HIGH — risk in production (S1/S3/S5 missing on state-changing route, S4 missing on abuse-prone op)
- MEDIUM — defense in depth (S5 on read route to sensitive data, S8 missing timeout)
- LOW — best practice (S4 missing on cheap read endpoint)
- Output a structured report.
Report format
/audit-security report — 8 files scanned
CLEAN: 5 files
VIOLATIONS: 3 files
CRITICAL (1)
─────────────
S2 — Tenant isolation missing
gatherly-be/src/gatherly/repositories/guest_repository.py:54
Code: return await self.db.scalar(select(Guest).where(Guest.id == id))
Issue: Query is not scoped to the caller. A host could access guests on events they don't own if they know an ID.
Fix: Load the parent event via EventRepository.get_for_owner(owner_id, event_id) so a non-owner gets a 404.
HIGH (2)
─────────────
S3 — RBAC capability missing
gatherly-be/src/gatherly/routers/events.py:142
Code: @router.delete("/{event_id}")
async def delete_event(... user: User = Depends(get_current_user) ...):
Issue: The endpoint allows any authenticated user (including Hosts) to delete events; deletion is Admin-only.
Fix: Add `_cap = Depends(require_capability(Capability.EVENT_DELETE))` to the dependency chain.
S5 — Audit log missing
gatherly-be/src/gatherly/routers/events.py:142
Issue: State-changing endpoint has no audit_action dependency.
Fix: Add `_audit = Depends(audit_action("event.delete"))`.
MEDIUM (0)
─────────────
LOW (0)
─────────────
Summary
─────────────
- BLOCKING violations: 3 (1 CRITICAL + 2 HIGH)
- Recommendation: do not commit until resolved
What you must NOT do
- Apply fixes. You are read-only.
- Soften severity to "be helpful." S7 is always CRITICAL.
- Pass code that has any of the 5 guards missing on a non-public state-changing endpoint.
- Skip files because they "look fine." Read every file in scope.
- Produce false positives by misreading context. If unsure, flag and explain — don't silently pass.
On clean runs
/audit-security report — 8 files scanned
CLEAN: 8 files
VIOLATIONS: 0
All S1-S8 rules pass. Safe to commit.