feat: add rate limiting to public API#11986
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review infoConfiguration used: Organization UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds express-rate-limit, new rate-limiting middleware with four configurable limiters, wires those limiters into public API routes and OpenAPI 429 responses, enables trust proxy, updates .env example, and wraps asset enrichment in a try/catch to avoid failures during base asset lookup. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Protect expensive swap endpoints from abuse with tiered rate limits: - Global: 300 req/min per IP (all routes) - Data: 120 req/min per IP (chains/assets) - Swap Rates: 60 req/min per IP (GET /v1/swap/rates) - Swap Quote: 45 req/min per IP (POST /v1/swap/quote) All limits are configurable via environment variables and compatible with the swap widget's 15-second polling interval. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
e14eb17 to
f9c3149
Compare
35759c7 to
58827b3
Compare
gh pr edit --body fails on this repo due to deprecated Projects Classic GraphQL fields. Document the REST API alternative. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/public-api/src/assets.ts`:
- Around line 50-60: In the catch block that wraps the
getBaseAsset(asset.chainId) call (the try surrounding getBaseAsset,
enrichedAssetsById, assetId and asset), replace the silent swallow with a
structured log entry that records the caught error plus context (at minimum
assetId and asset.chainId and a short message like "failed to enrich asset with
base data"), then continue to set enrichedAssetsById[assetId] = asset as the
fallback; ensure you use the module's standard logger (e.g., processLogger or
logger) and include the error object/stack in the log metadata for debugging.
In `@packages/public-api/src/docs/openapi.ts`:
- Around line 209-210: Remove the newly added comment block containing the line
"// --- Shared Response Schemas ---" from the file; locate the standalone
comment token matching that exact text and delete it so the codebase conforms to
the "no code comments" guideline.
In `@packages/public-api/src/index.ts`:
- Line 23: The line app.set('trust proxy', 1) forces Express to trust
X-Forwarded-* headers; make this configurable via an environment variable (e.g.,
TRUST_PROXY) so non-proxy environments don't accept spoofed headers—read
process.env.TRUST_PROXY (treat empty/undefined as false), parse values like "1",
"true", or a numeric value accordingly, and call app.set('trust proxy',
parsedValue) instead of the hardcoded 1; update the initialization around
app.set('trust proxy', 1) to use this parsed env value so req.ip/rate-limiter
behavior is correct per deployment.
In `@packages/public-api/src/middleware/rateLimit.ts`:
- Around line 13-17: Create a string enum (e.g., export enum RateLimitErrorCode
{ RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED' }) and replace the hardcoded
'RATE_LIMIT_EXCEEDED' in the rateLimitHandler (const rateLimitHandler:
Options['handler']) with the enum value
(RateLimitErrorCode.RATE_LIMIT_EXCEEDED); also update the OpenAPI/schema usage
to reference the same enum (via z.nativeEnum(RateLimitErrorCode)) so both the
handler and the schema share the single enum constant.
- Around line 20-27: The createLimiter function is missing an explicit return
type; change its signature to return the RateLimitRequestHandler type (the type
returned by rateLimit()), e.g. declare createLimiter(envKey: string, defaultMax:
number): RateLimitRequestHandler => ..., and ensure you import
RateLimitRequestHandler from the rate-limit package (the same module that
provides rateLimit) so the returned value from rateLimit(...) is correctly
typed; keep the body using rateLimit({ windowMs: WINDOW_MS, max:
parseEnvInt(envKey, defaultMax), standardHeaders: 'draft-7', legacyHeaders:
false, handler: rateLimitHandler }) unchanged.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (6)
packages/public-api/.env.examplepackages/public-api/package.jsonpackages/public-api/src/assets.tspackages/public-api/src/docs/openapi.tspackages/public-api/src/index.tspackages/public-api/src/middleware/rateLimit.ts
- Log getBaseAsset failures with structured context instead of silently swallowing - Make trust proxy configurable via TRUST_PROXY env var to prevent IP spoofing - Centralize RATE_LIMIT_EXCEEDED in a string enum shared by handler and OpenAPI schema - Add explicit RateLimitRequestHandler return type to createLimiter - Remove section comment violating no-comments guideline Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Description
Add tiered rate limiting to the public API using
express-rate-limitto protect expensive swap endpoints from abuse. A single client hitting/v1/swap/ratesfans out to 10+ external swapper protocols — without rate limiting, one abusive IP can exhaust external API quotas and degrade service for everyone.Four tiers (all configurable via env vars):
RATE_LIMIT_GLOBAL_MAX/v1/chains/*,/v1/assets/*RATE_LIMIT_DATA_MAXGET /v1/swap/ratesRATE_LIMIT_SWAP_RATES_MAXPOST /v1/swap/quoteRATE_LIMIT_SWAP_QUOTE_MAXWidget compatibility: The swap widget polls rates every 15s (4 req/min), well under the 60/min limit. 429 responses return immediately so the next poll cycle succeeds normally.
Key details:
trust proxyset to 1 for correct IP detection behind Railway's reverse proxydraft-7standard headers (RateLimit-Limit,RateLimit-Remaining,RateLimit-Reset) on every responseErrorResponseformat with codeRATE_LIMIT_EXCEEDEDBonus fix — resilient asset loading:
getBaseAsset()throws viaassertUnreachablefor any chain not yet inKnownChainIds. When new chains are added to the asset generation pipeline before being added toKnownChainIds, this crashed the entire server on startup. Nowassets.tscatches the error and includes unknown-chain assets with their existing data (without enrichment), so new chains no longer break the public API.Issue (if applicable)
Closes #11676
Risk
Low risk. This only affects the
public-apipackage (deployed independently on Railway). No changes to the main web app, swap widget, or any on-chain transaction logic. Rate limits use sensible defaults with generous headroom and are fully configurable via env vars without redeployment of code.None. This is server-side middleware only.
Testing
Engineering
cd packages/public-api && yarn build:bundle && yarn start:prod/v1/swap/rates— confirm 429 after 60 requests within 1 minute:{ "error": "...", "code": "RATE_LIMIT_EXCEEDED" }RateLimit-*headers on normal 200 responses:curl -i "http://localhost:3001/v1/chains"RATE_LIMIT_SWAP_RATES_MAX=5 yarn start:prodOperations
No user-facing UI changes. Rate limiting is transparent to normal usage patterns. Abusive clients will receive a 429 JSON response with a
Retry-Afterheader.Screenshots (if applicable)
N/A
Other
Also adds a
gh pr edit --bodyworkaround toCLAUDE.md— the GraphQL command fails on this repo due to deprecated Projects Classic fields, so we document the REST API alternative (gh api repos/.../pulls/<number> -X PATCH -F "body=@file").Summary by CodeRabbit
New Features
Bug Fixes