# Environment Variables Complete reference for all environment variables consumed by AgentIdP. Variables are loaded from a `.env` file at startup via `dotenv`. In production, inject them directly into the process environment — do not commit `.env` to version control. --- ## Required Variables These variables must be set. The server will throw and exit immediately if any are missing. ### `DATABASE_URL` PostgreSQL connection string. | | | |-|-| | **Required** | Yes | | **Format** | `postgresql://:@:/` | | **Example** | `postgresql://sentryagent:sentryagent@localhost:5432/sentryagent_idp` | The application uses `pg.Pool` with this connection string. Pool sizing is controlled by the optional `DB_POOL_*` variables documented below. --- ### `REDIS_URL` Redis connection URL. | | | |-|-| | **Required** | Yes | | **Format** | `redis://:` or `redis://:@:` | | **Example** | `redis://localhost:6379` | Used for token revocation, rate limiting, and monthly token counters. --- ### `JWT_PRIVATE_KEY` PEM-encoded RSA-2048 private key for signing JWT access tokens (RS256). | | | |-|-| | **Required** | Yes | | **Format** | PEM string, including `-----BEGIN RSA PRIVATE KEY-----` header and footer | | **Example** | See [Security guide](security.md) for key generation | In a `.env` file, use double quotes and encode newlines as `\n`: ``` JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEow...\n-----END RSA PRIVATE KEY-----" ``` Alternatively, read from a file at startup (see [Security guide](security.md)). --- ### `JWT_PUBLIC_KEY` PEM-encoded RSA-2048 public key for verifying JWT access tokens. | | | |-|-| | **Required** | Yes | | **Format** | PEM string, including `-----BEGIN PUBLIC KEY-----` header and footer | | **Example** | Derived from `JWT_PRIVATE_KEY` — see [Security guide](security.md) | Every authenticated request verifies the JWT signature using this key. If this key does not match the private key used to sign tokens, all authentication will fail. --- > **Note on Billing:** `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, and `STRIPE_PRICE_ID` are > required when `BILLING_ENABLED=true`. For local development, set `BILLING_ENABLED=false` and > use placeholder values. ## Optional Variables These variables have defaults and do not need to be set for local development. ### `VAULT_ADDR` HashiCorp Vault server address. **Required to enable Vault integration (Phase 2).** | | | |-|-| | **Required** | No (Vault is optional) | | **Format** | URL string | | **Example** | `VAULT_ADDR=http://127.0.0.1:8200` | When set alongside `VAULT_TOKEN`, new credentials are stored in Vault KV v2 instead of as bcrypt hashes in PostgreSQL. Existing bcrypt credentials continue to work unchanged until rotated. See [Vault setup guide](vault-setup.md). --- ### `VAULT_TOKEN` Vault authentication token. Required when `VAULT_ADDR` is set. | | | |-|-| | **Required** | Only when `VAULT_ADDR` is set | | **Format** | String | | **Example** | `VAULT_TOKEN=hvs.XXXXXXXXXXXXXXXXXXXXXX` | Use a Vault service token scoped to `read`, `write`, and `delete` on `{VAULT_MOUNT}/data/agentidp/*` and `{VAULT_MOUNT}/metadata/agentidp/*`. --- ### `VAULT_MOUNT` KV v2 secrets engine mount path. | | | |-|-| | **Required** | No | | **Default** | `secret` | | **Format** | String (no leading or trailing slash) | | **Example** | `VAULT_MOUNT=agentidp` | --- ### `BILLING_ENABLED` | | | |-|-| | **Required** | No | | **Default** | `false` | | **Values** | `true`, `false` | | **Example** | `BILLING_ENABLED=false` | Gates Stripe billing integration and free-tier agent limit enforcement. When `false`, no Stripe API calls are made and all tier limits are unenforced. Set to `false` for in-house testing. --- ### `STRIPE_SECRET_KEY` | | | |-|-| | **Required** | Only when `BILLING_ENABLED=true` | | **Format** | Stripe secret key string (`sk_live_*` or `sk_test_*`) | | **Example** | `STRIPE_SECRET_KEY=sk_test_placeholder` | Stripe API key used to create Checkout Sessions for tier upgrades. Never use a live key in development. --- ### `STRIPE_WEBHOOK_SECRET` | | | |-|-| | **Required** | Only when `BILLING_ENABLED=true` | | **Format** | Stripe webhook signing secret (`whsec_*`) | | **Example** | `STRIPE_WEBHOOK_SECRET=whsec_placeholder` | Used to verify the HMAC signature on incoming Stripe webhook events. Without this, the billing webhook endpoint will reject all events. --- ### `STRIPE_PRICE_ID` | | | |-|-| | **Required** | Only when `BILLING_ENABLED=true` | | **Format** | Stripe Price ID string (`price_*`) | | **Example** | `STRIPE_PRICE_ID=price_placeholder` | The Stripe Price object used when creating a Checkout Session for the Pro tier upgrade. --- ### `ANALYTICS_ENABLED` | | | |-|-| | **Required** | No | | **Default** | `true` | | **Values** | `true`, `false` | | **Example** | `ANALYTICS_ENABLED=true` | Feature flag that gates the `/api/v1/analytics/*` routes. When `false`, the analytics router is not mounted and all analytics endpoints return 404. Events are still recorded internally regardless of this flag. --- ### `TIER_ENFORCEMENT` | | | |-|-| | **Required** | No | | **Default** | `true` | | **Values** | `true`, `false` | | **Example** | `TIER_ENFORCEMENT=true` | Enables Redis-backed tier limit enforcement per tenant. When `true`, the `tierEnforcement` middleware checks daily API call and token counts against per-tier limits defined in `src/config/tiers.ts`. Enterprise tenants with `maxCallsPerDay: Infinity` bypass enforcement. When `false`, no tier limits are enforced. --- ### `COMPLIANCE_ENABLED` | | | |-|-| | **Required** | No | | **Default** | `true` | | **Values** | `true`, `false` | | **Example** | `COMPLIANCE_ENABLED=true` | Feature flag that gates the report and agent-card export endpoints under `/api/v1/compliance/*`. When `false`, those endpoints return 404. The SOC2 controls endpoint (`/api/v1/compliance/controls`) and audit chain verification (`/api/v1/audit/verify`) are always enabled regardless of this flag. --- ### `REDIS_RATE_LIMIT_ENABLED` | | | |-|-| | **Required** | No | | **Default** | `false` | | **Values** | `true`, `false` | | **Example** | `REDIS_RATE_LIMIT_ENABLED=true` | When `true`, rate limiting uses a Redis-backed sliding-window counter per `client_id`. When `false`, rate limiting uses an in-process `RateLimiterMemory` store (does not share state across multiple app instances). --- ### `RATE_LIMIT_WINDOW_MS` | | | |-|-| | **Required** | No | | **Default** | `60000` | | **Format** | Integer (milliseconds) | | **Example** | `RATE_LIMIT_WINDOW_MS=60000` | Duration of the sliding-window rate limit period in milliseconds. Only effective when `REDIS_RATE_LIMIT_ENABLED=true`. --- ### `RATE_LIMIT_MAX_REQUESTS` | | | |-|-| | **Required** | No | | **Default** | `100` | | **Format** | Integer | | **Example** | `RATE_LIMIT_MAX_REQUESTS=100` | Maximum number of requests allowed per `client_id` within `RATE_LIMIT_WINDOW_MS`. Requests exceeding this limit receive `429 RATE_LIMIT_EXCEEDED`. --- ### `DB_POOL_MAX` | | | |-|-| | **Required** | No | | **Default** | `20` | | **Format** | Integer | | **Example** | `DB_POOL_MAX=20` | Maximum number of PostgreSQL connections in the pool. Increase for high-throughput production deployments. Ensure your PostgreSQL instance's `max_connections` is set to at least `DB_POOL_MAX × number_of_app_instances + 5`. --- ### `DB_POOL_MIN` | | | |-|-| | **Required** | No | | **Default** | `2` | | **Format** | Integer | | **Example** | `DB_POOL_MIN=2` | Minimum number of idle connections kept alive in the pool. --- ### `DB_POOL_IDLE_TIMEOUT_MS` | | | |-|-| | **Required** | No | | **Default** | `30000` | | **Format** | Integer (milliseconds) | | **Example** | `DB_POOL_IDLE_TIMEOUT_MS=30000` | Milliseconds a connection can sit idle before being evicted from the pool. --- ### `DB_POOL_CONNECTION_TIMEOUT_MS` | | | |-|-| | **Required** | No | | **Default** | `5000` | | **Format** | Integer (milliseconds) | | **Example** | `DB_POOL_CONNECTION_TIMEOUT_MS=5000` | Milliseconds the pool waits for a connection to become available before throwing a connection timeout error. --- ### `VAULT_KV_MOUNT` | | | |-|-| | **Required** | No | | **Default** | `secret` | | **Format** | String (no leading or trailing slash) | | **Example** | `VAULT_KV_MOUNT=agentidp` | KV v2 secrets engine mount path used by `VaultService`. Equivalent to the existing `VAULT_MOUNT` variable — note that `.env.example` uses `VAULT_KV_MOUNT`; the underlying service reads either. --- ### `OPA_URL` | | | |-|-| | **Required** | No | | **Format** | URL string | | **Example** | `OPA_URL=http://localhost:8181` | URL of a running OPA server for external policy evaluation. When unset, the application falls back to the embedded Wasm or JSON policy in `POLICY_DIR`. Used for health check reporting. --- ### `KAFKA_BROKERS` | | | |-|-| | **Required** | No | | **Format** | Comma-separated broker addresses | | **Example** | `KAFKA_BROKERS=localhost:9092` | When set, the `KafkaAdapter` publishes domain events to Kafka. When unset, Kafka publishing is disabled and events are only delivered via the `WebhookService`. --- ### `ENFORCE_TLS` | | | |-|-| | **Required** | No | | **Default** | `false` | | **Values** | `true`, `false` | | **Example** | `ENFORCE_TLS=true` | When `true`, the `tlsEnforcementMiddleware` redirects all HTTP requests to HTTPS. Enable in production deployments where TLS termination is handled at the application layer. --- ### `POLICY_DIR` Directory containing OPA policy files (`authz.rego`, `authz.wasm`, `data/scopes.json`). | | | |-|-| | **Required** | No | | **Default** | `/policies` | | **Format** | Absolute or relative directory path | | **Example** | `POLICY_DIR=/etc/sentryagent/policies` | At startup the OPA authorization middleware loads `${POLICY_DIR}/authz.wasm` (Wasm mode) if present; otherwise it loads `${POLICY_DIR}/data/scopes.json` (fallback mode). Send `SIGHUP` to the process to hot-reload the policy files without a restart. --- ### `PORT` HTTP port the Express server listens on. | | | |-|-| | **Required** | No | | **Default** | `3000` | | **Format** | Integer | | **Example** | `PORT=8080` | --- ### `NODE_ENV` Node.js environment flag. | | | |-|-| | **Required** | No | | **Default** | `undefined` (treated as development) | | **Values** | `development`, `test`, `production` | | **Example** | `NODE_ENV=production` | Effect: When `NODE_ENV=test`, HTTP request logging (Morgan) is disabled. --- ### `CORS_ORIGIN` Allowed origin(s) for Cross-Origin Resource Sharing. | | | |-|-| | **Required** | No | | **Default** | `*` (all origins) | | **Format** | URL string or `*` | | **Example** | `CORS_ORIGIN=https://app.mycompany.ai` | In production, set this to the specific origin(s) that should be permitted to call the API. The default `*` is acceptable for a public API but restricts cookie-based auth flows (not applicable here — Bearer tokens only). --- ## Complete `.env` Example ``` # ── Server ────────────────────────────────────────────────────────────────── NODE_ENV=development PORT=3000 CORS_ORIGIN=http://localhost:3001 # ── Database ───────────────────────────────────────────────────────────────── DATABASE_URL=postgresql://sentryagent:sentryagent@localhost:5432/sentryagent_idp DB_POOL_MAX=20 DB_POOL_MIN=2 DB_POOL_IDLE_TIMEOUT_MS=30000 DB_POOL_CONNECTION_TIMEOUT_MS=5000 # ── Redis ──────────────────────────────────────────────────────────────────── REDIS_URL=redis://localhost:6379 REDIS_RATE_LIMIT_ENABLED=true RATE_LIMIT_WINDOW_MS=60000 RATE_LIMIT_MAX_REQUESTS=100 # ── JWT Keys (generate with openssl — see docs/devops/security.md) ────────── JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEow...\n-----END RSA PRIVATE KEY-----" JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIj...\n-----END PUBLIC KEY-----" # ── Billing (Stripe) — set BILLING_ENABLED=false for local/in-house testing ─ BILLING_ENABLED=false STRIPE_SECRET_KEY=sk_test_placeholder STRIPE_WEBHOOK_SECRET=whsec_placeholder STRIPE_PRICE_ID=price_placeholder # ── Phase 6 Feature Flags ───────────────────────────────────────────────────── ANALYTICS_ENABLED=true TIER_ENFORCEMENT=true COMPLIANCE_ENABLED=true # ── HashiCorp Vault (optional) ──────────────────────────────────────────────── # VAULT_ADDR=http://127.0.0.1:8200 # VAULT_TOKEN=hvs.XXXXXXXXXXXXXXXXXXXXXX # VAULT_KV_MOUNT=secret # ── OPA (optional) ─────────────────────────────────────────────────────────── # POLICY_DIR=/etc/sentryagent/policies # OPA_URL=http://localhost:8181 # ── Kafka (optional) ───────────────────────────────────────────────────────── # KAFKA_BROKERS=localhost:9092 # ── TLS ────────────────────────────────────────────────────────────────────── # ENFORCE_TLS=true ``` > Do not commit `.env` to version control. Add it to `.gitignore`. --- ## Variable Validation at Startup The application validates required variables at startup in this order: 1. `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` — checked in `createApp()` before the server starts 2. `DATABASE_URL` — checked when `getPool()` is first called (during `createApp()`) 3. `REDIS_URL` — checked when `getRedisClient()` is first called (during `createApp()`) If any required variable is missing, the process exits with an error before binding to any port. > **Feature flags** (`BILLING_ENABLED`, `ANALYTICS_ENABLED`, `TIER_ENFORCEMENT`, > `COMPLIANCE_ENABLED`) are read at startup. `ANALYTICS_ENABLED` and `COMPLIANCE_ENABLED` > determine whether their respective routers are mounted — changing these values requires a > process restart.