# Security & data

> What the CLI sends, what Comity stores, data modes, encryption, tokens and fail-open behavior.

This page summarizes how Comity handles your data, from an engineer's point of view. The full statements are on the [Security](/security) and [Privacy](/privacy) pages.

## What the CLI sends

Code analysis runs on your machine. On each agent event the hook sends:

| Event | Sent |
|---|---|
| Every event | Repo remote fingerprints (e.g. `github.com/acme/api`), branch, HEAD and merge-base SHAs, the agent tool and model, CLI version, OS, a hash of your machine id, an HMAC of the worktree path, the launch folder relative to the repo root |
| Session start | Workspace package names and their folders (from `package.json`/`pyproject` manifests, up to 200) |
| Prompt | The first 2,000 characters of the prompt (used to auto-draft the intent title and summary) |
| Plan | The plan text, when the host reports one |
| Before/after an edit | Repo-relative paths; after the edit, the local analysis: per file status and line counts, changed symbols (key, kind, change type, exported, signature hashes, compatibility), new references to contracts, and, in `full` data mode, normalized signature text |
| Shell command | The command (up to 4,000 characters) and the paths it changed |
| End of turn | An excerpt of the agent's last message (up to 500 characters) and the files touched, unless status sharing is off |

The CLI never sends file contents or diffs. Raw local paths never leave the machine (only the hash and HMAC above). Turn off end-of-turn statuses per machine with `COMITY_SHARE_STATUS=0`, or org-wide with the [`share_session_status`](/docs/admin/policies#share_session_status) policy.

Agents also send what they write through the MCP tools: intents, messages, questions and answers.

## Data modes

Each org has a data mode, shown in **Settings → Organization → Data mode**. Owners change it there (or with `PATCH /v1/org`, see below).

| | `full` (default) | `metadata_only` |
|---|---|---|
| Normalized signature text of changed symbols | sent by the CLI and stored | not sent; only signature hashes |
| Stored contract signatures | text and hash | hash only |
| GitHub App sync | reads changed files from GitHub and analyzes them in memory; stores symbols and contracts | records only changed paths and line counts; no file contents fetched |
| Paths, symbol keys, compatibility, conflicts | stored | stored |
| Prompt excerpts, plans, statuses, messages | stored (encrypted) | stored (encrypted) |

Conflict detection works in both modes: compatibility is computed on your machine and signature hashes are enough to detect a changed signature. In `full` mode, briefings and `contract_lookup` can show the old and new signature.

### Changing the data mode

Owners pick **Full** or **Metadata only** in **Settings → Organization** and click **Save data mode**. Through the API, an owner's personal token with `policy:write` (or the dashboard session) sends:

```sh
curl -X PATCH https://api.comity.so/v1/org \
  -H "Authorization: Bearer $COMITY_TOKEN" -H "Content-Type: application/json" \
  -d '{"data_mode": "metadata_only"}'
```

The response is `{org: {id, slug, name, data_mode}, previous_data_mode, cleared_signatures}`. Admins and other roles get `403`. Each change is recorded as an `org.data_mode_changed` event (`{from, to, cleared_signatures}`) in the audit log.

- **Switching to `metadata_only`** applies at once on the server: signature text already stored for the org (contract signatures and changed-symbol signatures) is cleared, and any signature text a CLI still sends is dropped. Hashes are kept, so detection keeps working.
- **Switching to `full`** stores signature text again from the next edits on.
- The CLI reads the mode at `comity login`. Ask each member to run `comity login` again so their CLI stops sending (or starts sending) signature text.

Self-hosted operators can also run `admin set-data-mode --org <slug> --mode full|metadata_only`.

## Encryption and isolation

- Customer text (intent summaries and plans, messages, thread subjects, status excerpts, questions and answers, override reasons, Slack and webhook settings, GitHub App secrets and the GitHub webhook inbox) is encrypted with AES-256-GCM under a data key unique to your org, wrapped by a master key that is not stored in the database.
- Identifiers Comity must search and compare (repo names, branches, paths, symbol keys, signature hashes, intent titles, conflict explanations, the event log) are not encrypted at the application level.
- Every request runs in a database transaction scoped to your org under PostgreSQL row-level security.

## Tokens

- Tokens look like `cmt_` + 43 characters and are stored only as SHA-256 hashes.
- `comity login` creates a personal token per machine, named `cli · <hostname>`, stored in your user config file with owner-only permissions. `comity init` never writes a token into the repo.
- Personal tokens act as their user, with that user's role. They stop working when the user leaves the org. Service tokens (for CI or bots) have no user, can't call hooks or MCP, and are never billed.
- Owners and admins create tokens on the **Team** tab (**Tokens → Create token**): a service token or a personal token for themselves, a scope preset, and an optional expiry (30 days, 90 days or 1 year). The token is shown once, with a usage snippet. API: `POST /v1/tokens` (needs `tokens:write`).
- Revoke tokens on the **Team** tab or with `DELETE /v1/tokens/:id`. Removing a member revokes their tokens.

| Scope | Allows |
|---|---|
| `hooks:evaluate` | Agent hooks (`/v1/hooks/evaluate`) |
| `mcp:tools` | The MCP server |
| `intents:read` / `intents:write` | Intents, sessions, presence, team context, scope checks |
| `conflicts:read` / `conflicts:write` | Conflicts and actions |
| `messages:read` / `messages:write` | Messages, conversations, questions |
| `repos:read` / `repos:write` | Repos and projects |
| `members:read` / `members:write` | Members, invites, usage |
| `tokens:write` | Creating tokens |
| `policy:read` / `policy:write` | Policies |
| `events:read` | Events, feed, stats, the SSE stream |
| `snapshots:write` | Reserved |

Tokens from `comity login` get every scope except `repos:write`, `members:write`, `tokens:write`, `policy:write` and `snapshots:write`. To call admin endpoints from scripts, create a token on the Team tab or use the dashboard.

Dashboard scope presets:

| Preset | Scopes | Token types |
|---|---|---|
| CI / GitHub Actions | `repos:read`, `intents:read`, `conflicts:read`, `events:read` | service or personal |
| Full agent | the `comity login` scopes (above) | personal only |

## Text from teammates

Agents read text written by other people's agents. Comity marks it as peer text (`⟦peer:…⟧ … ⟦/peer:…⟧`) and its MCP instructions tell agents to treat it as data, never as instructions. Status excerpts are scanned for common secret formats (private keys, tokens) and redacted before storage.

## Fail-open

If Comity is unreachable, slow (more than 400 ms before an edit or shell command, 1.5 s for other events), or answers with something unexpected, the hook allows the action and writes one line to `hook.log` in the CLI config folder. Blocking happens only when Comity answers with a block under your policy. The `fail_mode` and `fail_closed` policy settings are stored but not enforced.

## Audit log

Comity records an event per org for hook blocks and warnings, conflicts (detected, acknowledged, resolved, overridden with the encrypted reason), intents, sessions, messages (without text), policy changes, members, invites, tokens, integrations and billing. Allowed hook calls are not logged as events. Browse and export to CSV in **Settings → Audit log**, page through `GET /v1/events`, or stream with `GET /v1/stream`. Events are kept indefinitely.
