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

# Security

> How Chert handles Slack OAuth tokens, signing-secret verification, and channel-binding scope.

Chert treats every Slack credential as server-side and every inbound
event as untrusted until verified.

## Before You Begin

* Chert Notifications and Chert Agent are installed via the OAuth flow described in [Install](/slack/install).

## OAuth token storage

| Property             | Value                                                                                     |
| -------------------- | ----------------------------------------------------------------------------------------- |
| Where stored         | Per-project row in the `slack_installations` table                                        |
| Token type           | Slack bot token (`xoxb-…`)                                                                |
| Exposure to browsers | Never returned by the API                                                                 |
| Status endpoint      | Returns workspace metadata only — team id, team name, bot user id, install timestamp      |
| Disconnect           | `POST /api/slack/disconnect` deletes the row; the bot can no longer post for that project |

Chert does not call Slack's `auth.revoke` on disconnect. To fully
revoke the install on Slack's side, remove the app from Slack's
**Manage apps** page.

## Signing-secret verification

Every inbound webhook from Slack is verified before any logic runs.

| App                                | Signing secret env var        |
| ---------------------------------- | ----------------------------- |
| Chert Agent events webhook         | `SLACK_SIGNING_SECRET`        |
| Chert Notifications events webhook | `SLACK_HELPER_SIGNING_SECRET` |

The verification follows Slack's documented v0 scheme:

| Field            | Value                                                      |
| ---------------- | ---------------------------------------------------------- |
| Basestring       | `v0:<x-slack-request-timestamp>:<raw body>`                |
| Header           | `x-slack-signature: v0=<hex hmac-sha256>`                  |
| Freshness window | 5 minutes (timestamp must be within ±300 s of server time) |
| Comparison       | Timing-safe                                                |

A failed verification returns `401` with a structured error body —
`{ ok: false, code: 2004, message, retryable: false, trace_id }` — and
the event is dropped. If the Slack signing secret is missing
server-side the route returns `500` with `code 3003`
(`PROVIDER_NOT_CONFIGURED`, `retryable: false`) instead. Slack's
one-time `url_verification` handshake is the only event accepted
without signing — it still returns a bare `200` with the raw
`challenge` string, unchanged.

Genuine error paths across `/api/slack/*` now carry the structured
body (numeric `code`, generic `message`, `retryable`, `trace_id`);
the OAuth callback redirects to `/settings` with a stable short
`reason` slug (`no_code`, `slack_denied`, `oauth_exchange_failed`,
`not_configured`, …) rather than raw error text in the URL. See the
[Messaging API error scheme](/api/errors) for the `code` registry.

## Event deduplication

Slack retries webhooks on slow responses or network errors. Chert
deduplicates by Slack's `event_id` for 10 minutes, so retried events
do not double-fire actions like proposals or sends.

## Channel-binding scope

| Scope                   | What it means                                                                                                        |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------- |
| One channel per project | Notifications and agent posts target the bound channel only.                                                         |
| Cross-project isolation | Each project's bot token, settings, and canvases are independent of every other project, even on the same workspace. |
| Private channels        | The bot must be invited (`/invite @Chert Notifications`) before it can post or write canvases.                       |

## What the bot reads vs writes

| Operation                        | Type  | Surface                                                             |
| -------------------------------- | ----- | ------------------------------------------------------------------- |
| List channels for the picker     | Read  | `conversations.list`                                                |
| Post reply notification cards    | Write | `chat.postMessage`                                                  |
| Post agent responses             | Write | `chat.postMessage`                                                  |
| Update agent progress messages   | Write | `chat.update`                                                       |
| Read thread history for context  | Read  | `conversations.replies` (only on threads under bot-posted messages) |
| Read reactions on proposal cards | Read  | `reactions.get` (only on bot-posted messages)                       |
| Create and edit canvases         | Write | `conversations.canvases.create`, `canvases.edit`, `canvases.delete` |
| Upload xlsx previews             | Write | `files.getUploadURLExternal`, `files.completeUploadExternal`        |

The bot does not browse channel history beyond threads attached to its
own messages, does not read direct messages, and does not read user
profiles.

## Webhook signing for outbound notifications

When `notificationMethod` is `webhook` or `both` and a `webhookSecret`
is configured, every outbound POST from Chert carries an
`x-chert-signature: v1,<ts>,<hex>` header. The signature is HMAC-SHA256
over `<ts>.<raw-body>` using your `webhookSecret`. Verify on receipt
to authenticate the payload.

## Sub-processors

| Vendor    | Purpose                             | Data                                                                |
| --------- | ----------------------------------- | ------------------------------------------------------------------- |
| Slack     | Notification surface, agent surface | Lead names, message bodies, conversation excerpts, project metadata |
| Anthropic | LLM drafting and classification     | Same — used to draft replies and answer thread questions            |
| Resend    | Email notifications                 | Operator email addresses, reply previews                            |

All three are bound by their respective DPAs. Customers can disable
the LLM surface by turning off AI-drafted replies in Settings.

## See Also

<CardGroup cols={2}>
  <Card title="Configuration" icon="gear" href="/slack/configuration">
    Routing, flags, and the Nth-reply filter.
  </Card>

  <Card title="Notifications" icon="bell" href="/slack/notifications">
    Card anatomy and thread-reply triage.
  </Card>
</CardGroup>
