---
ADR: 0021
Title: Environment configuration contracts and secret handling
Status: Implemented
Version: 0.3
Date: 2026-02-02
Supersedes: []
Superseded-by: []
Related: [ADR-0017, ADR-0015, ADR-0024, ADR-0025]
Tags: [security, configuration]
References:
- [Next.js environment variables](https://nextjs.org/docs/app/guides/environment-variables)
- [Zod](https://zod.dev/)
- [GitHub: Managing personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
- [Vercel REST API](https://vercel.com/docs/rest-api)
- [Vercel Sandbox authentication](https://vercel.com/docs/vercel-sandbox/concepts/authentication)
- [Vercel Sandbox local development](https://vercel.com/docs/vercel-sandbox/guides/local-development)
- [Neon API](https://neon.com/docs/api)
- [Upstash Developer API](https://upstash.com/docs/common/account/developerapi)
---
Implemented — 2026-01-30.
Updated — 2026-02-01 (implementation/deploy automation integrations).
Updated — 2026-02-02 (public auth UI env; client NEXT_PUBLIC_* exception).
Centralize environment variable access behind a single typed module, and enforce server-only secret handling.
All integrations are feature-gated: missing credentials disable a feature cleanly, without leaking secrets or breaking unrelated parts of the app.
ai-agent-builder integrates several providers (DB, Upstash, QStash, AI Gateway, Blob, Exa, Firecrawl, Sandbox, MCP). These integrations require secrets and configuration, and incorrect configuration should fail clearly without breaking optional features or leaking secrets to client bundles (Next.js environment variables Environment variables).
Implementation Runs add new integrations:
- GitHub (RepoOps)
- Vercel API (deployments + env var management)
- optional Neon API provisioning
- optional Upstash Developer API provisioning
- Avoid secret exposure to client bundles (Next.js environment variables Environment variables)
- Clear, typed configuration contracts (Zod v4 Zod v4)
- Feature-gated validation to keep optional features optional
- Consistent error behavior for Route Handlers and Server Actions
- Support optional automation integrations without forcing credentials
- FR-001: Managed authentication via Neon Auth.
- FR-023: Start a durable Implementation Run (plan → code → verify → deploy).
- FR-028: Create/configure a deployment target and set environment variables.
- FR-031: Enforce approval gates for side-effectful operations.
- NFR-001: Protect server-only keys and secrets.
- NFR-003: Strict TypeScript and modular configuration contracts.
- NFR-013: Least privilege for provider credentials and gated unsafe tools.
- PR-004: Runs complete despite client disconnects (feature gating avoids breaking unrelated paths).
- PR-006: CI completes within 10 minutes (p95) for typical PRs (avoid costly global env validation on import).
- IR-001: Model/embedding calls through Vercel AI Gateway.
- IR-002: Relational store is Neon Postgres.
- IR-004: Durable runs via Upstash QStash.
- IR-011: Repo operations via GitHub.
- IR-012: Deployments and env var management via Vercel API/SDK.
- IR-013: Optional provisioning via Neon API.
- IR-014: Optional provisioning via Upstash Developer API.
- A: Read
process.envdirectly everywhere- Pros: minimal upfront work.
- Cons: untyped, inconsistent errors, easy to leak secrets.
- B: Parse all env vars at module import time
- Pros: fail-fast at startup.
- Cons: optional features break builds/deploys; harder local iteration.
| Criterion | Weight | Score | Weighted |
|---|---|---|---|
| Solution leverage | 0.35 | 9.4 | 3.29 |
| Application value | 0.30 | 9.2 | 2.76 |
| Maintenance & cognitive load | 0.25 | 9.1 | 2.28 |
| Architectural adaptability | 0.10 | 9.2 | 0.92 |
Total: 9.25 / 10.0
We will adopt typed, feature-gated environment configuration contracts to
address secret handling and optional integrations. This involves using
Zod v4 schemas behind a server-only env boundary (src/lib/env.ts)
configured with per-integration feature gates and normalized errors.
src/lib/env.tsis the only place insrc/**server code that readsprocess.env(tests excluded).- Client Components may read
process.env.NEXT_PUBLIC_*values directly. src/lib/env.tsis server-only (server-only) and must not be imported from Client Components.- Each integration validates its required variables only when accessed ("feature gates").
- Misconfiguration fails at runtime on first usage with an
AppErrorand a clear message listing missing/invalid variables.
DATABASE_URL- Neon Auth:
NEON_AUTH_BASE_URLNEON_AUTH_COOKIE_SECRETNEON_AUTH_COOKIE_DOMAIN(optional)
- Auth UI (public):
NEXT_PUBLIC_AUTH_SOCIAL_PROVIDERS(optional)
- App access control:
AUTH_ACCESS_MODE(optional; defaults torestricted)AUTH_ALLOWED_EMAILS(required when restricted)
AI_GATEWAY_API_KEY(+ optionalAI_GATEWAY_BASE_URL)UPSTASH_REDIS_REST_URL,UPSTASH_REDIS_REST_TOKENUPSTASH_VECTOR_REST_URL,UPSTASH_VECTOR_REST_TOKENQSTASH_TOKEN,QSTASH_CURRENT_SIGNING_KEY,QSTASH_NEXT_SIGNING_KEYBLOB_READ_WRITE_TOKENEXA_API_KEY,FIRECRAWL_API_KEY,CONTEXT7_API_KEY
- GitHub:
GITHUB_TOKEN(fine-grained PAT recommended) - GitHub webhooks (optional):
GITHUB_WEBHOOK_SECRET - Vercel API:
VERCEL_TOKEN(and optionalVERCEL_TEAM_ID) - Vercel Sandbox (Code Mode):
VERCEL_OIDC_TOKEN(preferred)VERCEL_TOKEN+VERCEL_PROJECT_ID(access-token auth fallback)VERCEL_TEAM_ID(optional; used in both modes when applicable)
- Optional auto-provisioning:
- Neon:
NEON_API_KEY - Upstash Developer API (native accounts only):
UPSTASH_EMAIL,UPSTASH_API_KEY
- Neon:
- No secrets in client bundles.
- No env validation that fails builds for unused features.
- Client Components may read
process.env.NEXT_PUBLIC_*values directly; keep them non-secret and documented. - Tooling configs may still read env directly (e.g.
drizzle.config.ts).
flowchart LR
subgraph App[Next.js app runtime]
UI[UI / Route Handlers / Server Actions]
ENV[src/lib/env.ts]
ERR[AppError]
end
UI --> ENV
ENV -->|valid config| INTEG[Integration client]
ENV -->|missing/invalid| ERR
src/lib/env.tsdefines:- a single
process.envread boundary - per-feature Zod schemas that validate only when that feature is used
- normalization into stable config shapes (minimize downstream conditional logic)
- a single
- Server-only enforcement uses
server-onlyand must be upheld across all import paths. - Feature gates must return
null/undefined(or a typed discriminated union) for “not configured” integrations, and throw only when:- a code path explicitly requires that integration, and
- credentials are present but invalid (or required-for-that-path and missing)
- The canonical list of env var groups lives in this ADR under Environment variable groups and is mirrored in docs/ops/env.md.
.env.examplemust stay aligned with the feature gates so local development fails clearly and safely.
- Unit tests cover env feature gates and error normalization.
- CI enforces format/lint/typecheck/test/build.
- Added:
src/lib/env.tssrc/lib/core/errors.tssrc/lib/core/log.tssrc/lib/core/ids.tssrc/lib/core/time.tssrc/lib/upstash/redis.server.tssrc/lib/upstash/qstash.server.tssrc/lib/env.test.tssrc/lib/core/errors.test.tssrc/lib/core/log.test.tssrc/lib/core/ids.test.tssrc/lib/core/time.test.tssrc/lib/upstash/redis.server.test.tssrc/lib/upstash/qstash.server.test.ts- docs/ops/env.md
- Updated:
.env.examplevitest.config.ts
- Single import boundary for secrets reduces accidental leakage.
- Optional integrations remain optional (missing creds disable features cleanly).
- Typed, centralized config reduces duplicated error handling.
- Some misconfiguration shifts from “build-time fail-fast” to “first-use runtime error”, which must be surfaced clearly in UI and logs.
- Keep docs/ops/env.md and
.env.examplealigned with new feature gates. - Prefer adding small, isolated schemas per integration rather than one global “mega schema”.
- Added: server-only
- 0.1 (2026-01-30): Implemented typed env feature gates and server-only secret handling.
- 0.2 (2026-02-01): Documented implementation/deploy automation env feature gates (GitHub/Vercel/optional provisioning).
- 0.3 (2026-02-01): Documented public auth UI env; client
NEXT_PUBLIC_*exception (server secrets remain server-only).