# Environment Variables

<!-- DO NOT EDIT MANUALLY. Generated by scripts/build-docs-reference.mjs from .env.example -->


Cloud Harness MCP is configured via environment variables supplied to the stateless **API**, the **Runner**, and the **Cloudflare Worker Gateway**.

Copy `.env.example` to `.env` and replace all `change-me` placeholder secrets before starting services.

## Configuration Table

| Variable | Default / Example | Required / Mode | Description & Purpose |
|---|---|---|---|
| `MCP_BEARER_TOKEN` | `change-me-at-least-32-random-characters` | **Required** | Copy to .env and replace every change-me value. Never commit real secrets. |
| `RUNNER_TOKEN` | `change-me-independent-runner-token` | **Required** | Required configuration. |
| `OWNER_ID` | `owner` | **Required** | Required configuration. |
| `AUTH_MODE` | `owner-bearer` | **Required** | owner-bearer (default) or cloudflare-access. In Access mode, remove MCP_BEARER_TOKEN. |
| `CLOUDFLARE_ACCESS_ISSUER` | `https://your-team.cloudflareaccess.com` | Optional | Optional configuration. |
| `CLOUDFLARE_ACCESS_AUDIENCE` | `—` | Optional | Optional configuration. |
| `CLOUDFLARE_ACCESS_JWKS_URL` | `https://your-team.cloudflareaccess.com/cdn-cgi/access/certs` | Optional | Optional configuration. |
| `API_KEY_AUTH_ENABLED` | `false` | Optional | Optional managed API-key lane. Enable all four together only in cloudflare-access mode. The gateway audience belongs to a separate Access application scoped exactly to /mcp-api-key. |
| `API_KEY_GATEWAY_ACCESS_AUDIENCE` | `—` | Optional | Optional configuration. |
| `API_KEY_GATEWAY_SERVICE_SUBJECT` | `cf-service:base64url-cloudflare-service-token-client-id` | Optional | Optional configuration. |
| `API_KEY_GATEWAY_PUBLIC_URL` | `https://api.harness.zuey.me/mcp` | Optional | Optional configuration. |
| `ACCESS_LEGACY_OWNER_ID` | `owner` | Optional | Worker-only secrets CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET are configured with Wrangler, never here. Exact one-time legacy owner binding for the first Access rollout: |
| `ACCESS_LEGACY_ISSUER` | `https://your-team.cloudflareaccess.com` | Optional | Optional configuration. |
| `ACCESS_LEGACY_SUBJECT` | `—` | Optional | Optional configuration. |
| `ACCESS_PRINCIPAL_RELINKS` | `[]` | Optional | Optional audited subject-rotation mappings, supplied as strict JSON: |
| `API_PUBLIC_HOSTS` | `localhost,127.0.0.1,cloud-harness-mcp.46-250-239-227.sslip.io` | **Required** | Required configuration. |
| `API_ALLOWED_ORIGINS` | `https://cloud-harness-mcp.46-250-239-227.sslip.io` | **Required** | Required configuration. |
| `API_PORT` | `3000` | **Required** | Required configuration. |
| `RUNNER_PORT` | `3001` | **Required** | Required configuration. |
| `RUNNER_URL` | `http://runner:3001` | **Required** | Required configuration. |
| `MCP_GATEWAY_TIMEOUT_MS` | `30000` | Optional | MCP gateway (/mcp-gateway) downstream connection bounds and safe defaults. |
| `MCP_GATEWAY_MAX_RESPONSE_BYTES` | `262144` | Optional | Optional configuration. |
| `MCP_GATEWAY_MAX_TOOLS_PER_SERVER` | `500` | Optional | Optional configuration. |
| `MCP_GATEWAY_MAX_SCHEMA_BYTES` | `65536` | Optional | Optional configuration. |
| `MCP_GATEWAY_MAX_CATALOG_BYTES` | `2097152` | Optional | Optional configuration. |
| `MCP_GATEWAY_MAX_TRACE_ROWS` | `20000` | Optional | Optional configuration. |
| `MCP_GATEWAY_MAX_CONNECTIONS` | `32` | Optional | Optional configuration. |
| `MCP_GATEWAY_ALLOW_PRIVATE_ENDPOINTS` | `false` | Optional | Localhost, loopback, link-local, metadata, and private MCP endpoints are rejected unless the private-endpoint opt-in is enabled. Cleartext http endpoints additionally require both the insecure-http and private-endpoint opt-ins. Both opt-ins are refused in cloudflare-access mode; use https public endpoints there. |
| `MCP_GATEWAY_ALLOW_INSECURE_HTTP` | `false` | Optional | Optional configuration. |
| `MODEL_GATEWAY_SESSION_HEADER` | `x-opencode-session` | Optional | Model gateway provider override. When set, the gateway sends this one upstream header, filled with the calling agent id, to OpenAI-compatible providers that require a conversation identifier (for example x-opencode-session for OpenCode Go). Unset sends no such header. The value must be a lowercase header name the gateway does not set itself. |
| `JOBS_ROOT` | `/var/lib/cloud-harness/jobs` | **Required** | Required configuration. |
| `STATE_DB` | `/var/lib/cloud-harness/state/cloud-harness.db` | **Required** | Required configuration. |
| `ARTIFACT_ROOT` | `/var/lib/cloud-harness/artifacts` | **Required** | Required configuration. |
| `MAX_ARTIFACT_BYTES` | `16777216` | **Required** | Required configuration. |
| `MAX_PRINCIPAL_ARTIFACT_BYTES` | `134217728` | **Required** | Required configuration. |
| `ARTIFACT_RETENTION_SECONDS` | `86400` | **Required** | Required configuration. |
| `REPO_CACHE_ROOT` | `/var/lib/cloud-harness/cache/repos` | **Required** | Required configuration. |
| `ENABLE_REPO_CACHE` | `false` | Optional | Optional configuration. |
| `TOOLKIT_CACHE_ROOT` | `/var/lib/cloud-harness/cache/toolkits` | **Required** | Required configuration. |
| `ENABLE_TOOLKIT_CACHE` | `true` | Optional | Optional configuration. |
| `TOOLKIT_NETWORK_POLICY` | `cache-only` | Optional | Optional configuration. |
| `BUILTIN_SKILLS_ROOT` | `/var/lib/cloud-harness/skills` | Optional | Operator-provided agent skills: the single source for the `built-in` skills tier. Set to a host directory (absolute) and it is mounted read-only into every executor at /opt/cloud-harness/skills, the worker's highest-precedence tier, and rescanned by the runner from this same directory to attribute that partition. Content is operator-owned: the harness never writes to it and records its provenance as built-in. The mount target stays authoritative on the executor side. This name is reserved, so a workspace environment can never shadow it. Replaces the former CH_BUILTIN_SKILLS_ROOT override: rename that variable to this one, or move the catalog to the mount target. Leave unset to keep the tier empty. |
| `AGENTKIT_REGISTRY_URL` | `https://agentkit.best` | Optional | Licensed AgentKit kits (toolkits: [{ kind: "agentkit", kitId: "engineer" }]). |
| `AGENTKIT_REGISTRY_CREDENTIAL_SECRET` | `AGENTKIT_REGISTRY_TOKEN` | Optional | The secret name that holds this principal's AgentKit licence token. The token must start with ak_dev_ or ak_cli_ and MUST be created with purpose=provisioning, never runtime: runtime secrets are injected into executor environments. It is resolved from the dashboard secret store; without it the agentkit toolkit kind fails closed. |
| `AGENTKIT_REGISTRY_KEY_ID` | `agentkit-registry-2026` | Optional | Ed25519 registry signing key id. Required before the agentkit toolkit kind is available. |
| `AGENTKIT_REGISTRY_PUBLIC_KEY` | `<PEM or base64 SPKI DER>` | Optional | Ed25519 registry signing public key (PEM or base64 SPKI DER). Required before the agentkit toolkit kind is available; an unverified manifest is refused. |
| `EXECUTOR_IMAGE` | `cloud-harness-executor:local` | **Required** | Required configuration. |
| `NETWORK_GUARD_IMAGE` | `cloud-harness-network-guard:local` | **Required** | Required configuration. |
| `ALLOWED_GIT_HOSTS` | `github.com` | **Required** | Required configuration. |
| `WORKSPACE_NETWORK_PROFILE` | `dependency-access` | **Required** | Executor egress for newly opened workspaces. dependency-access (default) permits public DNS and TCP 80/443 so the GitHub API and the bundled gh CLI work; network-none blocks all egress. dependency-access requires a Linux host firewall provisioned via deploy/scripts/setup-dependency-firewall.sh and is refused, never silently downgraded, when that firewall is not attested. The dashboard Settings page overrides this value for future workspaces without a redeploy. |
| `DEPENDENCY_DNS_RESOLVERS` | `8.8.8.8,1.1.1.1` | Optional | Optional configuration. |
| `DEPENDENCY_BRIDGE_SUBNET` | `172.30.240.0/24` | Optional | Optional configuration. |
| `DEPENDENCY_BRIDGE_INTERFACE` | `chm-egress0` | Optional | Optional configuration. |
| `DEPENDENCY_NETWORK_NAME` | `cloud-harness-dependency-access` | Optional | Optional configuration. |
| `WORKSPACE_WALL_TTL_SECONDS` | `900` | **Required** | Required configuration. |
| `WORKSPACE_IDLE_TTL_SECONDS` | `300` | **Required** | Required configuration. |
| `MAX_ACTIVE_WORKSPACES_PER_OWNER` | `3` | **Required** | Concurrent active workspaces allowed per principal. Defaults to 3 when unset; set 1 to restore single-workspace behaviour. Each counted workspace can use up to 1 GiB of container memory, one CPU, and 256 pids, so size host memory for this limit times the expected simultaneous builds. |
| `GITHUB_APP_ID` | `—` | Optional | Optional GitHub App repository access; required fields depend on AUTH_MODE: |
| `GITHUB_APP_INSTALLATION_ID` | `—` | Optional | Required in owner-bearer mode; omit in Access mode, where each principal binds an installation: |
| `GITHUB_APP_SLUG` | `—` | Optional | Required in Access mode for the installation redirect: |
| `GH_TOKEN` | `—` | Optional | Optional operator-wide GitHub fallback credential, read from GH_TOKEN (preferred) then GITHUB_TOKEN, each also accepting a _FILE form. Used only by the runner, only when no GitHub App repository token can be minted, and only in owner-bearer mode: an operator-wide credential must never authorize a different principal. In cloudflare-access mode, create a per-principal global runtime secret named GH_TOKEN or GITHUB_TOKEN in the dashboard instead. This credential authenticates harness-side GitHub operations only and is never placed in executor environments. To authenticate the bundled gh CLI inside a workspace, create a global runtime secret named GH_TOKEN or GITHUB_TOKEN. |
| `GITHUB_TOKEN` | `—` | Optional | Optional configuration. |
| `GITHUB_APP_PRIVATE_KEY_FILE` | `/run/cloud-harness-secrets/github-app-private-key.pem` | Optional | Production host file: /etc/cloud-harness-mcp/github-app-private-key.pem |
| `SECRET_KEYRING_FILE` | `/run/cloud-harness-secrets/secret-keyring.json` | Optional | Versioned AES-256-GCM keyring JSON. Prefer the runner-only file form. |

## Security Guidelines

1. **Never commit `.env` files** or tokens into version control.
2. **Runner secrets isolation:** `RUNNER_TOKEN` and `SECRET_KEYRING_FILE` are passed only to the Runner container, never to the API or workspace executors.
3. **Managed OAuth vs Bearer:** When `AUTH_MODE=cloudflare-access`, remove `MCP_BEARER_TOKEN` and configure `CLOUDFLARE_ACCESS_*` variables instead.
4. **Executor Isolation:** Executors never inherit host environment variables or control plane tokens.
