Security hardening: auth replay, encryption guards, rate limits, CORS#209
Conversation
|
@claude code review |
This comment was marked as outdated.
This comment was marked as outdated.
|
@claude code review |
This comment was marked as outdated.
This comment was marked as outdated.
…, error sanitization (#209) Address 18 security audit findings (7 HIGH, 8 MEDIUM, 3 LOW) across the MCP HTTP server, plus test and observability improvements. HIGH: - H1: Fail-closed encryption — encrypt/decrypt raise RuntimeError in HTTP mode when UPLOAD_SECRET is missing. Required regardless of --no-auth. - H2: ALLOW_NO_AUTH strict check — only "1" disables auth. - H3: Redis password required in authenticated HTTP mode (hard error). - H4: HTTPS-only URL validation on mcp_server_url and supabase_url. - H5: Referrer-Policy no-referrer meta tag in both widget templates. - H6: Auth code and refresh token re-storage uses SET NX to prevent concurrent overwrites. - H7: OAuth state consumed atomically in handle_start, re-stored to narrow replay window. MEDIUM: - M1: Security events (revoked tokens, failed polls, rate limits) at WARNING. - M2: Stack traces suppressed in error responses; generic messages returned. - M3: app.openLink() calls guarded with https scheme check. - M4: rel="noopener noreferrer" on linkified anchors in results widget. - M5: Removed in-memory rate limit fallback — Redis is a hard dependency. - M6: Rate limit TTL always refreshed (removed nx=True on EXPIRE). - M7: CORS wildcard origin for widget endpoints (auth via Bearer tokens). - M8: Content-Type allowlist on upload endpoint (415 for unsupported types). LOW: - L1: Per-user upload rate limiting via atomic Redis pipeline. - L4: Remote Redis without SSL fails in HTTP mode (warns in stdio). Additional: - Upload tool fails early when client has no API token. - Direct path_params lookup for state in handle_start. - E2e integration test: swap results tool to HTTP variant in _http_state. - Fernet cache cleared in test override_settings for correct encryption. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7e23d9e to
2ba26fb
Compare
Peek upload metadata (non-destructive GET) during validation, then atomically pop (GETDEL) only after content-type, token, and rate-limit checks pass. This makes 429/415/403 responses retryable without burning the one-time upload URL. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@claude code review. focus on everyrow-mcp/src/everyrow_mcp/uploads.py |
|
Claude finished @RafaelPo's task in 1m 59s —— View job Code Review:
|
Security hardening PR #209 requires UPLOAD_SECRET (≥32 chars) in HTTP mode for HMAC token signing. Missing secret caused staging pod crash on startup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Security hardening PR #209 requires UPLOAD_SECRET (≥32 chars) in HTTP mode for HMAC token signing. Missing secret caused staging pod crash on startup. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Addresses 18 security audit findings (7 HIGH, 8 MEDIUM, 3 LOW) across the MCP HTTP server, plus test and observability improvements. 24 files changed, 518 insertions, 177 deletions.
HIGH
encrypt_value/decrypt_valueraiseRuntimeErrorin HTTP mode whenUPLOAD_SECRETis missing. Required regardless of--no-auth.ALLOW_NO_AUTHstrict check: only"1"disables auth.mcp_server_urlandsupabase_url(localhost exempt).Referrer-Policy: no-referrermeta tag in both widget HTML templates.SET ... NXto prevent concurrent overwrites.handle_start, re-stored to narrow replay window.MEDIUM
app.openLink()calls guarded withhttps?://scheme check.rel="noopener noreferrer"on linkified anchors in results widget.nx=Trueon EXPIRE).LOW
Additional
path_params["state"]lookup inhandle_start(was.get("state", ""))._http_statefixture now swaps results tool to HTTP variant._get_fernet.cache_clear()in testoverride_settingsfor correct encryption behavior.Test plan
RUN_INTEGRATION_TESTS=1)test_security_hardening.py: HTTPS URL validation, encryption HTTP guard, Redis SSL enforcement (16 tests)TestCorsOriginValidationintest_routes.py(3 tests)TestContentTypeValidationintest_uploads.py--http --no-authwithoutUPLOAD_SECRET→ startup errorALLOW_NO_AUTH=false→ startup errorMCP_SERVER_URL=http://remote.example.com→ validation errorREDIS_HOST=remote.example.comwithoutREDIS_SSL=true→ validation error🤖 Generated with Claude Code