# Security

> What Coordinate holds, how it's protected, what agents can and cannot do, and the limits.

## Holding as little as possible

Many integrations need no key from you at all:

| How it connects | Examples | What Coordinate holds |
|---|---|---|
| The tool pushes to Coordinate | GitHub, Claude Managed Agents webhooks, Claude Code telemetry, HTTP ingest | A signing secret, or nothing |
| GitHub App | Pull requests, checks, Actions, readiness | Only the app's key; each use mints a one-hour installation token |
| One click (OAuth) | Todoist, Notion, Gmail, Linear | A read-only grant you can revoke from the provider's settings; refreshed automatically |
| Your own webhook | Zapier, your own service | Nothing, or a signing secret |

A pasted key is needed only where the provider offers nothing else (Cloudflare, Vercel, Granola, Poke, the Anthropic API, APIs your triggers call).

## Stored secrets

- **Encrypted with a key kept apart from the data.** Each value is sealed with AES-256-GCM under a key derived for your workspace from the deployment's `SECRETS_KEY`, a Worker secret that is never written to storage. Values are bound to their name and workspace. The GitHub App's credentials and the session key are sealed the same way.
- **Write-only.** Nothing in the app, API, MCP, or search returns a value. Settings shows the last four characters, where each secret may be sent, and what used it last.
- **Bound to a host.** A secret goes only to the host it was first sent to (or the hosts you list when saving it). A trigger or poller later pointed elsewhere fails with an explanation instead of sending the key. A request's host can't come from a template. Saving the secret again resets the binding.
- **Removed when no longer needed.** Disconnecting a source deletes its token unless something else uses it, and revokes one-click grants at the provider. A secret still in use can't be deleted.
- **Owner only.** Agents can't store, read, or delete secrets; they refer to them as `secret:NAME`.

## Tokens and agents

- **Tokens** are scoped: read-only, configure, or ingest (one source). Only a hash is stored. They can expire after 1 to 365 days; revoke any in Settings.
- **Triggers** an agent creates send only with secrets the owner stored, only to the hosts those secrets are bound to, and every firing and action is in the log.
- **The query replica** holds no secrets, so read-only SQL is safe to hand to agents.
- **Payload text** from sources is untrusted data, never instructions, and agents are told so.

## Traffic

- **Webhooks in** are verified: GitHub with the app's secret, Todoist with the app's client secret, Linear with the app's webhook signing secret (and deliveries over a minute old are refused), Gmail's Pub/Sub pushes with a secret token in the push URL, Claude Managed Agents with the SDK's `webhooks.unwrap`. **Webhooks out** can be signed with HMAC-SHA256; redirects are not followed.
- **Requests and pollers** reach public https URLs only; IP literals and internal names are refused.

## Sign-ins are events

Every way into a workspace lands in its log, on the `coordinate` source:

| Event | When |
|---|---|
| `login.signed_in` | Someone signed in to the web app, with GitHub or an email code |
| `login.requested` | An agent or the CLI asked to sign in with a device code that names this workspace |
| `login.approved`, `login.denied` | The owner decided a device sign-in |

Each one says where the request came from and carries a one-line `summary`. The **Sign-in alerts** recipe texts you that summary for every sign-in. A request to sign in carries the approve link: approve only a sign-in you or your agent just started. Requests are limited to 20 an hour per workspace.

## Check the log yourself

Every event is hash-chained: its hash covers the event and the hash before it, in an open format anyone can recompute ([format](https://usecoordinate.com/log-format.md)). Settings shows the log's chain head, and where it ships if it has a bucket.

```bash
coordinate log            # events, chain head, and the bucket it ships to
coordinate log verify     # recompute every hash from the first event; save the head as a checkpoint
coordinate log export --out log.ndjson   # a full copy in the open format
```

`verify` runs on your machine with the Coordinate CLI (`curl -fsSL https://usecoordinate.com/install.sh | sh`). It saves each head it checks, and the next run fails if any event before a saved head has changed. Over HTTP the same are `GET /v1/log` and `GET /v1/log/events?after_cursor=N`.

## Limits

- Coordinate has to use your keys to do its job, so the running service can decrypt them. Encryption protects stored data; it does not protect against someone who can change and deploy the service's code, which today is its operator.
- Every workspace is its own Durable Object with its own database; one workspace can't read another's.
- Hosted on Cloudflare Workers, or self-hosted on one machine (see the runbook in the repository).
