What
Add tower middleware to the axum router so the api defends itself even if Cloudflare is bypassed or misconfigured:
RequestBodyLimitLayer on POST /v1/drip -- cap at 16 KB. Default axum is generous; a tiny JSON body is all this endpoint ever sees.
TimeoutLayer at 30s per request. Prevents slow-loris from holding handlers open.
tower-governor (or homegrown IP-bucket) for per-IP rate limit: 10 req/min global, 3 req/min on POST /v1/drip. Belt-and-suspenders with Cloudflare's rate limits.
- HTTP security headers:
X-Content-Type-Options: nosniff, Referrer-Policy: same-origin, no Server: header, etc. Cheap with tower-http SetResponseHeader.
- Structured drip log -- log each /v1/drip with
target_address, client_ip, tx_hash (or rejection reason) at INFO. Currently we log "drip signer loaded" + per-drip latency but not the per-call audit trail.
Why
Cloudflare's WAF + rate limit handles ~95% of attacks at the edge, but:
- A bug in Cloudflare config could expose the origin (Railway pubic URL
ligate-api-production.up.railway.app)
- Future infra changes (e.g., adding direct VPC access for explorer) might bypass the edge
- An in-code limit is a backstop the platform layer can't accidentally drop
Test plan
What
Add tower middleware to the axum router so the api defends itself even if Cloudflare is bypassed or misconfigured:
RequestBodyLimitLayeronPOST /v1/drip-- cap at 16 KB. Default axum is generous; a tiny JSON body is all this endpoint ever sees.TimeoutLayerat 30s per request. Prevents slow-loris from holding handlers open.tower-governor(or homegrown IP-bucket) for per-IP rate limit: 10 req/min global, 3 req/min onPOST /v1/drip. Belt-and-suspenders with Cloudflare's rate limits.X-Content-Type-Options: nosniff,Referrer-Policy: same-origin, noServer:header, etc. Cheap withtower-httpSetResponseHeader.target_address,client_ip,tx_hash(or rejection reason) at INFO. Currently we log "drip signer loaded" + per-drip latency but not the per-call audit trail.Why
Cloudflare's WAF + rate limit handles ~95% of attacks at the edge, but:
ligate-api-production.up.railway.app)Test plan
crates/api/src/main.rs(betweenRouter::new()andaxum::serve())/v1/dripin 60s; expect first 10 to 200/429-from-rate-limit, 11th to 429