Navigate

Security Model

What the Automate HTTP Server's loopback boundary does and does not protect, how the access key is stored and rotated, filesystem scope rules, destructive-tool policy, and the checklist for shipping an integration that holds the key.

The Automate HTTP Server is off by default, local-only, and authenticated. It is also, by design, a way for local software to make a signed-in user's assistant act on their machine — so it deserves to be understood before you ship an integration that holds its key.


The boundary

Control What it means
Opt-in Disabled until a Professional user enables it in Extensions → Cephable features → Build & Extend. It cannot be enabled over HTTP, by a launch argument, or by a config file — only from the app UI
Loopback only Binds 127.0.0.1. Not 0.0.0.0, not a LAN address. Remote and cloud services cannot connect directly
Bearer token on every route Including /health. A wrong token gets 401 before routing, so an unauthenticated caller cannot enumerate endpoints
Constant-time comparison Token checks are length-checked then compared with timingSafeEqual, so a wrong key leaks nothing through timing
Encrypted at rest The key is stored via the OS secure-storage API (Windows DPAPI, macOS Keychain, Linux keyring) in a 0600 file under the app's user-data directory
Never synced The key and the enabled flag stay on the device. They are not sent to Cephable's servers and do not follow the account to another machine
Serialized One run at a time. A second concurrent run is refused rather than queued
Approval-gated destructive tools delete_path, run_command, and move_path are rejected unless the request explicitly opts in
Bounded human-in-the-loop An unanswered required question cancels the run instead of hanging indefinitely
Bounded bodies Requests over 1 MiB are rejected
Licensed Requires a Cephable Professional account; the extension is gated behind the enableAutomateHttpServer feature flag

What the boundary does not protect against

Be clear-eyed about this list when deciding what to build:

  • Any local process can try. Loopback is not a permission boundary between programs on the same machine. Every local process — including one running as another user on a shared machine, depending on OS configuration — can reach the port. The key is the only thing standing between them and the assistant.
  • The key is as powerful as the user. Holding it means being able to run the assistant with the user's own file access, installed apps, connected accounts (email, Google Drive, MCP servers), and — with allowDestructiveTools: true — arbitrary shell commands. Treat it exactly like a password, not like a client id.
  • There is no per-client identity, scope, or audit trail. All callers share one key. You cannot grant one integration read-only access, and revocation is all-or-nothing (regenerate the key, which breaks every client).
  • A browser page cannot reach it, but that is not a security feature you should rely on. The server sends no CORS headers and has no OPTIONS handler, so a page on an http(s):// origin fails preflight. That blocks the casual case; it is not a defense against a native process.
  • Prompt injection is live. A run that reads a file, a web page, or an MCP tool result can be steered by content in it. The agent has real tools. Do not feed untrusted content into an unattended run that has destructive tools enabled or unrestricted filesystem access.
  • The run is not isolated from the user. Your run shares the conversation thread and inference slot with the app's own panel. A continuation may pick up a conversation the user started, and your run blocks theirs.
  • An inline stdio MCP server is local code execution. A request may declare an MCP server with transport: "stdio", which spawns a process with caller-supplied command, args and env. That is not gated behind allowDestructiveTools — that flag governs the agent's own run_command, not MCP transports. So holding the access key is equivalent to arbitrary local execution, independently of every other setting on the run. See Custom tools.
  • Caller-declared tools run in the caller's process, and a parked run holds the slot. A run waiting on caller-executed tool results keeps the app's single inference slot until results arrive, it is cancelled, or its two-minute per-round timeout expires.

Handling the access key

Do

  • Read it from an environment variable, an OS keychain entry, or a config file with restrictive permissions that your installer creates.
  • Scope its lifetime: fetch once at startup, keep it in memory, do not log it.
  • Redact it in error messages, crash reports, and telemetry. A URL is safe to log; an Authorization header is not.
  • Tell users how to rotate it — and make your integration recover from a 401 by asking for a new key rather than retrying.

Do not

  • Hardcode it in source, commit it, or ship it inside a sample.
  • Pass it on a command line. Process command lines are readable by other local processes — this is exactly why Cephable refuses to accept the key as a launch argument.
  • Put it in a URL query string, where it lands in logs and shell history.
  • Store it anywhere that syncs to another machine. The server it belongs to is local, and a leaked key plus local access is full assistant access.

Rotation. Regenerate access key in the extension detail view mints a new key and invalidates the old one immediately — there is no grace period and no key list. Every client using the old key starts getting 401 on its next request. Plan for that path in your integration.


Filesystem and command scope

By default, an API run reaches the same files the user's own run does. That is intentional: the caller holds the key and the user enabled the server deliberately, so the run behaves like a run started from the app.

Two things narrow it:

  1. The user's own workspace restriction, configured in Cephable, always wins. A request cannot widen or relocate it.
  2. restrictToWorkspace: true on a run confines file and CLI tools to the server's workspace folder, reported as workspace by /health.
Windows  %APPDATA%\Cephable\automate-http-workspace
macOS    ~/Library/Application Support/Cephable/automate-http-workspace
Linux    ~/.config/Cephable/automate-http-workspace

The scope is applied per run, at run start, from the request where one asks and from the user's settings otherwise — so an API run can never leave the assistant sandboxed for the user's next run from the panel, and cannot inherit a sandbox from a previous API run.

Rule of thumb: anything unattended, batch, or test-shaped should set restrictToWorkspace: true. Leave it off only for an integration that is genuinely acting on the user's behalf across their own files, at their request.


Destructive tools

delete_path, run_command, and move_path pause the run for review. The server decides from the request body, before the run starts:

  • allowDestructiveTools omitted or false (default) — every request is rejected, with a message the agent sees so it can adapt or explain.
  • allowDestructiveTools: true — every request is approved as-is, with no inspection of what is being deleted or executed.

Because run_command is in that set, allowDestructiveTools: true is equivalent to granting the caller arbitrary local command execution through the agent. Only enable it when all of the following hold:

  • restrictToWorkspace: true is also set.
  • The prompt and every input the run will read are trusted — no untrusted files, web pages, or third-party MCP results.
  • The machine is a sandbox or dedicated test profile, not a production workstation.
  • A human accepted the risk for this specific automation, not for "the integration" in general.

Secure storage requirements

The key is persisted only if the OS can encrypt it. If secure storage is unavailable, the server refuses to start and the extension detail view says so:

This server cannot be enabled because secure operating-system storage is unavailable.

  • Windows / macOS — available by default (DPAPI, Keychain).
  • Linux — requires a real keyring backend. The basic_text fallback is explicitly rejected, because it stores the key in plaintext. Install and unlock a keyring (GNOME Keyring, KWallet) or the feature stays off.

Organization and compliance notes

  • Nothing leaves the device by virtue of using this API. Inference runs on the local llama.cpp server; tool execution happens locally. What a tool reaches (web search, an MCP server, Google Drive, an email client) is the same set the app itself reaches, with the user's own credentials — the server does not add a new egress path.
  • Feature-flag gated. Because the extension is controlled by enableAutomateHttpServer, an organization that does not want it can keep it off the account entirely, rather than relying on users not enabling it.
  • No server-side logging of your prompts by Cephable. Prompts and results travel app-internal IPC, not the Cephable API. The app's own local logs may record run metadata.
  • Policy still applies. Org policies that block tools or workflows in the app apply to API runs too — a blocked run returns errorCode: "DISABLED_BY_POLICY".

Shipping checklist

Before you ship an integration that holds this key:

  • The key is read from the environment or an OS keychain — never hardcoded, logged, or passed on a command line.
  • 401 triggers a re-prompt for the key, not a retry loop.
  • 409 triggers a wait-and-retry on /health readiness, or a clear "Cephable is busy" message — never a duplicate run.
  • The endpoint is discovered or configurable, not hardcoded to 4317.
  • restrictToWorkspace: true for anything unattended.
  • allowDestructiveTools is off, or justified in writing against the four conditions above.
  • Untrusted content never enters a run that has destructive tools enabled.
  • Inline MCP servers use http/sse on loopback where possible, and any use of transport: "stdio" is a deliberate, reviewed decision — it is local code execution available to anything holding the key.
  • A parked caller-executed-tool run is cancelled on your error path, so a crash in your tool handler does not hold the user's assistant for two minutes.
  • Client timeout exceeds timeoutMs, and the error path calls POST /v1/automate/cancel so an abandoned run does not block the user.
  • Users are told what your integration asks the assistant to do, and how to turn it off (disable the extension, or regenerate the key).

If you need to reach one machine remotely

The honest answer is that you mostly should not — this runs on each user's device, which is the point. When you genuinely do, the public-gateway sample is the shape of it, and it is worth reading before you build your own.

It keeps the Cephable key inside its own process and issues callers separate, individually revocable keys. It builds the forwarded request from an allowlist rather than filtering a denylist, so a new upstream option cannot become a new remote capability just because Cephable shipped it. It forces restrictToWorkspace on and allowDestructiveTools off, refuses stdio inline MCP servers, refuses continuation, tracks which caller owns each parked run, queues for the single inference slot, and strips every host path and hardware detail out of the response before it goes back over the wire.

Its test suite is mostly "this refusal still refuses", which is the property you actually want from a layer like that.