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.
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.
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.
ID: saas-authorization.access-control.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-side useFeatureFlag('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
}