Security

Secrets management

Control 6.7.1 and the OWASP Secrets Management Cheat Sheet — dedicated store, no hardcoded or Git-committed secrets, least privilege, rotation and revocation, access monitoring, and immediate replacement of a compromised secret.

Last updated: 28 August 2026

1. Access-control policy

  • Server secrets live only as Vercel project environment variables for this project, scoped to Production and Preview. They are marked Sensitive. Values are hidden in the dashboard after save (lock / Sensitive). They are not NEXT_PUBLIC_ and are never bundled to the browser.
  • Only Vercel project members with environment-variable permission can view or edit them. There is no in-app admin console that displays secret values.
  • Application source does not contain live secrets. Configuration is injected at deploy by the Vercel runtime.
  • Per-user Google refresh tokens are readable only by the server process that holds TOKEN_ENCRYPTION_KEY. End users, browsers, and client JavaScript cannot request the ciphertext or the key.
  • Decryption happens only on the server, only to call the Gmail API or to revoke the token when a mailbox or account is deleted.
  • Application logs must not record secret values, session tokens, card data, or raw refresh tokens (see also 6.5.1).

2. Cryptographic protection

Platform secrets (API keys, AUTH_SECRET, TOKEN_ENCRYPTION_KEY, database URL, billing keys) are not copied into the application database. Vercel stores project environment variables encrypted at rest and injects them into the serverless runtime. That is the storage mechanism for those secrets.

Application secrets that must persist (Google refresh tokens) are encrypted in the application with AES-256-GCM before they are written to Postgres:

  • Algorithm: AES-256-GCM (Node.js crypto.createCipheriv)
  • Key: 32-byte value from TOKEN_ENCRYPTION_KEY (64 hex chars)
  • IV: 12 random bytes per encryption
  • Stored form: iv.ciphertext.authTag (base64 parts)
  • Tampered ciphertext fails closed (auth-tag verification throws; no plaintext)

Session tokens are encrypted JWEs using a key derived from AUTH_SECRET (alg=dir, enc=A128CBC-HS256). Short-lived Google access tokens are never stored.

3. Secret inventory

SecretClassStoreProtection
AUTH_SECRETPlatform secretVercel env (Sensitive)Used to encrypt/sign session JWEs (dir / A128CBC-HS256). Never written to the database or the browser.
TOKEN_ENCRYPTION_KEYPlatform secretVercel env (Sensitive)32-byte AES-256 key (hex). Unlocks stored Google refresh tokens only. Never logged.
GOOGLE_CLIENT_SECRETPlatform secretVercel env (Sensitive)OAuth client secret. Server runtime only; used to exchange authorization codes and refresh access tokens.
OPENAI_API_KEYPlatform secretVercel env (Sensitive)Provider API key. Read from process.env on the server. Never sent to the browser or stored in Postgres.
DATABASE_URLPlatform secretVercel env (Sensitive)Postgres connection string (Supabase). Server-only. TLS to the database.
RESEND_API_KEY, Inngest keys, Stripe / Paddle / cron / Pub/Sub secretsPlatform secretVercel env (Sensitive)Same control: env-only, not NEXT_PUBLIC_, not in git, not in client bundles.
Google refresh token (per mailbox)Application secretPostgres column refresh_token_encAES-256-GCM before INSERT/UPDATE. Format iv.ciphertext.authTag. Decrypted in memory only for Gmail API or revoke.
Google access tokenEphemeralNot storedMinted in process memory by the Google client from the refresh token. Never persisted, never sent to the browser.
Session cookieSession secretHttpOnly Secure cookieEncrypted JWE bound to AUTH_SECRET. maxAge 24 hours. Cleared on sign-out.

4. OWASP mapping

OWASP requirementHow Inbox Wingman implements it
Dedicated secrets-management solutionVercel project Environment Variables (Sensitive) is the dedicated store for platform secrets. They are encrypted at rest by Vercel and injected only into the serverless runtime. TLS certificates are managed by Vercel, not stored in the app or the database.
Do not hardcode secretsApplication code reads process.env.* only. Repo search finds no live API keys, passwords, or private keys. Placeholders such as sk-placeholder are not production credentials.
Do not commit secrets to Git.gitignore ignores .env* (except .env.example with dummy names). Local env files are not in version control.
Least privilegeEnv access is limited to Vercel project members who can edit Environment Variables. Production and Preview are separate. The app never exposes secret values in HTML, APIs, or admin UI. Token decrypt is server-only for Gmail API or revoke.
Rotation and revocationPlatform secrets are rotated by replacing the Vercel env value and redeploying. Google refresh tokens are revoked via Google's revoke endpoint when a mailbox is disconnected or the account is deleted, then the ciphertext row is removed. Session JWEs expire in 24 hours; changing AUTH_SECRET invalidates all sessions.
Monitor accessVercel records who added or updated each variable and when. Project activity retains deploys and env changes. The app logs secret.encrypt / secret.decrypt (kind, purpose, ok, time) without secret values.
Replace exposed secrets immediatelyOn suspected exposure: rotate the Vercel variable, revoke the provider key (Google Cloud OAuth client, OpenAI, Resend, Stripe/Paddle), redeploy, and revoke user Gmail grants if a refresh-token key is involved. Old values are not kept in the app.

5. Logging and monitoring

  • Platform. Each Vercel environment variable shows Added / Updated timestamps and the actor. Deployments and environment changes appear in Vercel project activity. Members review that history; values stay hidden.
  • Application. Encrypt and decrypt of stored Google refresh tokens emit a structured log event secret.encrypt / secret.decrypt with kind, purpose (gmail.persist, gmail.api, gmail.revoke), ok, and at. Ciphertext, plaintext, IVs, and keys are omitted. Events go to Vercel Runtime Logs and are retained with the project log stream.
  • Failed decrypts (wrong key or tampered payload) are logged with ok: false and throw; the request fails closed.

6. Rotation, revocation, incident

  1. Generate a new value at the provider (or openssl rand -hex 32 for TOKEN_ENCRYPTION_KEY / AUTH_SECRET).
  2. Paste it into Vercel → Project → Settings → Environment Variables as Sensitive, Production and Preview. Save. The previous value is replaced, not shown again.
  3. Redeploy Production so every instance picks up the new value.
  4. Revoke the old provider credential. Disconnecting Gmail or deleting an account calls Google revoke and deletes refresh_token_enc.
  5. If TOKEN_ENCRYPTION_KEY itself is rotated, existing ciphertext cannot be read; users reconnect Gmail (new encrypted refresh token). If AUTH_SECRET is rotated, every session cookie is invalid and users sign in again.

7. What this control does not do

  • No hardcoded production secrets in source or in Git.
  • No NEXT_PUBLIC_ secret variables and no secret values in HTML or client JS.
  • No admin screen that displays env values or decrypted refresh tokens.
  • Logs never include ciphertext, plaintext, IVs, keys, session tokens, or card data.

Related: Security, Privacy Policy, DPA.