# Configuration for the control plane running as a Node process. Copy to # `.env` and fill it in; `.env` is gitignored. The host reads its process # environment, so any loader works: # node --env-file=.env packages/control-plane/dist/node/main.js # The compose stack (docker-compose.yml) reads it as the app's env_file. # # The deployment variables have the same names and meanings on Cloudflare, # where they are Worker variables and secrets. Each line names its source # there: a `terraform.tfvars` entry (var.…), a value Terraform computes or # generates (local.…, random_password.…, a module output), or "not set by # Terraform". The Node-only settings at the top have no Cloudflare # counterpart. An empty value counts as unset. # # On AWS the container's environment is materialized from SSM Parameter Store # by the deploy step; nothing in this file is baked into the image. # # `packages/control-plane/src/node/env-example.test.ts` checks that this file # names every variable the host reads and nothing else, and that the keys the # host requires at boot are exactly the ones marked "Required.". # --------------------------------------------------------------------------- # Node host (no Cloudflare equivalent: the platform provides these there) # --------------------------------------------------------------------------- # Interface and port to listen on. 8787 matches the Worker's local dev port. HOST=0.0.0.0 PORT=8787 # Directory holding the global store (global.db), the per-session files # (sessions/.db) and the host alarm index. A volume in the container. DATA_DIR=/data # The D1 migration files, applied to the global store at boot. The image sets # this to its own copy; leave empty to use terraform/d1/migrations from a # repository checkout. MIGRATIONS_DIR= # How long a shutdown waits for in-flight work before the process is forced # down. The compose stack pins this together with its stop grace period. SHUTDOWN_TIMEOUT_MS=30000 # --------------------------------------------------------------------------- # Object storage for media (Cloudflare: the MEDIA_BUCKET R2 binding) # --------------------------------------------------------------------------- # Any S3-compatible bucket. The compose stack points these at its object store. OBJECT_STORE_BUCKET=media OBJECT_STORE_REGION=us-east-1 # Leave empty for AWS S3. Other S3-compatible services need it. OBJECT_STORE_ENDPOINT=http://object-store:9000 # Plain-http endpoints are refused unless this is "true" (local object store only). OBJECT_STORE_ALLOW_HTTP=true # "true" for the local object store (bucket in the path, not the hostname). OBJECT_STORE_FORCE_PATH_STYLE=true # Credentials through the AWS SDK's default chain. Leave both empty on EC2 to # use the instance role. For the compose stack they are the object store's # root user and password (OBJECT_STORE_ROOT_USER / OBJECT_STORE_ROOT_PASSWORD # below). AWS_ACCESS_KEY_ID=objectstoreadmin AWS_SECRET_ACCESS_KEY= # Only with temporary (STS) credentials; empty for static keys or a role. AWS_SESSION_TOKEN= # --------------------------------------------------------------------------- # Deployment identity (Cloudflare: var) # --------------------------------------------------------------------------- # Required. Cloudflare: var.deployment_name DEPLOYMENT_NAME=local # Display name in the UI, PR footers and User-Agent headers. Cloudflare: var.app_name APP_NAME=Open-Inspect # Required. The GitHub App's bot login, `[bot]`. Only used to attribute # and filter the bot's own activity, so the placeholder below boots a local or # staging stack; set it to the real login before connecting a GitHub App. # Cloudflare: var.github_bot_username GITHUB_BOT_USERNAME=open-inspect[bot] # Public base URL of this control plane; sandboxes and image builds call back # to it, so it must be reachable from the sandbox provider. Cloudflare: local.control_plane_url WORKER_URL=http://localhost:8787 # Base URL of the web app, for PR links and browser-auth origin checks. Cloudflare: local.web_app_url WEB_APP_URL=http://localhost:3000 # "github" (default) or "gitlab". Cloudflare: not set by Terraform; a Worker var if needed. SCM_PROVIDER=github # Cloudflare account id; only used to derive a workers.dev callback URL when # WORKER_URL is unset. Cloudflare: not set by Terraform. CF_ACCOUNT_ID= # --------------------------------------------------------------------------- # Encryption keys (Cloudflare: secret). Each is 32 random bytes, base64: # openssl rand -base64 32 # A key that is not 32 bytes is rejected (the token and repo-secrets keys at # boot, the provider-accounts key at first use). Rotating one invalidates what # it encrypted, so generate them once per deployment and keep them. # --------------------------------------------------------------------------- # Required. OAuth tokens at rest. Cloudflare: var.token_encryption_key TOKEN_ENCRYPTION_KEY= # Required. Model-provider account credentials. Cloudflare: var.provider_accounts_encryption_key PROVIDER_ACCOUNTS_ENCRYPTION_KEY= # Required. Repo-scoped secrets and MCP server configuration. Cloudflare: var.repo_secrets_encryption_key REPO_SECRETS_ENCRYPTION_KEY= # Pepper for image-build callback token hashes. Cloudflare: generated (random_password.image_callback_token_pepper) IMAGE_CALLBACK_TOKEN_PEPPER= # --------------------------------------------------------------------------- # Browser sign-in and access control # --------------------------------------------------------------------------- # Signs Better Auth state. Cloudflare: secret, var.nextauth_secret BROWSER_AUTH_SECRET= # GitHub OAuth app for sign-in. Cloudflare: var / secret, var.github_client_id / var.github_client_secret GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= # Google OAuth app for sign-in (optional). Cloudflare: var / secret, var.google_client_id / var.google_client_secret GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= # Who may sign in: comma-separated lists. Cloudflare: var.allowed_users, # var.allowed_email_domains, var.allowed_emails, var.allowed_github_orgs ALLOWED_USERS= ALLOWED_EMAIL_DOMAINS= ALLOWED_EMAILS= ALLOWED_GITHUB_ORGS= # "true" disables the allowlists entirely. Cloudflare: var.unsafe_allow_all_users UNSAFE_ALLOW_ALL_USERS= # --------------------------------------------------------------------------- # Service-to-service authentication (Cloudflare: secret, generated by # Terraform as random_password.service_auth_secret_*). Each service signs its # requests with its own secret; an unset secret means that service cannot # authenticate. The web app's SERVICE_AUTH_SECRET must equal SERVICE_AUTH_SECRET_WEB. # --------------------------------------------------------------------------- SERVICE_AUTH_SECRET_WEB= SERVICE_AUTH_SECRET_SLACK_BOT= SERVICE_AUTH_SECRET_GITHUB_BOT= SERVICE_AUTH_SECRET_LINEAR_BOT= # Bot token for agent-initiated Slack messages (optional). Cloudflare: secret, var.slack_bot_token SLACK_BOT_TOKEN= # --------------------------------------------------------------------------- # Source control (Cloudflare: secret) # --------------------------------------------------------------------------- # GitHub App credentials for repository access and git operations. The private # key is the PEM text (PKCS#8) on one line, either double-quoted with real # newlines (compose decodes "\n" inside double quotes) or with the two # characters \n in place of each newline, which the key parser strips. # Cloudflare: var.github_app_id, var.github_app_private_key, var.github_app_installation_id GITHUB_APP_ID= GITHUB_APP_PRIVATE_KEY= GITHUB_APP_INSTALLATION_ID= # GitLab, when SCM_PROVIDER=gitlab. Cloudflare: not set by Terraform; a Worker # secret and var of the same names if needed. GITLAB_ACCESS_TOKEN= GITLAB_NAMESPACE= # --------------------------------------------------------------------------- # Sandbox provider. One of "modal" (default), "modal-vm", "daytona", "vercel", # "e2b", "boat"; only that provider's block needs values. # Cloudflare: var.sandbox_provider # --------------------------------------------------------------------------- SANDBOX_PROVIDER=modal # Set modal-vm for deployment-wide Docker-capable VMs; see docs/MODAL_DOCKER.md. # Modal. The API secret is shared with the Modal deployment for HMAC-signed # endpoint calls; the workspace and environment build the endpoint URLs. # Cloudflare: secret var.modal_api_secret; var.modal_workspace, # var.modal_environment, var.modal_environment_web_suffix MODAL_API_SECRET= MODAL_WORKSPACE= MODAL_ENVIRONMENT= MODAL_ENVIRONMENT_WEB_SUFFIX= # Origin serving the Modal functions by path (`/api-create-sandbox`), in # place of their derived `*.modal.run` hosts. Leave empty against Modal itself; # a proxy or the compose smoke's stand-in server sets it. # Cloudflare: not set by Terraform MODAL_API_URL= # Modal API tokens; not read by the control plane today (the Modal app is # deployed with them separately). Cloudflare: not set by Terraform. MODAL_TOKEN_ID= MODAL_TOKEN_SECRET= # Daytona. Cloudflare: secret var.daytona_api_key; var.daytona_api_url, # var.daytona_base_snapshot, var.daytona_target, var.daytona_toolbox_api_url, # var.daytona_prebuilds_enabled. The two interval settings are not set by # Terraform. # # DAYTONA_TOOLBOX_API_URL overrides the per-sandbox toolbox proxy; leave it # empty on a deployment whose sandboxes report their own. # # DAYTONA_PREBUILDS_ENABLED ("true"/"1") admits new Daytona image builds and # lets fresh sessions boot from one. Default off: callbacks, finalization, # status and cleanup keep working while it is, so closing it is the rollback. DAYTONA_API_KEY= DAYTONA_API_URL= DAYTONA_BASE_SNAPSHOT= DAYTONA_TARGET= DAYTONA_TOOLBOX_API_URL= DAYTONA_PREBUILDS_ENABLED= DAYTONA_AUTO_STOP_INTERVAL_MINUTES= DAYTONA_AUTO_ARCHIVE_INTERVAL_MINUTES= # Vercel Sandboxes. Cloudflare: secret var.vercel_sandbox_token; # var.vercel_sandbox_project_id, var.vercel_sandbox_team_id, # var.vercel_base_snapshot_id, var.vercel_sandbox_runtime, # var.vercel_sandbox_api_base_url, var.vercel_snapshot_expiration_ms. # VERCEL_BASE_SNAPSHOT_NAME is generated (module.vercel_sandbox_infra[0].snapshot_name) # when no snapshot id is supplied. VERCEL_TOKEN= VERCEL_PROJECT_ID= VERCEL_TEAM_ID= VERCEL_BASE_SNAPSHOT_ID= VERCEL_BASE_SNAPSHOT_NAME= VERCEL_RUNTIME= VERCEL_SANDBOX_API_BASE_URL= VERCEL_SNAPSHOT_EXPIRATION_MS= # E2B. Cloudflare: secret var.e2b_api_key; var.e2b_api_url, # var.e2b_template_id, var.e2b_sandbox_timeout_seconds, var.e2b_auto_pause E2B_API_KEY= E2B_API_URL= E2B_TEMPLATE_ID= E2B_SANDBOX_TIMEOUT_SECONDS= E2B_AUTO_PAUSE= # Boat. Cloudflare: secret var.boat_api_key; var.boat_api_url, # var.boat_base_snapshot (the saved snapshot the boat-infra module builds), # var.boat_org, var.boat_sandbox_type. # # BOAT_SANDBOX_TYPE is the machine size (small, default, large, xlarge) for # sessions that request no CPU or memory; a request picks the smallest size # that fits it. When sandboxes bill an organization (BOAT_ORG, or the account's # active wallet), set the organization's default visibility to private; the # provider unshares any sandbox that still starts shared. BOAT_API_KEY= BOAT_API_URL= BOAT_BASE_SNAPSHOT= BOAT_ORG= BOAT_SANDBOX_TYPE= # --------------------------------------------------------------------------- # Sandbox lifecycle (Cloudflare: var) # --------------------------------------------------------------------------- # A connected session-page WebSocket extends idle timers; direct terminal, # VS Code, or VNC connections alone do not. # Regular idle timeout, in ms (default 600000), for user, slack-bot, and linear-bot sessions. # Cloudflare: var.sandbox_inactivity_timeout_ms SANDBOX_INACTIVITY_TIMEOUT_MS= # Idle timeout for automation, github-bot, and agent sessions, in ms (default 120000, # minimum 60000; lower values are rejected). Effective timeout is min(this, regular). # Set at or above SANDBOX_INACTIVITY_TIMEOUT_MS to disable the shorter timeout. # Cloudflare: var.sandbox_unattended_inactivity_timeout_ms SANDBOX_UNATTENDED_INACTIVITY_TIMEOUT_MS= # Longest a sandbox whose bridge has connected may keep booting (clone, # setup.sh, start.sh, agent start) before it is failed and the prompt it was # for fails, in ms (default 1800000). Cloudflare: var.sandbox_boot_timeout_ms SANDBOX_BOOT_TIMEOUT_MS= # Longest a turn may run before it is failed, in ms; also the budget the # automation recovery sweep holds a run to. A session without its own sandbox # timeout setting falls back to the sandbox timeout (7200000). Cloudflare: not # set by Terraform. EXECUTION_TIMEOUT_MS= # "enforce" (default) fails spawn and build on oversized secret payloads; # "warn" only logs. Cloudflare: not set by Terraform. SECRETS_CAP_ENFORCEMENT= # Session team enforcement: off, shadow, or on. Unset defaults to shadow. # Private visibility applies in every mode. Cloudflare: var.teams_enforcement. TEAMS_ENFORCEMENT= # --------------------------------------------------------------------------- # Logging # --------------------------------------------------------------------------- # "debug" | "info" | "warn" | "error" (default "info"). Cloudflare: not set by Terraform. LOG_LEVEL=info # --------------------------------------------------------------------------- # Compose stack only (docker-compose.yml). Not read by the control plane. # --------------------------------------------------------------------------- # Host interface the app's port is published on. Loopback keeps the plaintext # listener local; Caddy (the "tls" profile) reaches the app over the compose # network. Use 0.0.0.0 only where a firewall or security group fronts the # host, such as behind the AWS load balancer. The object store's S3 port is # published on loopback only. APP_BIND_ADDRESS=127.0.0.1 # Root credentials for the compose stack's object store. The password is # required and has no default: # openssl rand -hex 16 # The AWS_* and LITESTREAM_* credentials are these same values when the # stack's own object store is in use. OBJECT_STORE_ROOT_USER=objectstoreadmin OBJECT_STORE_ROOT_PASSWORD= # Where Litestream replicates DATA_DIR/global.db, read by the sidecar and by # the app image's restore-on-empty entrypoint. The compose stack requires a # bucket name; only the image run on its own treats an empty value as "no # replica". LITESTREAM_ENDPOINT is empty for AWS S3. LITESTREAM_BUCKET=backups LITESTREAM_ENDPOINT=http://object-store:9000 LITESTREAM_ACCESS_KEY_ID=objectstoreadmin LITESTREAM_SECRET_ACCESS_KEY= # Hostname Caddy obtains a certificate for under the "tls" profile. CADDY_DOMAIN=