When the feature list on your pricing page diverges from backend enforcement, users pay for access that doesn't exist or get features they shouldn't have. OWASP A01 (Broken Access Control) and CWE-284 (Improper Access Control) cover the enforcement gaps. Beyond security, pricing-backend mismatches erode customer trust when a paid feature is silently accessible to free users — competitors can exploit this, and paying customers feel deceived when they discover the paywall was optional.
High because features advertised as paid-only but unenforced in the backend allow free users to exploit premium capabilities, and features listed but not delivered constitute misrepresentation.
Centralize plan configuration in a single lib/plans.ts that both the pricing page and backend checks import. This eliminates drift between what you advertise and what you enforce.
// lib/plans.ts — single source of truth for both pricing UI and API enforcement
export const PLANS = {
free: {
name: 'Free',
features: ['basic_analytics', 'up_to_3_projects'] as const,
stripePriceId: null,
},
pro: {
name: 'Pro',
features: ['advanced_analytics', 'unlimited_projects', 'csv_export'] as const,
stripePriceId: process.env.STRIPE_PRO_PRICE_ID,
},
} as const
// In API routes:
// if (!PLANS[user.plan].features.includes('csv_export')) return 402
For each feature listed in the pricing page, verify a server-side check exists by searching for the feature key in app/api/ and middleware.ts.
ID: saas-billing.pricing-enforcement.pricing-page-matches-backend
Severity: high
What to look for: Compare the pricing page or pricing component against the actual backend enforcement logic. Look for plan feature lists in the pricing page (often a constants file like lib/plans.ts, config/pricing.ts, or hardcoded in the pricing component). Compare those feature lists against the actual server-side checks in API routes and middleware. Quote the actual feature names from the pricing page and verify each has a server-side gate. Look for discrepancies: features listed as "Pro only" on the pricing page that are actually served to all authenticated users, or features listed as available on a plan that the backend checks don't enforce for that plan tier.
Pass criteria: Enumerate all features listed on the pricing page. At least 1 feature must have enforcement. Every feature listed as plan-specific has a corresponding server-side access check that enforces that restriction. The plan names, feature names, and price IDs referenced in the pricing page match those used in the backend logic. No feature listed as paid-only is freely accessible via API.
Fail criteria: Features listed as paid-only on the pricing page are not enforced in the backend. Plan names or feature flags in the pricing component are inconsistent with those used in server-side checks. A significant feature is absent from enforcement logic.
Skip (N/A) when: No pricing page or pricing differentiation detected — the application has a single flat plan.
Detail on fail: "Pricing page lists 'Advanced Analytics' as Pro feature but /api/analytics/advanced has no plan check" or "Pricing component references plan IDs 'basic'/'pro' but backend checks use 'free'/'premium' — mismatch may cause enforcement gaps"
Remediation: Centralize your plan configuration in a single source of truth:
// lib/plans.ts — used by BOTH the pricing page and backend checks
export const PLANS = {
free: {
name: 'Free',
features: ['up_to_3_projects', 'basic_analytics'],
stripePriceId: null,
},
pro: {
name: 'Pro',
features: ['unlimited_projects', 'advanced_analytics', 'exports'],
stripePriceId: process.env.STRIPE_PRO_PRICE_ID,
},
} as const
export type PlanFeature = typeof PLANS[keyof typeof PLANS]['features'][number]
// Then in API routes:
// if (!PLANS[user.plan].features.includes('advanced_analytics')) return 402
This ensures the pricing page and backend always reference the same feature list.