Environment Variables
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 to the /mcp-api-key path (its /mcp-api-key/gateway subpath included). |
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. |
MCP_DOWNSTREAM_OAUTH_REDIRECT_URL | https://dashboard.example.com/dashboard/mcp-oauth/callback | Optional | Downstream MCP OAuth (Cloud Harness authenticating TO a third-party MCP server; unrelated to the Cloudflare Access login that authenticates clients TO Cloud Harness). The redirect URL must be the dashboard origin plus /dashboard/mcp-oauth/callback; OAuth Connect is unavailable until it is set. The client metadata URL is optional: a public https URL serving this client's metadata document, used as client_id when the authorization server supports Client ID Metadata Documents. Without it, dynamic registration or a manually registered client is used. |
MCP_DOWNSTREAM_OAUTH_CLIENT_METADATA_URL | https://public.example.com/oauth/client/cloud-harness | 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 a Custom route to OpenCode). A registry provider's own session header, such as OpenCode Go's, takes precedence. 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 | Workspace lifetime defaults. Dashboard Settings > Workspace limits & lifecycle can override these (and MAX_ACTIVE_WORKSPACES_PER_OWNER) for newly opened workspaces without a redeploy. |
WORKSPACE_IDLE_TTL_SECONDS | 300 | Required | Idle timeout: a workspace without tool activity for this long expires (30..43200, always capped by the wall TTL). |
WORKSPACE_RECOVERABLE_GRACE_SECONDS | 3600 | Optional | How long an expired workspace stays recoverable before auto-close (0..86400, default 3600). 0 closes clean expired workspaces on the next reaper pass; work that is not pushed is always retained. |
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. |
TERMINAL_MAX_PER_WORKSPACE | 4 | Optional | Dashboard Terminal (runner): live PTYs one workspace may hold; another open is refused with 429. |
TERMINAL_RETAINED_BYTES | 1048576 | Optional | Raw output bytes retained per terminal for reconnecting readers; older bytes are reported as missed. |
TERMINAL_IDLE_TIMEOUT_MS | 900000 | Optional | A terminal with no input, resize, or read for this long ends as timed_out. |
TERMINAL_MAX_LIFETIME_MS | 28800000 | Optional | Absolute terminal lifetime regardless of activity. |
PACKAGES_ENABLED | false | Optional | Managed toolbox (runner): enables the bash tool's toolbox target, which runs each command in a disposable no-network executor-image container. Off by default; the workspace target is unaffected. |
TOOLBOX_MAX_CONCURRENT | 2 | Optional | Maximum toolbox containers running at once on this runner; a call beyond it gets LIMIT_EXCEEDED (429, retryable). |
PACKAGES_ROOT | /var/lib/cloud-harness/packages | Optional | Managed toolbox packages (runner, used only while PACKAGES_ENABLED=true). Immutable releases live here; Compose mounts HOST_PACKAGES_ROOT at this path and the two must be equal. |
PACKAGES_ECOSYSTEMS | npm,uv | Optional | Ecosystems that may be planned: a comma-separated subset of npm,uv. apt always answers NOT_SUPPORTED. |
PACKAGES_APPROVAL_MODE | required | Optional | required: each plan waits for a Dashboard approval bound to its digest (cloudflare-access mode; owner-bearer refuses apply). none: plans are approved by policy when created. |
PACKAGES_NPM_LIFECYCLE_SCRIPTS | deny | Optional | allow lets a plan that asks for npm lifecycle scripts run them in the builder; deny always skips them. |
PACKAGES_MAX_RELEASE_BYTES | 536870912 | Optional | Largest verified release tree, in bytes (16 MiB to 4 GiB). |
PACKAGES_MAX_OWNER_BYTES | 4294967296 | Optional | Total retained release bytes per owner (64 MiB to 64 GiB). |
PACKAGES_MAX_BINDINGS | 32 | Optional | Package bindings (one package in one scope) per owner. |
PACKAGES_BUILD_TIMEOUT_MS | 600000 | Optional | Wall-clock limit for one plan or apply builder container. |
DECISION_PLANE_PURPOSES | — | Optional | Decision Plane (runner): advisory semantic decisions, sent only when the owner has a TypeSafe integration key. Comma-separated subset of mcp-routing,failure-classification; empty keeps it off. |
TYPESAFE_EGRESS | on | Optional | Instance-wide TypeSafe egress switch (runner): off stops skill suggestions and the Decision Plane even when an owner has stored a key. Per owner, deleting the key does the same. |
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. |
CLOUDFLARE_ACCESS_API_TOKEN_FILE | /run/cloud-harness-secrets/cloudflare-access-api-token | Optional | Optional Cloudflare Access Managed OAuth integration (cloudflare-access mode only). Lets Dashboard -> Settings read and update the inbound MCP OAuth grant session duration and access-token lifetime of the Access application that protects /mcp. All three identifiers are required together. The API token is runner-only: scope it to "Access: Apps and Policies Write" (or Read with CLOUDFLARE_ACCESS_OAUTH_ACCESS=read-only) and prefer the _FILE form. Production host file: /etc/cloud-harness-mcp/cloudflare-access-api-token |
CLOUDFLARE_ACCESS_ACCOUNT_ID | — | Optional | Optional configuration. |
CLOUDFLARE_ACCESS_APP_ID | — | Optional | Optional configuration. |
CLOUDFLARE_ACCESS_OAUTH_ACCESS | read-write | Optional | read-write (default) or read-only. |
CLOUDFLARE_ACCESS_OAUTH_MAX_GRANT_SECONDS | 2592000 | Optional | Longest grant the provider accepts, in seconds. Defaults to 2592000 (30 days). |
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
- Never commit
.envfiles or tokens into version control. - Runner secrets isolation:
RUNNER_TOKENandSECRET_KEYRING_FILEare passed only to the Runner container, never to the API or workspace executors. - Managed OAuth vs Bearer: When
AUTH_MODE=cloudflare-access, removeMCP_BEARER_TOKENand configureCLOUDFLARE_ACCESS_*variables instead. - Executor Isolation: Executors never inherit host environment variables or control plane tokens.