Security hardening: body size limit, Gemini key header, AI rate limiting, playbook path traversal#161
Security hardening: body size limit, Gemini key header, AI rate limiting, playbook path traversal#161TFT444 wants to merge 1 commit into
Conversation
…g, playbook path traversal guard Closes #149. - SEC-002: cap request bodies at 2MB with a JSON 413 handler - SEC-003: send the Gemini API key via x-goog-api-key header instead of a URL query param, so it never leaks into logs - SEC-004: add a lightweight in-memory rate limiter (20 req/min/IP) on all AI endpoints - SEC-005: validate rule_id and confirm the resolved script path stays inside playbooks/cli/ before reading it - SEC-001 was already fixed on dev via #144; added a regression test to lock it in
2311c7c to
2b32252
Compare
m-khan-97
left a comment
There was a problem hiding this comment.
Reviewed the full diff. The Gemini header fix and the playbook path-traversal fix (regex allowlist + resolve() + is_relative_to() double-guard) are both correct and well covered by tests — good work on SEC-003 and SEC-005.
Two things on the rate limiter (SEC-004) I want to raise before we call this closed:
- State is per-process, but
startup.shrunsgunicorn --workers 2._hitsis a plain module-level dict, so each worker process keeps its own count. That means "20 requests/min per IP" is actually enforced as up to ~40/min with our current startup script — not a hypothetical multi-instance scenario, it's true today. - The limiter keys on
request.remote_addrwith noProxyFix/X-Forwarded-Forhandling, and we sit behind Render's proxy. If Render doesn't forward the real client IP straight through to the container, every external client collapses into a singleremote_addr, which turns "20/min per client" into "20/min for the whole API" — a much harsher and almost certainly unintended limit. Can we confirm what Render actually hands us here before relying on this in prod?
Minor/non-blocking: _hits entries for a given IP+path are never evicted once that key goes quiet, so there's slow unbounded memory growth over a long-running process. Worth a TODO, not urgent.
None of this is a regression risk and everything else here is solid, but I'd like the rate-limiting behavior confirmed against our actual Render setup (or moved to something process-shared, e.g. Redis-backed) before we treat SEC-004 as fully closed. Requesting changes on that basis — happy to re-review quickly once addressed.
|
@TFT444 , fixes requested. |
ritiksah141
left a comment
There was a problem hiding this comment.
I reviewed this against #149. The security fixes are moving in the right direction, and the focused tests pass locally, but I am requesting changes before merge.
Findings:
-
The branch fails the current dev lint and format gate. Current dev now enforces Ruff checks, and the touched files do not pass that gate:
api/app.py:263hasE402 Module level import not at top of fileapi/routes/findings.py:100hasE741 Ambiguous variable name: lapi/routes/findings.py:109hasF541 f-string without placeholdersruff format --checkwould reformatapi/app.py,api/rate_limit.py,api/routes/ai.py,api/routes/findings.py, andapi/services/ai_provider.py
Please rebase or merge latest
dev, then run the current lint and format checks before this merges. -
api/rate_limit.py:30keys the AI rate limit onrequest.remote_addr. On Render or any reverse-proxy deployment, that value can be the proxy or load balancer address instead of the real client. That means unrelated users can share one bucket, and one noisy client can rate-limit everyone behind the same proxy. Since SEC-004 is meant to protect AI endpoints per caller, this should use a trusted proxy-aware client identity helper, for example via configured proxy handling or a controlledaccess_route/forwarded-header strategy. -
This branch is stale against current
devand needs revalidation with the newer app changes. The branch predates the current observability and CI updates, and the merge base shows overlap inapi/app.pyandapi/routes/findings.py. Please update the branch and make sure the current/ready,/metrics, request-id/error handling, async scan persistence, and new CI checks still pass after the security changes are applied.
Validation I ran:
- Focused security tests:
17 passed - Full pytest:
125 passed, 2 skipped, 1 failed - The full pytest failure is the known local ONNX Runtime temp-directory error in
tests/test_ai_hallucination_guard.py::TestHallucinationGuard::test_vector_store_purity, not related to this PR git diff --check dev...HEADpassed- PR checks currently shown on GitHub are the older
Run All CI Checksand CodeQL checks, not the current dev CI pipeline
|
Gonna be fix soon. thanks for the review |
Summary
Closes #149 (FIX 1: Security hardening).
devvia feat(auth): require JWT for GET /api/* endpoints with optional public demo mode #144; added a regression test to lock it in.MAX_CONTENT_LENGTH(2MB) with a JSON 413 error handler.?key=query param to thex-goog-api-keyheader.rule_idis now validated against a strict pattern and the resolved script path is confirmed to stay insideplaybooks/cli/before being read.Test plan
tests/test_app_security.py— JWT enforcement on GET routes, 413 on oversized bodytests/test_ai_provider.py— Gemini key sent via header, absent from URLtests/test_rate_limit.py— limiter enforces per-route/per-IP limits, bypassed in test modetests/test_findings_playbook.py— traversal/absolute-path/slash rule_id rejected, valid rule_id still worksdevcheckout)