# No hardcoded test users seeded at production startup

- **Pattern:** `ab-000261` (`ai-slop-half-finished.hardcoded-test-data.hardcoded-test-users-in-seed`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000261
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000261)

## Why it matters

A seed script wired into `postinstall` or `scripts.start` runs on every production deployment and creates backdoor admin accounts with credentials that are publicly visible in your repository. Anyone who clones your repo — or reads your deploy logs — has the admin password. CWE-798 (Use of Hard-coded Credentials) and CWE-1188 (Insecure Default Initialization) both apply: the first deployment permanently plants known credentials in the live database, and subsequent deploys may re-upsert them if the seed is idempotent. Your security posture is now bounded by how broadly your repo can be read.

## Severity rationale

High because the first production deploy creates a live admin account with credentials visible in source control, giving repository readers direct admin access to the production system.

## Remediation

Move the seed command out of any lifecycle hook that runs in production (`postinstall`, `start`, `build`) and into an explicit dev-only script. In `package.json`:

```json
{
  "scripts": {
    "seed:dev": "tsx prisma/seed.ts",
    "postinstall": "prisma generate"
  }
}
```

Run `npm run seed:dev` manually in development only — never in CI deploy pipelines. If your staging environment needs seed data, use environment-specific seeding gated by a `SEED_DB=true` env var that is never set in production. Audit `prisma/seed.ts` (or equivalent) and remove any user records with plaintext passwords or well-known test emails.

## Detection

- **ID:** `hardcoded-test-users-in-seed`
- **Severity:** `high`
- **What to look for:** Walk seed files (`seed.{ts,js}`, `seeds/*`, `prisma/seed.{ts,js}`, `db/seed.{ts,js}`, `scripts/seed.{ts,js}`) AND any source file that runs at application startup (imported from `server.{ts,js}`, `app.{ts,js}`, `main.{ts,js}`, `index.{ts,js}` at the module level). Count all occurrences of creating user records with these exact email patterns: `test@example.com`, `admin@admin.com`, `user@test.com`, `john@doe.com`, `foo@bar.com`, `admin@localhost`, `test@test.com`, `demo@demo.com`. Also count hardcoded passwords: `'password'`, `'password123'`, `'123456'`, `'letmein'`, `'admin123'`, `'qwerty'`, `'test'`. Check whether the seed file is referenced by `package.json` `scripts` as a startup/postinstall step OR imported unconditionally from server entry points.
- **Pass criteria:** 0 hardcoded test users are seeded at production startup. Seed files that run only via explicit `npm run seed` commands are acceptable. Report: "Scanned X seed/startup files, 0 hardcoded test users found in production-startup paths."
- **Fail criteria:** At least 1 seed or startup file creates a user with hardcoded test credentials AND runs at production startup (unconditionally imported or referenced in `scripts.start` / `scripts.postinstall`).
- **Skip (N/A) when:** Project has no seed files AND no server entry point file.
- **Detail on fail:** `"1 hardcoded test user seeded at startup: prisma/seed.ts creates {email: 'admin@admin.com', password: 'password123', role: 'admin'} and is invoked by 'scripts.postinstall'"`
- **Remediation:** Seeding test users at production startup means the first deploy creates a backdoor admin account with known credentials. Gate seed scripts explicitly:

  ```json
  {
    "scripts": {
      "seed:dev": "tsx prisma/seed.ts",
      "postinstall": "prisma generate"
    }
  }
  ```

  Then run `npm run seed:dev` only in development. Never put seed commands in `postinstall`, `start`, or any production lifecycle script.

## External references

- cwe CWE-798
- cwe CWE-1188
- owasp A07

Taxons: placeholder-hygiene, access-control

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