# Hardcoded service identifiers are scoped to config files

- **Pattern:** `ab-000045` (`ai-slop-hallucinations.asset-references.hardcoded-service-ids-scoped`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000045
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000045)

## Why it matters

A Stripe price ID (`price_1AbC...`) hardcoded in an API route handler means the same literal string is duplicated across every checkout flow the AI generated. When the price changes — plan restructuring, annual pricing, regional variants — every scattered reference must be found and updated manually, with no guarantee they all match. CWE-798 applies because the same pattern that scatters price IDs also scatters webhook secrets (`whsec_*`), which must never appear as literals. OWASP A05 (Security Misconfiguration) covers the broader class: configuration that belongs in a single source of truth spread across the codebase is a maintenance and security misconfiguration by definition.

## Severity rationale

Medium because scattered price IDs create update-coherence failures and mask webhook secret leaks — the business impact is pricing drift and potential secret exposure rather than immediate data breach.

## Remediation

Centralize all Stripe identifiers in a single config file and import from there. Webhook secrets must always come from environment variables, never literals.

```ts
// lib/stripe.ts — single source of truth for all Stripe IDs
export const STRIPE_PRICE_IDS = {
  pro_monthly:  'price_1AbCdEfGhIjKlMnOp',
  pro_yearly:   'price_1XyZaBcDeFgHiJkLm',
} as const

export const STRIPE_PRODUCT_IDS = {
  pro: 'prod_AbCdEfGhIjKlMn',
} as const

// src/app/api/checkout/route.ts
import { STRIPE_PRICE_IDS } from '@/lib/stripe'

const session = await stripe.checkout.sessions.create({
  line_items: [{ price: STRIPE_PRICE_IDS.pro_monthly, quantity: 1 }],
})
```

For `whsec_*` values: remove the literal immediately and replace with `process.env.STRIPE_WEBHOOK_SECRET`. Webhook secrets in source code are a secret leak regardless of file location.

## Detection

- **ID:** `hardcoded-service-ids-scoped`
- **Severity:** `medium`
- **What to look for:** Walk source files for literal string matches against these service-identifier regex patterns: `arn:aws:[a-z0-9-]+:[a-z0-9-]*:[0-9]{12}:[\\w/-]+` (AWS ARN), `price_[A-Za-z0-9]{20,}` (Stripe price ID), `prod_[A-Za-z0-9]{14,}` (Stripe product ID), `whsec_[A-Za-z0-9]{20,}` (Stripe webhook secret PREFIX — flag presence even if not literal secret), `re_[A-Za-z0-9]{20,}` (Resend API key prefix), `ACa[a-f0-9]{32}` (Twilio Account SID). For each occurrence, record the file path. Then check whether the file matches one of these allowlisted "config-style" filename patterns: `(stripe|billing|plans|pricing|products|payments|subscriptions|tiers)\\.(ts|tsx|js|jsx|mjs|cjs)$`, `**/config/**`, `**/constants/**`, `**/lib/(stripe|billing|payments|aws)/**`, `*.example`, `*.test.*`, `*.spec.*`, `**/__tests__/**`, `**/__fixtures__/**`. Count all hardcoded service IDs found, total in allowlisted config files, total scattered in non-config files.
- **Pass criteria:** 0 hardcoded service identifiers exist outside of allowlisted config-style files. Report: "X service IDs found, Y in config files, 0 scattered."
- **Fail criteria:** At least 1 hardcoded service identifier appears in a non-config file (e.g., scattered across API route handlers, components, hooks).
- **Do NOT pass when:** A `whsec_*` (webhook secret) literal appears anywhere in source (even in config files) — webhook secrets must come from env vars, not literals. Flag separately with detail.
- **Skip (N/A) when:** Project has 0 hardcoded service identifiers at all.
- **Cross-reference:** For comprehensive secret detection including AWS keys and API tokens, the Environment Security audit (`environment-security`) covers credential hygiene.
- **Detail on fail:** `"2 scattered hardcoded service IDs: 'price_1AbC...' in src/app/api/checkout/route.ts (move to lib/stripe.ts), 'arn:aws:s3:::user-uploads' in src/components/Uploader.tsx (move to config/aws.ts)"`
- **Remediation:** Hardcoded service identifiers scattered across handlers indicate the AI didn't centralize configuration — the same ID appears in 5 different files, and updating it requires hunting all of them down. Centralize them:

  ```ts
  // lib/stripe.ts — single source of truth
  export const STRIPE_PRICE_IDS = {
    pro: 'price_1AbC...',
    team: 'price_1XyZ...',
  } as const

  // src/app/api/checkout/route.ts
  import { STRIPE_PRICE_IDS } from '@/lib/stripe'
  // use STRIPE_PRICE_IDS.pro instead of the literal
  ```

  For webhook secrets (`whsec_*`), never store them as literals — they must always be in environment variables.

## External references

- cwe CWE-798
- owasp A05

Taxons: code-quality, cryptography-and-secrets

HTML version: https://auditbuffet.com/patterns/ab-000045
