Conversation
- API-key middleware (X-API-Key) with Secret Manager rotation pattern - Body-size + per-request timeout + Pydantic text max_length guards - structlog JSON logs + per-request UUID + Prometheus /metrics - Split /v1/health (liveness) from /v1/ready (eager-load + Skyflow creds) - slowapi per-API-key rate limiting (in-memory, per-instance) - InflightTracker with bounded SIGTERM drain → 503 once draining - Tests: 25 passing across auth/limits/rate-limit/shutdown/routes - GitHub Actions: lint+test+docker-smoke on PR; tag-driven release pushes to Artifact Registry - deploy/cloudrun.yaml + deploy/README.md (WIF setup, secrets, knobs) - Dockerfile honors $PORT (Cloud Run sets it) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Hey @claude can you review please? Looks like we have failures in CI for this. |
|
Claude finished @jstjoe's task in 3m 42s —— View job PR Review: Plan 07 — Cloud Run HardeningTasks
CI Failures — Root CauseBoth failing jobs have the same root cause: the 1. Test jobs (all 3 Python versions) — "Install workspace" step
[tool.uv.workspace]
members = ["eval", "api", "privacy-filter"] # ← privacy-filter doesn't existBecause the directory is missing, uv can't register it as a workspace member, so 2. Docker smoke build
Fix: the Code ReviewThe overall design is solid — the auth/limits/observability/shutdown/rate-limit split is clean, the middleware ordering comment is accurate, and the test coverage is good. Here are the issues worth addressing: Bugs
latency_ms = float(result.get("latency_ms") or (time.perf_counter() - t0) * 1000)If a detector returns raw = result.get("latency_ms")
latency_ms = float(raw) if raw is not None else (time.perf_counter() - t0) * 1000Security
rid = request.headers.get("x-request-id") or uuid.uuid4().hexAn attacker can send import re
_UUID_RE = re.compile(r'^[0-9a-f]{32}$')
raw_rid = request.headers.get("x-request-id", "")
rid = raw_rid if _UUID_RE.match(raw_rid) else uuid.uuid4().hex
return f"key:{key}"For in-memory storage this is inconsequential. But if you ever move to Redis/Memorystore (already mentioned in the docstring), the plaintext key will be written there. Use a hash:
The code comment is honest about this: "Bodies without Content-Length fall through." Worth calling out explicitly: an attacker omitting Test Infrastructure
inner = getattr(storage, "storage", None)
if isinstance(inner, dict):
inner.clear()This reaches into slowapi private internals. If slowapi changes its storage structure the teardown silently does nothing and tests start polluting each other. The Minor
SummaryThe hardening design is well-executed — the auth middleware, probe bypass logic, drain semantics, and metric naming are all good. The blocking issues are:
|
Summary
Turns the unified API into something deployable on Cloud Run.
X-API-Keymiddleware, keys fromAPI_KEYSenv (Secret Manager in prod). Probes (/v1/health,/v1/ready,/metrics) skip auth so Cloud Run + scrapers can hit them. Refuses to start with no keys configured (setAUTH_DISABLED=1for dev).max_lengthontext(422), per-request timeout viaasyncio.wait_for(504). Defaults: 200 KB body, 100k char text, 30s timeout. All overridable by env.X-Request-IDheader, Prometheus/metricswith custom counters:pii_detector_calls_total,pii_detector_latency_seconds,pii_spans_detected_total./v1/healthis liveness (always 200),/v1/readyis readiness (200 only when EAGER_LOAD detectors are loaded and Skyflow creds look complete). Cloud Run probe wiring incloudrun.yaml./redact, 60/min for/detect. Per-instance counter — caveat documented.InflightTrackerwaits for in-flight requests on SIGTERM (bounded bySHUTDOWN_DRAIN_SECONDS=20); new requests get 503 once draining starts; probes still pass..github/workflows/ci.ymlruns ruff + pytest matrix (3.10/3.11/3.12) + Docker smoke build on every PR.release.ymlbuilds the full image on tag and pushes to Artifact Registry via Workload Identity Federation.api/deploy/cloudrun.yamlservice spec +api/deploy/README.mdwith one-time setup (Artifact Registry, secrets, WIF), per-release commands, knob explanations, rollback.Test plan
uv run pytest api/tests/— 25 passing (auth, limits, rate-limit, shutdown, routes)uvx ruff check api/— cleandocker build -f api/Dockerfile --build-arg INCLUDE_OPF=0 --build-arg INCLUDE_GLINER=0 --build-arg INCLUDE_PRESIDIO=0 -t privacy-api:smoke .boots,/v1/healthreturns 200API_KEYS=test DEFAULT_DETECTOR=opf EAGER_LOAD=opf uvicorn opf_api.main:app— 401 without key, 200 with, 504 on slow text, 429 after burst🤖 Generated with Claude Code