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

# Security

> Key hygiene, rotation, logging, and incident response

## Key storage

* API keys are stored as SHA-256 hashes — we cannot recover a lost key.
* OAuth access and refresh tokens are hashed at rest.
* Full secrets are never written to audit logs.

## Rotation

1. Create a new key (or use **Rotate** on an existing key in the Developers page).
2. Update your integration to use the new key.
3. Revoke the old key.

OAuth client secrets are rotated from **Integrations → Sight AI API → OAuth apps → Rotate secret**. The old secret stops working immediately; outstanding refresh tokens can't be redeemed without the new secret. To invalidate all issued tokens, revoke the client instead.

## Logging

We log: route, method, status code, latency, key prefix, and IP — **not** request/response bodies or Authorization headers.

## Incident response

If a key is exposed:

1. Revoke it immediately in **Integrations → Sight AI API**.
2. Review recent API logs in the Developers page.
3. Create a new key with minimum required scopes.

## MCP OAuth

* Redirect URIs must be HTTPS or localhost/127.0.0.1 only.
* PKCE (`S256`) is required for authorization code exchange — for every client, including confidential ones.
* OAuth tokens are bound to a single team selected at consent.

## OAuth client authentication

* **Public clients** (`token_endpoint_auth_method: none`) authenticate with PKCE only — for desktop MCP clients, SPAs, and mobile apps that can't hold a secret.
* **Confidential clients** (`client_secret_basic` / `client_secret_post`) authenticate with a client secret **and** PKCE. The secret is hashed at rest (SHA-256) and returned once at creation; it can never be recovered. Use a confidential client for server-side integrations so a stolen refresh token can't be redeemed without the secret.
* Confidential clients are issued only by an authenticated team admin via the dashboard. Open Dynamic Client Registration (`POST /oauth/register`) is public-only — anonymous callers can never obtain a secret.

## Outreach webhook verification

When you subscribe to [Outreach Webhooks](/developers/webhooks), Sight AI signs every POST with an HMAC-SHA256 signature in the `SightAI-Signature` header (format: `t=<unix>,v1=<hex>`). **Always verify the signature before processing the payload** — an unverified webhook could be a forged request from anyone who knows your endpoint URL.

The signing secret is shown once at subscription creation. Store it in your environment and never expose it in client-side code or logs. See [Webhooks → Verifying the signature](/developers/webhooks#verifying-the-signature) for Node.js and Python verification code.

## API-key-trusted sender semantics

When you push a message into the Outreach Agent via `POST /api/v1/sites/{siteId}/outreach/inbox/messages`, the `from` field you supply is treated as the prospect's email address — Sight AI does not verify that the email actually belongs to the person your platform claims it does. The API key holder is a trusted forwarder (analogous to a team member forwarding an email), so the platform is responsible for the accuracy of the `from` field.

A compromised API key could inject arbitrary "forwarded" messages and cause the agent to send replies to arbitrary addresses. Mitigations:

* **Rotate exposed keys immediately** via the Developers page.
* **Use minimum scopes** — a key with only `inbox:write` can't read analytics or trigger other agents.
* **Restrict to specific sites** at key creation time when possible.
* The per-site daily ingestion cap (default 500/day) bounds the blast radius.
* The suppression list + noreply filter + agent triage are still honored regardless of the sender.
