Imported from bedkillerspacex-boop/codex-skill-library (
javascript-rate-keys/SKILL.md). Install upstream withnpx skills add bedkillerspacex-boop/codex-skill-library --skill javascript-rate-keys. Copyright stays with the author.
Javascript Rate Keys
Scope And Authorization
- In scope: Key construction for limiters on APIs you own (Express, Fastify, Nest, API Gateway + Node, edge workers).
- Out of scope: Bypassing someone else’s rate limits; credential stuffing tooling.
- Test key isolation in staging with multiple synthetic identities.
- Redact real API keys from logs when debugging counters.
- Pair with
javascript-quota-designfor plan math;code-quality-standardsfor code.
When To Use
- Choosing what goes into the rate-limit key so fair use and abuse controls work.
- Fixing NAT/proxy pain (enterprise users share one IP) or multi-key abuse (one user many tokens).
- Preventing Redis key explosion (unbounded path/query in key).
- Dual limits: per-user and per-IP on login; per-tenant and global safety cap.
Do Not Use As Primary
| Need | Skill instead |
|---|---|
| Plan tiers / fair-use dimensions | javascript-quota-design |
| Client retry behavior | javascript-retry-policy |
| AuthZ permissions | javascript-rbac-design |
| Bot/CAPTCHA challenges | catalog captcha/bot skills |
| Implementation quality | code-quality-standards |
Key design principles
| Principle | Practice |
|---|---|
| Stable identity | Prefer authenticated tenant_id / api_key_id over raw IP |
| Low cardinality | Never put full URL, free-text, or unvalidated headers in keys |
| Hierarchical | global → tenant → user → route_class |
| Dual keys | Check strictest applicable limit (e.g. user AND IP) |
| Normalization | Lowercase scheme-independent route templates (/users/:id) |
| Versioning | Prefix rl:v1: so algorithm changes don’t collide |
Identity matrix
| Surface | Primary key material | Secondary |
|---|---|---|
| Public marketing API | IP + JA3/edge bot score (if available) | Route class |
| Login / password reset | IP + username hash (not raw if PII policy) | Global IP |
| Authenticated SaaS API | api_key_id or token.sub |
tenant_id |
| Browser session | session_id / user id |
IP soft limit |
| Webhooks inbound | Source IP allowlist first; then provider id | — |
| GraphQL | Operation name class + user | Complexity separately |
Workflow
1. Confirm scope and success criteria
- List endpoints and whether they are authenticated.
- Success: no cross-tenant counter sharing; cardinality under budget; dual-key login limits documented; bypass paths ticketed.
- Note trusted proxy config (
X-Forwarded-Forhop count).
2. Inventory current keys
rg -n "rateLimit|RateLimiter|incr\(|rl:|quota:" -g '!node_modules' -g '*.{js,ts}'
rg -n "X-Forwarded-For|req\.ip|request\.ip" -g '!node_modules'
3. Define route classes (not raw paths)
// Map concrete paths to stable classes — avoids /users/1 vs /users/2 key explosion
export function routeClass(method, pathTemplate) {
return `${method.toUpperCase()}:${pathTemplate}`; // e.g. GET:/v1/items/:id
}
// Express: use route path from layer when available, not req.url with query
export function clientIp(req, { trustedHops = 1 } = {}) {
// Only after correct proxy trust — misconfig = client spoof
return req.ip; // set app.set('trust proxy', trustedHops)
}
4. Build composite keys
/**
* Dual-key example for authenticated API
* Returns ordered keys to check (all must allow)
*/
export function rateKeys(ctx) {
const v = "rl:v1";
const keys = [];
keys.push(`${v}:global:${ctx.routeClass}`);
if (ctx.tenantId) keys.push(`${v}:tenant:${ctx.tenantId}:${ctx.routeClass}`);
if (ctx.apiKeyId) keys.push(`${v}:key:${ctx.apiKeyId}`);
else if (ctx.userId) keys.push(`${v}:user:${ctx.userId}:${ctx.routeClass}`);
else keys.push(`${v}:ip:${ctx.ip}:${ctx.routeClass}`);
return keys;
}
// Login: always dual IP + account fingerprint
export function loginKeys({ ip, usernameNorm }) {
const v = "rl:v1:login";
return [`${v}:ip:${ip}`, `${v}:user:${usernameNorm}`];
}
5. Anti-bypass and privacy
| Risk | Mitigation |
|---|---|
| XFF spoof | Trust proxy only for known LB hops |
| Key rotation storm | Limit on tenant_id not only api_key_id |
| Header injection | Never key on X-User-Id without auth verify |
| PII in Redis | Hash emails/usernames at rest in keys if required |
| IPv6 expansion | Canonicalize IP form |
6. Verify isolation and cardinality
npm run test:unit -- --grep rateKeys
# Staging: two API keys same tenant — confirm per-key and tenant caps
# Redis: SCAN rl:v1:* — watch unique key count under load test
Assertions:
- User A load does not decrement User B remaining.
- Unauthenticated flood hits IP keys; authenticated uses user keys.
- Query strings do not create unique keys per request.
7. Hand off
- Document key format in runbook for support (how to reset a bucket).
- TTL on every key; no immortal counters.
- Metrics:
rate_limit_keys_cardinality, denies by key tier.
Good / Bad
| Topic | Good | Bad |
|---|---|---|
| Auth API | tenant + api_key_id |
IP-only behind corporate NAT |
| Path | Route template class | Raw req.url with query |
| Login | Dual IP + user | User-only (no IP) or IP-only |
| Proxy | Explicit trust hops | Blind trust of X-Forwarded-For |
| Reset | Documented DEL pattern | Ops deletes random Redis keys |
Output Checklist
- Identity primary/secondary chosen per surface
- Route classes defined (no high-cardinality paths)
- Dual-key rules for login and global safety
- Trusted proxy / IP derivation correct
- Key version prefix and TTL policy
- Isolation tests for two tenants/keys
- Cardinality checked under load
- Privacy hashing if usernames in keys
- Support reset runbook
-
code-quality-standardsapplied
Rules
- Do not help evade third-party rate limits.
- Never trust client-supplied identity headers without authentication.
- Prefer authenticated stable IDs; use IP as secondary or anonymous primary only.
- Keep focus on key design; quota numbers live in
javascript-quota-design.