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.
- 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. - Approved privileged commands run in isolated ephemeral containers with root cleanup normalizers.
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, and session stdout/stderr 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.