Add global rate limit and /.well-known/security.txt#24
Merged
Conversation
…trust Production-hardening additions ported from vagrant-story-api's launch prep: - Global default rate limit (60/min per client IP) via SlowAPIMiddleware + `default_limits` on the Limiter. Applies to every route that isn't decorated with its own limit or marked exempt; the stricter per-path auth limits stack on top. Infrastructure routes (`/`, `/health`, `/docs`, `/.well-known/security.txt`) are `@limiter.exempt` so health probes, spec fetches, and automated scanners can't burn through the quota. - `/.well-known/security.txt` per securitytxt.org, with a placeholder Contact and a 1-year Expires; the README "before first deploy" note now flags both for replacement. - `start.sh` runs uvicorn with `--proxy-headers --forwarded-allow-ips='*'` so `request.client.host` resolves to the real client IP behind a proxy/load balancer — without it the IP-keyed rate limiter collapses every request into one bucket. A comment spells out the trust assumption. Tests (tests/test_app.py): security.txt content; the default limit 429s an undecorated route on the 61st call within the window; infra routes don't. conftest resets the limiter storage between tests so counts don't leak. Closes #22, closes #23.
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.
Production-hardening additions ported from
vagrant-story-api's launch prep — closes #22 and #23.1. Global default rate limit (#22)
Limiternow hasdefault_limits=["60/minute"]andSlowAPIMiddlewareis installed — without the middleware, only routes with an explicit@limiter.limit(...)are limited; with it, the default applies to every route. (We don't have any@limiter.limit-decorated routes; the auth limits are done via a path-matching middleware because the fastapi-users routers are generated.)5/minlogin,3/minregister,30/minrefresh) stack on top of the global default — an auth route is subject to both. The stricter auth limit is the binding constraint in practice; the global one is a backstop./,/health,/docs,/.well-known/security.txt— are@limiter.exemptso health-check probes, OpenAPI/doc fetches, and automated scanners can't burn through the quota. (/openapi.jsonand/redocare FastAPI built-ins and aren't easily decoratable; 60/min is plenty for spec fetches and codegen.)request.client.host, which behind a proxy/load balancer is the proxy's IP unless uvicorn trustsX-Forwarded-For— see chore: add info about open api #3 below.2.
/.well-known/security.txt(#23)text/plainper securitytxt.org, with a placeholderContact: mailto:security@example.comandExpires: 2027-05-12(one year out).COOKIE_PREFIXreminder).3. Proxy-header trust in
start.shstart.shnow runsuvicorn ... --proxy-headers --forwarded-allow-ips='*'sorequest.client.hostresolves to the real client IP behind a proxy (Cloud Run, ALB, nginx). Without it, the IP-keyed rate limiter collapses every request into one global bucket. A comment instart.shexplains the trust assumption (*is correct only when the service is always behind a proxy that overwrites the header).Tests
tests/test_app.py:security.txtis served astext/plainwithContact:andExpires:./v1/notes) on the 61st call within the window./health,/,/.well-known/security.txt) survive 70+ rapid hits without a 429.conftest.pygets an autousereset_rate_limiterfixture (clearslimiter._storagebefore/after each test) so request counts don't leak between tests.Verification
uv run pytest— 32 passed.uv run ruff check ./ruff format --check .clean.sh -n start.shclean. App boots; route list confirmed (/.well-known/security.txtpresent and unversioned; infra routes exempt).