Feature Flags Server-Verified
Why it matters
Hiding a UI button behind a client-side feature flag is not access control — it is presentation logic. The API endpoint behind that button remains callable by anyone who can craft a request. CWE-602 (Client-Side Enforcement of Server-Side Security) names this pattern precisely: security controls that exist only in client code are not controls at all. OWASP A01 covers the broader failure of missing server-side authorization. An attacker who skips the UI and calls the API directly will exercise the gated feature regardless of any client-side useFeatureFlag check.
Severity rationale
Low severity because feature-only gating typically exposes paid features to non-paying users rather than enabling data exfiltration, but the business and billing impact is real.
Remediation
Client-side flag checks are UX — add the same check server-side in the API route or Server Action that actually executes the gated capability. The server-side check is what counts.
// app/api/generate/route.ts
export async function POST(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
// Server-side enforcement — this is the real gate
const hasFeature = await checkFeatureFlag('ai-generation', session.user.id);
if (!hasFeature) return NextResponse.json({ error: 'Feature not enabled' }, { status: 403 });
// ... proceed with gated action
}
The client-side flag can remain for UX — it just must not be the only gate.
Detection
-
ID:
feature-flags-server-verified -
Severity:
low -
What to look for: Count all relevant instances and enumerate each. Look for feature flag usage — LaunchDarkly, PostHog feature flags, GrowthBook, or custom boolean flags in the database (
user.betaFeatures,org.plan). Specifically, check whether feature-gating logic exists in API routes and Server Actions, or only in client-side components. A client-sideuseFeatureFlag('ai-generation')without a corresponding server-side check is a fail. -
Pass criteria: Feature-gated capabilities have their access checks in the server-side handler (API route, Server Action, or server component) before returning gated data or performing gated actions. At least 1 implementation must be verified.
-
Fail criteria: Feature flag checks are present only in client-side components (hiding UI elements) with no corresponding API-level enforcement. The API endpoint will execute the action regardless of the flag if called directly.
-
Skip (N/A) when: No feature flag library or feature-gating pattern detected anywhere in the codebase.
-
Detail on fail:
"Feature flag checks found in client components but no corresponding server-side enforcement found in API routes or Server Actions for: [feature names]."(List the affected features.) -
Remediation: Client-side feature flag checks are UX-only — they improve the experience but are not security controls. Always enforce the same check server-side before acting on a gated request.
// app/api/generate/route.ts export async function POST(req: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); // Server-side feature enforcement — this is what matters const hasFeature = await checkFeatureFlag('ai-generation', session.user.id); if (!hasFeature) return NextResponse.json({ error: 'Feature not enabled' }, { status: 403 }); // ... proceed with action }
External references
- cwe · CWE-284 — Improper Access Control
- cwe · CWE-602 — Client-Side Enforcement of Server-Side Security
- owasp:2021 · A01
- nist:rev5 · AC-3
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-authorization·automated