F1.7 — Environment Variables Management
What This Feature Does
F1.7 introduces project-scoped environment variables management in Vexlyx.
- Authenticated Encryption at Rest: All variable values are encrypted with AES-256-GCM using a 32-byte key derived from
ENCRYPTION_KEYorSESSION_SECRET. Plaintext secrets are never stored in the database. - Masked By Default: Variable listing endpoints and dashboard views mask values with
••••••••to prevent shoulder-surfing and accidental disclosure. Authorized project owners can toggle and reveal or copy individual values on demand. - Container Runtime Injection: During deployment (both via manual deploy and automated Nixpacks build pipeline), project environment variables are decrypted in-memory and injected directly into the project's generated
docker-compose.ymlservice block. - Key-Value Editor & .env Importer/Exporter: A dashboard UI supporting single variable addition, inline editing, search/filter, delete confirmations, .env export, and bulk
.envraw text import (with comments and multiline value support).
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Next.js Dashboard │
│ • EnvVarEditor (Key-Value Table, Search, Quick Add) │
│ • Import .env Modal (Raw parser, overwrite/merge) │
│ • On-demand decrypt/reveal toggle │
└──────────────────────────────┬──────────────────────────────┘
│ REST (Session Cookie)
▼
┌─────────────────────────────────────────────────────────────┐
│ Fastify API │
│ • modules/env/routes.ts │
│ • modules/env/service.ts (list, reveal, upsert, import) │
│ • utils/encryption.ts (AES-256-GCM encrypt/decrypt) │
└──────────────┬───────────────────────────────┬──────────────┘
│ │
SQL (Prisma) Deploy / Build Service
│ │
▼ ▼
┌──────────────────────────────┐ ┌─────────────────────────────┐
│ PostgreSQL (env_vars) │ │ system/python/ │
│ • id, projectId, key │ │ docker_manager.py │
│ • value: iv:tag:ciphertext │ │ • injects into compose │
└──────────────────────────────┘ └─────────────────────────────┘Security & Encryption Specification
- Algorithm:
aes-256-gcm(Galois/Counter Mode with Authenticated Encryption). - IV (Initialization Vector): 12 bytes (
96 bits) cryptographically random buffer generated per encryption vianode:crypto.randomBytes(12). - Auth Tag: 16 bytes (
128 bits) generated by GCM to guarantee ciphertext authenticity and prevent tampering. - Storage Format: Stored in the
valuecolumn as colon-delimited hex strings:<iv_hex>:<auth_tag_hex>:<cipher_text_hex> - Key Derivation: SHA-256 digest of
ENCRYPTION_KEY(orSESSION_SECRETfallback) producing a 256-bit key.
Key Files
| File | Role |
|---|---|
packages/shared/src/schemas/env.ts | Zod validation schemas (SetEnvVarSchema, BulkSetEnvVarsSchema, ImportEnvFileSchema) |
packages/shared/src/types/index.ts | Public EnvVar and DecryptedEnvVar TypeScript types |
apps/api/src/utils/encryption.ts | AES-256-GCM encryption, decryption, and authentication tag validation |
apps/api/src/modules/env/service.ts | Business logic for list (masked), reveal (decrypted), upsert, bulk upsert, delete, .env parser |
apps/api/src/modules/env/routes.ts | REST endpoints mounted at /api/projects/:id/env |
apps/api/src/modules/deploy/service.ts | Injects decrypted env map into runDockerDeploy |
apps/api/src/modules/build/service.ts | Injects decrypted env map into Nixpacks Phase 3 container deployment |
apps/dashboard/src/hooks/useEnvVars.ts | React state management hook for environment variables |
apps/dashboard/src/components/projects/EnvVarEditor.tsx | Interactive UI table, quick add, live reveal, search, edit, delete, and import modal |
API Endpoints
1. GET /api/projects/:id/env
- Auth: Required (Owner only)
- Response:
{ variables: EnvVar[] }wheremaskedValueis"••••••••".
2. GET /api/projects/:id/env/:key/reveal
- Auth: Required (Owner only)
- Response:
{ id, key, value, createdAt, updatedAt }with plain decryptedvalue.
3. POST /api/projects/:id/env
- Auth: Required (Owner only)
- Body: Single
{ key: string, value: string }OR bulk{ variables: Array<{ key: string, value: string }> }. - Response: Upserted record or
{ count, variables }.
4. POST /api/projects/:id/env/import
- Auth: Required (Owner only)
- Body:
{ content: string, overwrite?: boolean } - Response:
{ count, variables }
5. DELETE /api/projects/:id/env/:key
- Auth: Required (Owner only)
- Response:
{ message: string }
How to Test
1. Add Environment Variable
- Open any project detail page
/projects/:idin the dashboard. - Scroll to the Environment Variables section.
- Enter
KEY_NAMEandValue...in the quick add form. - Click Add.
- Inspect the list: the new key appears with value masked as
••••••••. - Inspect the database via
pnpm db:studio: verifyvalueinenv_varstable is stored as an encrypted hex string (iv:tag:cipher) and not plaintext.
2. Reveal and Copy Value
- Click the Eye icon on any variable row.
- The decrypted plaintext value is retrieved and displayed.
- Click the Copy icon next to the value to copy it to clipboard.
- Click the EyeOff icon to re-mask the value.
3. Import .env File
- Click Import .env button in the header.
- Paste raw
.envtext containing multiple variables, comments (#), and quoted values:dotenv# Database connection DATABASE_URL="postgres://admin:secret@localhost:5432/myapp" PORT=8000 API_KEY="sk_live_998877" - Click Import Variables.
- Confirm all 3 variables are parsed, encrypted, and listed.
4. Export .env File
- Click Export in the header.
- An
.env.<projectId>file downloads containing all decrypted variables properly quoted.
5. Container Deployment Injection
- Deploy the project via the Deploy button.
- Check
workspaces/projects/:id/deploy/docker-compose.yml. - Verify all decrypted variables are formatted under the
environment:section of the service.