A single global rate limit applied identically to free users, paid users, and admins means the limit must be conservative enough for the most restricted tier — which throttles legitimate premium users — or generous enough for heavy users — which provides no abuse protection for the free tier. CWE-284 (Improper Access Control) covers the failure to differentiate resource access by privilege level. OWASP A01 (Broken Access Control) and NIST 800-53 AC-6 (Least Privilege) require that access controls reflect the actual entitlement model, which for API rate limits means tier-aware limits that match the product's promise to each user segment.
Low because absent role-differentiated limits degrades product experience and cost controls rather than enabling direct data exfiltration.
Implement tiered rate limits keyed by the user's subscription plan. Free, pro, and admin tiers each get limits appropriate to their entitlement.
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
const limits = {
free: new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(50, '1 h') }),
pro: new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(500, '1 h') }),
admin: new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(5000, '1 h') }),
};
export async function applyRateLimit(userId: string, plan: 'free' | 'pro' | 'admin') {
const { success } = await limits[plan].limit(userId);
return success;
}
Derive the plan from the authenticated session — never from a user-supplied header or query parameter.
ID: saas-authorization.api-auth.api-rate-limit-per-role
Severity: low
What to look for: Count all relevant instances and enumerate each. Look for rate limiting implementation — Upstash Rate Limit, @upstash/ratelimit, express-rate-limit, or custom rate limiting logic. If rate limiting exists, check whether the limits vary based on user role or subscription plan. A single global limit applied identically to free and paid users, or to users and admins, is a partial fail.
Pass criteria: Rate limits are implemented and differentiated based on user role or subscription tier (e. At least 1 implementation must be verified.g., free users: 50 requests/hour, paid users: 500 requests/hour, admin users: unlimited or higher limit).
Fail criteria: No rate limiting is implemented at all, or rate limiting is implemented with a single fixed limit regardless of user role, subscription tier, or trust level.
Skip (N/A) when: Project is a purely static site with no API routes, or project is in early development with no subscription/role concept yet established.
Detail on fail: "Rate limiting is absent or uses a single global limit regardless of user role. Premium users cannot be given higher limits, and abuse is not differentiated from normal use." (Note whether rate limiting is absent entirely or present but untired.)
Remediation: Implement tiered rate limits based on the user's subscription plan or role.
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
const limits = {
free: new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(50, '1 h') }),
pro: new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(500, '1 h') }),
admin: new Ratelimit({ redis, limiter: Ratelimit.slidingWindow(5000, '1 h') }),
};
export async function applyRateLimit(userId: string, plan: 'free' | 'pro' | 'admin') {
const { success } = await limits[plan].limit(userId);
return success;
}
Cross-reference: For related patterns and deeper analysis, see the corresponding checks in other AuditBuffet audits covering this domain.