May 30, 2026
Harbor Internals, Part 1: why exec does not mean secret access
Harbor exec hands code capabilities, not credentials. A walk through the secret boundary: isolate split, secret refs, mediated outbound fetch, error sanitization, and sealed CLI secrets.
- security
- execution layer
- credentials
- secrets
- sandbox
- isolate
- architecture
Most agent runtimes have an uncomfortable problem: the more useful an agent becomes, the closer it gets to credentials. If an agent can call GitHub, Slack, Linear, Stripe, or an internal API, something somewhere holds a token. The naive design puts that token next to the agent — in environment variables, in a process config, in a local .env, or in a server-side runtime object the agent’s own code can accidentally inspect. Harbor takes a different approach: execution gets capabilities, not credentials.
An agent running through Harbor exec can call a source tool. It can ask GitHub to search issues, hit a configured API endpoint, or use a workspace-approved MCP tool. But the secret that powers that call never lives inside the agent’s execution environment. That distinction is the core of Harbor’s execution-layer security model, and this post is a tour of how it is enforced. For the layer this boundary sits inside, start with what is an execution layer.
The boundary
Harbor’s execution path looks roughly like this:
CLI / MCP / SDK
-> Harbor execute API
-> host-side source + credential loading
-> isolated worker execution
-> host dispatch
-> MCP / API / source callThe important part is the split between host-side execution infrastructure and user code. User code runs in a separate Cloudflare Worker V8 isolate. The host side owns every sensitive object: encrypted credential reads, decrypted credential material, MCP clients, API invokers, OAuth grants, the encryption key, the credential database, the request context, and the policy/audit context. The user isolate sees only generated tool proxies, the JSON arguments it sends, and the JSON results it gets back. By design, decrypted credentials, MCP clients, encryption keys, and auth config live in the host isolate’s heap — never in the user-code isolate.
When user code calls something like:
await github.searchIssues({ query: "label:bug" })it is not holding a GitHub token. It is sending a structured JSON message — a tool key plus arguments — across the isolate boundary. The host receives that message, looks up the registered tool, attaches the correct auth, performs the upstream call, normalizes the result, and sends back JSON. No closures cross the boundary. No host objects cross the boundary. No decrypted credential crosses the boundary. That is why user code cannot reach the host’s environment variables, the encryption key, the credential store, or a live client’s auth — none of those objects are in its heap.
- The one bridge
- The only channel between the two isolates is JSON-RPC over structured cloning. The runner exposes generated tool proxies, never the host closures. Capability without the secret behind it.
Credentials are workspace data, not runtime data
Source credentials are stored server-side in an encrypted store, scoped per workspace and per source. Creating or updating a credential encrypts the submitted value before it is written. List responses deliberately omit the value and return only metadata plus a masked "****" placeholder — the read path returns credential metadata only, never plaintext. The credential is a control-plane object, not an agent runtime object, and the exec runtime can use it only through server-side resolution paths that already know the workspace, source, credential, agent identity, source visibility, and tool binding.
This prevents a common failure mode: “the agent got exec, so it got env.” In Harbor, exec does not imply environment access. Exec means: run code against a mediated capability surface. For API and MCP sources, the host loads the source rows and encrypted credentials, decrypts them host-side, builds the actual invoker or client, and registers a function in the host-side dispatch table. The isolated code can call github.search(...), but it only ships JSON args; the host attaches auth and performs the upstream call.
Secret refs, not secret values
Harbor also exposes a runtime secret provider. When code asks for a source secret, the execution layer does not return the value — it returns an opaque handle. secrets.fromSource(namespace, name) registers a reference ID in a request-local map and returns a handle. Conceptually:
const token = await harbor.secrets.fromSource("github", "token")
// token is NOT "ghp_live_secret_value"
// token IS { __harbor_secret_ref: "sec_..." }The real encrypted value stays in a host-side, request-local map. User code can pass the handle into supported runtime operations, but it cannot stringify it back into the secret. That is the difference between a secret as data and a secret as authority — Harbor exposes the authority without exposing the data behind it.
Outbound fetch is mediated
A secret boundary is useless if the runtime can exfiltrate whatever it observes. So generic outbound fetch from exec is routed through a mediating outbound gateway. It blocks credential-bearing request headers (Authorization, Cookie, Proxy-Authorization, and Harbor or Cloudflare internal headers), rejects URLs with embedded credentials, and rejects credential-like query params such as access_token, refresh_token, token, api_key, client_secret, code, and state. It also blocks localhost, private, and metadata hosts, strips Set-Cookie from responses, and caps oversized responses. Exec code may fetch public resources; it should not become a generic tunnel into Harbor internals, cloud metadata, or auth-bearing requests.
Errors are treated as a leak surface
Credentials usually leak through boring paths: exception messages, URL strings, JSON debug packets, upstream errors, logs. Harbor sanitizes tool-call errors before they reach user code — the sanitizer redacts bearer tokens, API-key-like query params, token JSON fields, and full URLs. This is not the primary boundary; the primary boundary is still “the secret was never in the user isolate.” But upstream tools and APIs are messy, so error redaction is necessary defense in depth.
- Two layered defenses
- (1) Tool errors are sanitized before crossing back into user code. (2) Generic outbound fetch is mediated by the gateway. Even an agent with exec capability is kept from using it as a raw network exfil channel for credentials it should never see.
CLI sources: sealed, not handed over
CLI-backed sources are the harder case — some tools genuinely need a secret in a local process environment. Harbor handles that differently from cloud exec. It decrypts the credential server-side, seals it to the local machine’s public key, and ships only the sealed secret. The local runner unseals it into environment variables only for the spawned sandbox command, blocks dangerous env keys, and disallows caller env overrides from replacing the sealed secrets. The cloud exec code never receives the plaintext; only the local sandbox process does, and only for the duration of the command.
Remote MCP does not expose the local sand bridge at all — sand is unsupported over Harbor MCP. A remote MCP client can submit TypeScript to Harbor Cloud exec, but it does not get local sandbox runtime access. Local CLI auth is also separate: the hrbr CLI keeps only profile metadata on disk; actual API keys live in the OS keychain when available, falling back to a permission-locked local file. Those API keys authenticate calls to Harbor; they are not a second secret source the exec code can read.
Why CLI / MCP access is not enough to steal secrets
With CLI or MCP access, an agent can authenticate, select a workspace, announce an identity, and call Harbor’s execute endpoint. It can run code through Harbor’s execution layer. But that access only grants the public exec surface: selected workspace context, allowed source/tool proxies, run and log result data, opaque secret refs, and allowed outbound fetch with auth-bearing headers and credential-like URLs blocked. It does not grant:
- Out of reach from user code
- direct access to the credential database, the Harbor encryption key, host-side closures, MCP client objects, decrypted auth, raw OAuth access tokens, local keychain contents, arbitrary process environment, and arbitrary private-network / metadata access.
So “basically impossible” is accurate in the intended threat model: an agent limited to CLI/MCP and exec cannot directly read Harbor-stored credentials, because no raw credential value ever crosses the user-code boundary. The one meaningful caveat is capability abuse: if a workspace intentionally exposes a tool that performs sensitive actions, the agent may use that tool’s authorized capability. That is not the same as extracting the credential — and it is exactly why Harbor also runs preflight, policy, and audit around source and tool access.
The takeaway
A secret as data and a secret as authority are different things. Harbor is built so the agent only ever touches authority: a callable proxy, an opaque ref, a sealed env handed to a local sandbox for the length of one command. The data — the decrypted token, the encryption key, the MCP client — stays on the host side of an isolate boundary the agent cannot cross. Exec is a mediated capability surface, not an open door to the runtime.
This is Part 1 of a series on Harbor internals. If you want to see the boundary in practice, connect a workspace and run hrbr execagainst a real plugin source — then try to read the credential from inside the program. You can’t.
Keep reading
- What is an execution layer?
An execution layer turns submitted code plus workspace context into an audited run: source resolution, MCP pooling, sandbox isolate, orbit primitives.
- Shared workspaces for AI agents
Why N agents × M tools needs a shared workspace, not per-agent toolboxes — credentials, memory, runs, and audit live with the team.
- Control plane vs MCP gateway: when each one fits
MCP gateways forward tool calls. A control plane owns the workspace around them — discovery, credentials, execution, audit. When each one fits.
Evaluating MCP control planes? See the comparisons hub for per-vendor breakdowns, or connect a workspace to try Harbor.