PensionsPortal.ie enforces a strict no-secrets-in-code policy. All sensitive values are managed through Vercel environment variables and are never committed to the repository.
Principles
- No hardcoded secrets — the codebase contains zero plaintext secrets, keys, or credentials
.env.example for documentation only — placeholder values show the expected format; real values are never in .gitignore-excluded files committed to CI artefacts
- Principle of least privilege — each secret is used by exactly one component; no shared credentials
- Rotation without deployment — Vercel environment variables can be rotated without code changes (except
AUTH_SECRET, which invalidates all sessions on rotation)
Required Environment Variables
| Variable | Component | Notes |
|---|
DATABASE_URL | Drizzle ORM / Neon | Pooled connection string. Rotated via Neon console. |
DATABASE_URL_UNPOOLED | Drizzle migrations only | Direct connection for drizzle-kit migrate. Not used at runtime. |
AUTH_SECRET | Auth.js | Signs JWTs. Rotation invalidates all active sessions. Generate: openssl rand -hex 32 |
AUTH_URL | Auth.js | Canonical app URL for callback construction |
PPS_ENCRYPTION_KEY | Member PPS encryption | 64-character hex string (32 bytes / 256-bit AES key). Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" |
ANTHROPIC_API_KEY | AI features | Scoped to EU inference via ANTHROPIC_INFERENCE_GEO=EU |
RESEND_API_KEY | Email (Resend) | Used for password reset and notification emails |
RAG_INGEST_SECRET | RAG corpus ingest | Bearer token protecting POST /api/rag/ingest |
SENTRY_AUTH_TOKEN | Sentry source maps | Upload-only token for CI. Not present at runtime. |
Vercel Environment Variable Management
Secrets are stored in Vercel project settings under Environment Variables. Vercel:
- Encrypts secrets at rest
- Injects them into the build and runtime environment
- Does not expose them in build logs (values are masked)
- Supports per-environment overrides (production / preview / development)
Preview deployments on Vercel inherit environment variables. Review access controls on preview deployments to ensure that preview environments do not expose production secrets to unauthorised parties.
PPS Encryption Key
The PPS_ENCRYPTION_KEY requires special handling:
- Must be a 64-character hex string (32 bytes = 256-bit AES-256 key)
- Generated once and stored only in Vercel environment variables
- If lost, all stored PPS numbers become unrecoverable without the original key
- Rotation requires re-encrypting all stored PPS numbers — a planned maintenance procedure, not a hotfix
# Generate a new PPS_ENCRYPTION_KEY
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
AUTH_SECRET Rotation
Rotating AUTH_SECRET:
- Generate a new secret:
openssl rand -hex 32
- Update the Vercel environment variable
- Trigger a redeployment
- All existing user sessions are immediately invalidated — users must log in again
Plan rotation during low-traffic periods and communicate to users in advance.
What Must Never Appear in Code or Logs
- Database connection strings (including usernames and passwords)
AUTH_SECRET value
PPS_ENCRYPTION_KEY value
ANTHROPIC_API_KEY
RESEND_API_KEY
- Any bearer token or API key
The security test suite (src/tests/api/security.test.ts) verifies that connection strings and env var names are never exposed in API error responses.
Developer Workflow
Developers use a local .env.local file (gitignored) for development secrets. This file is never committed. The .env.example file in the repository contains safe placeholder values and serves as documentation.