Blanket per-endpoint limits would have been the wrong shape here. Every REST/WS/MCP surface is owner-only — an unauthenticated request is rejected by resolve_bearer/is_owner before any handler runs — so limiting them would mostly throttle the single legitimate operator, and real spend control for outbound calls is already max_concurrent_calls in gateway.make_call. What is genuinely exposed is the handful of /auth/* routes that must answer before an identity exists. /auth/callback and /auth/refresh-callback each make an outbound token exchange with Casdoor on every request; /auth/me opens a DB session and runs a token lookup. All are free to trigger and none are cheap to serve. /auth/logout is left unlimited — it builds a redirect URL and does no I/O. Not a defence against credential guessing: PATs are secrets.token_urlsafe(32) (256 bits) compared by SHA-256 digest, so brute force was never the threat. This is about unauthenticated work an attacker controls. Fixed-window, in-process, no new dependency — one operator and one process make a shared counter store infrastructure without a purpose. The bucket store is bounded and evicts oldest-first, since an unbounded map keyed by source address would itself be the exhaustion vector. The limiter keys on the socket peer and deliberately ignores X-Forwarded-For. That header is attacker-controlled unless a trusted proxy overwrites it, and this app establishes no such trust; keying on it would let one client present as thousands and make the limiter worse than useless. Behind the estate's reverse proxy the limit is therefore per-proxy, not per-caller — correct for exhaustion and honest about what it can enforce. Per-caller limits need an explicit trusted-proxy config, noted in CLAUDE.md so it isn't added silently. Verified against a real server: exactly 30 requests pass, then 429 with Retry-After: 60, while an owner-gated route serves 40/40. The 429s appear in the JSON access log with queryable status_code and client_addr, so an attack is visible in Loki. The wiring test identifies the dependency by qualname rather than string search, and was mutation-checked by removing the limit from /auth/me. Also documents 401/403/429 in the API reference — 401 and 403 have existed since auth landed but were never in the status-code table. Phase 4 is now complete. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hold Slayer Documentation
Comprehensive documentation for the Hold Slayer AI telephony gateway.
Contents
| Document | Description |
|---|---|
| Architecture | System architecture, component diagram, data flow |
| Core Engine | SIP engine, media pipeline, call manager, event bus |
| Dial Plan | Number normalisation and the emergency-number guard |
| PJSUA2 Build | Building the pjsua2 bindings (not pip-installable) |
| Asterisk Lab | The fake PSTN used for media validation |
| Hold Slayer Service | IVR navigation, hold detection, human detection, transfer |
| Audio Classifier | Waveform analysis, feature extraction, classification logic |
| Services | LLM client, transcription, recording, analytics, notifications |
| Call Flows | Call flow model, step types, learner, CRUD API |
| API Reference | REST endpoints, WebSocket protocol, request/response schemas |
| MCP Server | MCP tools and resources for AI assistant integration |
| Configuration | Environment variables, settings, deployment options |
| Development | Setup, testing, contributing, project conventions |