> ## Documentation Index
> Fetch the complete documentation index at: https://internal.september.wtf/llms.txt
> Use this file to discover all available pages before exploring further.

# Access control

> Who can do what against an Engine.

Access control in the Engine is layered. The Engine itself has a
single binary auth boundary — you have the API key or you don't. The
multi-user complexity lives upstream (in the routing layer) and below
(in MCP per-user OAuth). This page maps the layers.

## The layers

```mermaid theme={null}
flowchart TB
    User["End user / customer"]
    App["Application layer<br/>(your product's user accounts)"]
    BAP["Upstream router (BAP)<br/>knows which Engine belongs to which user"]
    Engine["The Engine<br/>(single-tenant; one API key gates all access)"]
    Sandbox["Sandbox<br/>(bwrap + seccomp + landlock)"]
    MCP["MCP credentials<br/>(per-connection, encrypted)"]

    User -->|application auth| App
    App -->|per-user routing| BAP
    BAP -->|X-Engine-Key| Engine
    Engine --> Sandbox
    Engine --> MCP

    classDef userBox fill:#fef3c7,stroke:#a16207
    classDef boundary fill:#dbeafe,stroke:#1d4ed8
    classDef engineBox fill:#0F172A,stroke:#0F172A,color:#fff
    classDef secured fill:#dcfce7,stroke:#15803d
    class User userBox
    class App,BAP boundary
    class Engine engineBox
    class Sandbox,MCP secured
```

Each layer has its own access concept. This page covers each.

## Engine API key

The most basic boundary. Every request except `/health` requires the
`X-Engine-Key` header.

What the key is:

* Either a static value (`ENGINE_API_KEY` in env) or pre-hashed
  (`ENGINE_KEY_HASH` in env, plaintext lives upstream).
* Single value per Engine. There are no scopes; the key has full
  access to this Engine.

What the key is **not**:

* Not a per-user identifier. The Engine doesn't know which user is
  calling; it knows whether the caller is authenticated.
* Not OAuth. Plain token in a header.
* Not encrypted in the request body (HTTPS encrypts the transport;
  always use HTTPS in production).

For rotation see [Secrets](../config/secrets#engine-api-key-rotation-with-overlap).
For the conceptual auth model see
[Authentication](../../platform/api-reference/auth).

## Upstream routing (BAP)

The Engine is single-tenant, so multi-user logic lives in the upstream
router. BAP (the public product's backend) holds:

* User accounts.
* The user-to-engine mapping (which Engine instance serves user X).
* Application-level RBAC (which users can perform which operations).
* Billing.

When user X makes a request, BAP:

1. Authenticates the user via its own auth (sessions, OAuth, API key,
   etc.).
2. Authorizes the operation per BAP's RBAC.
3. Routes to the right Engine, attaching `X-Engine-Key`.

The Engine never sees user identity; it sees a pre-validated
`X-Engine-Key`. If the routing fails open or routes to the wrong
Engine, user X's request hits user Y's brain. This is a BAP-level
concern.

## MCP per-connection access

MCP credentials are per-connection. When user X connects Slack, the
OAuth flow grants the Engine specific scopes against user X's Slack
workspace. The credentials are encrypted at rest (`AD_ENCRYPTION_KEY`)
and used only for outbound calls to the corresponding MCP server.

Key properties:

* **Per-user.** Each Engine instance's brain has its own connections.
* **Per-scope.** Each connection grants specific scopes; calls outside
  those scopes fail at the provider.
* **Revocable.** Disconnecting (`DELETE /assets/connections/{ref_id}`)
  revokes the stored credentials.

## Sandbox access

The sandbox is the access boundary for tool execution. See
[Code execution](../../platform/capabilities/code-execution) for the
guarantees.

In summary:

* The agent (and tool subprocesses) see only paths in `ALLOWED_ROOTS`.
* Dangerous syscalls are blocked by `seccomp`.
* Filesystem ACLs are enforced by `landlock`.
* Permission prompts surface destructive operations to the user
  (HITL).

The sandbox is per-task. The agent's bash in task A cannot see files
written by the agent's bash in task B (within the same Engine), beyond
what's persisted to `ALLOWED_ROOTS`.

## Per-feature access

Some Engine features have additional access:

### Admin endpoints

`/admin/*` endpoints (catalog reload, soul reload, LC trigger) are
authenticated by the same API key. There's no separate admin role.

In multi-user deployments, BAP gates which users can call admin
endpoints based on its own RBAC.

### `/feedback`

Same `X-Engine-Key`. No separate authorization. The submitted feedback
is recorded as a learning signal regardless of who submitted it; in
practice BAP gates this to authenticated users.

### Memory endpoints

Same `X-Engine-Key`. Read access to memory is full; write access goes
through `/memory/import` only. There is no partial-access mode for
memory.

## Audit

For every request, the Engine logs:

* Timestamp.
* `request_id`.
* `task_id` (for `/execute` and HITL).
* Path.
* HTTP status.
* Latency.

Logs ship to your logging pipeline. The pipeline's retention is your
audit retention.

For specific compliance auditing of permission decisions and admin
actions, the `observability_events` table holds:

* Permission grants and denials.
* Admin endpoint invocations.
* Migration runs.
* API key validation failures.

These are SQLite rows; export periodically for long-term retention.

## What multi-user products usually do

For a typical multi-user product running on the Engine:

1. **BAP authenticates the user** (web session, OAuth, etc.).
2. **BAP authorizes** based on its own RBAC.
3. **BAP looks up the user's Engine** (fixed mapping or dynamic
   provisioning).
4. **BAP forwards the request** to the Engine with the Engine's API
   key.
5. **The Engine processes** without knowing which user — its job is to
   serve whoever sent the right `X-Engine-Key`.

This separation matters: BAP can enforce complex multi-user policy
without each Engine needing to understand it.

## What this access model can't enforce

* **The model's behavior.** The agent can decide to do things that
  the access model permits but that you'd consider wrong (e.g. sending
  a Slack message in an awkward tone). Defense lives in the agent's
  system prompt and the permission layer.
* **Cross-user data leaks via routing bugs.** If BAP routes user X's
  request to user Y's Engine, user X reads user Y's data. The Engine
  trusts that the request was correctly routed.
* **The user's own actions.** A user can ask their own Engine to do
  things; the access model can't tell them no. (The permission system
  catches the destructive operations specifically.)

## See also

* [Threat model](./threat-model) — the full picture.
* [Authentication](../../platform/api-reference/auth) — `X-Engine-Key`
  details.
* [Permissions](../../platform/agents-and-tools/permissions) — the
  HITL gate for risky operations.
* [Data handling](./data-handling) — what's stored, who can read it.
