Security & Threat Model
Intended Trust Model
Cloud Harness MCP is intentionally a private, single-owner remote coding harness. It allows arbitrary repository-controlled execution inside a constrained executor, but it is not a hostile multi-tenant sandbox.
Defensive Layers
1. Ingress & Control Plane Isolation
- The Ingress Proxy is the only service bound to external loopback.
- The API and Runner never publish host ports directly.
- The API has no access to the Docker socket or host filesystem mounts.
Public authentication: Cloudflare Access Managed OAuth
On a cloudflare-access instance, such as the owner deployment at https://harness.zuey.me/mcp, Cloudflare Access with Managed OAuth is both the OAuth authorization server your AI client talks to and the public protection layer in front of Cloud Harness. GitHub and Google only sign you in to Access; Cloud Harness trusts the signed assertion Access forwards, never your client's token, and Access gains no Docker, runner, or workspace authority. The static API-key lane at https://api.harness.zuey.me/mcp is separate and not interchangeable. The sequence and verification details live in the repository security model; per-lane setup is on Connecting MCP Clients.
2. Executor Confinement & Hardening
- Non-Root User: Containers execute as UID 10001 (
harness). - Hardened Standard Mode: Standard executors strictly maintain
--read-only,--cap-drop ALL, and--security-opt no-new-privileges. - 3-Zone Storage Partitioning: Ephemeral secrets/config in RAM tmpfs (
/tmp/cloud-harness-home), persistent user-space toolchains in/opt/user-tools&/var/cache/harness, and clean Git checkout in/workspace. - No Docker Authority: No socket mount or host filesystem access.
- Dashboard Terminal: The Terminal opens a shell inside this same executor, as the same user and under the same limits; it never runs on the host or in the harness services. It is reachable only through the authenticated dashboard, is not an MCP tool, and its audit records only open and close, never keystrokes or output.
- Network Egress On by Default (
dependency-access): Workspace executors have outbound network access by default, so a workspace can reach the GitHub API and the bundledghCLI.dependency-accesspermits only public DNS and TCP 80/443 while an attested Linux host firewall blocks loopback-to-host, Docker/control-plane, RFC 1918, link-local, and cloud-metadata ranges. It fails closed if attestation is unavailable and still permits public exfiltration.network-noneblocks all egress and is the per-workspace or instance-wide opt-out.
3. Privileged Execution & Operator Grants
Privileged (
sudo/root) commands are treated as an explicit threat model weakening and are supported in Cloudflare Access mode only.Running with
privileged: truerequires an operator approval grant (PRIVILEGE_APPROVAL_REQUIRED), single-use, 60s TTL, bound to command and working directory hash.Approval is performed by authenticated operators via the Dashboard control plane (
/api/v1/privilege-grants). MCP clients cannot self-approve. The Approvals page shows each grant's real status and offers a decision only for an unexpired pending grant.Approved privileged commands run in isolated ephemeral containers with root cleanup normalizers.
The
bashtool has no privileged mode; privileged commands stay onexec_run. Itstoolboxtarget (off unless the operator setsPACKAGES_ENABLED=true) runs each command in a fresh, hardened, no-network container with no checkout, mounts, or secrets. The runner removes it by exact name and verifies the removal; an in-container deadline and a periodic sweep cover a runner that dies mid-command, andTOOLBOX_MAX_CONCURRENTcaps how many run at once.Managed packages (
packages_*, off unlessPACKAGES_ENABLED=true) are built in a short-lived hardened container whose only network is the provisioning proxy. That proxy is shared with toolkit acquisition, so a builder reaches the registry hosts the operator lists inPACKAGES_PROXY_HOSTSplus the proxy's built-in Git and toolkit hosts. The builder gets no secrets or tokens. Planning runs no package code: uv installs pre-built wheels only and npm lifecycle scripts are off by default. Each release is tree-verified and launch-checked with no network before it is published. Releases are mounted read-only at/opt/chm-packages/<releaseId>. WithPACKAGES_APPROVAL_MODE=required, every apply needs a Dashboard approval bound to the exact plan digest. A package still runs with full workspace authority, so approve only packages you would run in a workspace yourself.The optional Decision Plane (off unless the operator sets
DECISION_PLANE_PURPOSESand the owner stored a TypeSafe key) is advisory only. It can reorder MCP gateway search results that permission filtering already allowed, and add adata.diagnosishint to a generic GitHub failure. It never changes an error code, retries, or grants access. Before anything is sent, secrets are redacted fail-closed: if redaction cannot complete, nothing leaves. Each decision leaves one audit row without prompt text.TYPESAFE_EGRESS=offstops it instance-wide.
4. Credential Safety
- Private clone, push, and GitHub CLI operations use short-lived GitHub App tokens passed exclusively over
stdininto ephemeral helpers. - When no GitHub App token is available, an operator-supplied
GH_TOKEN/GITHUB_TOKENfallback is used instead: the runner environment credential (owner-bearer mode only), then the requesting principal's global runtime secret. The fallback is also passed only overstdin. - The optional Cloudflare API token that lets Dashboard Settings manage MCP OAuth session lifetimes is runner-only: the API, ingress, browser, and workspace executors never receive it. Scope it to one account with Access: Apps and Policies Write (or Read).
- Tokens are never stored in configuration files, MCP results, audit payloads, or repository commit history. The one documented exception is an operator-created GitHub runtime secret, which is deliberately injected into executor environments to authenticate the workspace
ghCLI. Because egress is the default posture, repository-controlled code can exfiltrate such a credential; prefer a fine-grained token scoped to the repositories the workspace needs, or keep the instance or workspace onnetwork-none.
5. Secrets Management & Ingest-Time Redaction
- Write-Only At Rest: Global and project environment secrets are encrypted at rest with AES-256-GCM via the runner-held keyring (
SECRET_KEYRING_FILE). Secret values and ciphertext are never returned over MCP or dashboard APIs. - Scope Precedence: Active global
runtimesecrets are automatically inherited by remote Docker workspaces;runtimeenvironment secrets override colliding global keys when explicitly injected withconfirmEnvironmentInjection: true. - Stream & Output Redactor: Streaming task, shell, session, and Dashboard Terminal output chunks are sanitized across stream boundaries to
[REDACTED_SECRET: <NAME>]before retained buffering, while synchronousexec_runcommand outputs and error messages are sanitized after capture before return.
6. Toolkit Provisioning Firewall & Content-Addressed Storage
- Internal Network Containment: All toolkit clone helpers run attached strictly to an
internal: truenetwork with no default gateway. Raw TCP sockets fail at the kernel level (ENETUNREACH). - Dual-Homed Provisioning Proxy: All helper outbound traffic traverses
provisioning-proxy:3128, enforcing DNS allowlists for approved Git hosts and blocking private subnets, loopback, and cloud metadata (169.254.169.254). - Content-Addressed Storage (CAS): Pinned bundles are verified with full-tree SHA-256 digests and published atomically with
fsyncordering toTOOLKIT_CACHE_ROOT.
7. Skill Tiers & Execution Isolation
- Built-in & Owner Tiers (
/opt/cloud-harness/skills:ro,/opt/cloud-harness/owner-skills:ro): Mounted read-only (:ro) at the container engine boundary, preventing in-container modification. - Workspace & Repository Tiers (
/workspace/.cloud-harness/skills,/workspace/.agents/skills): Reside within the mutable repository checkout. Execution creates a snapshot under/tmp/cloud-harness-exec/<runId>and validates the full-tree bundle digest before execution to detect unintentional filesystem race conditions. - Skills over MCP: The native
skills/*extension serves onlybuilt-inskills and the authenticated user's own library skills. Every file is hashed from the bytes on disk, symlinked or special files make a skill unservable, and a read whose bytes no longer match the manifest fails. Served content is untrusted input; reading it never activates a skill or runs a script.