# 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.

Source: https://developers.cephable.com/docs/automate-http-server/security

---
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](/docs/automate-http-server/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](https://github.com/Cephable/Cephable-Automate-Agent-Samples/tree/main/samples/public-gateway) 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.
