# Security model

Disclosure policy and the summary posture live in the root `SECURITY.md`. This
document is the engineering view: assets, actors, controls, and the tests that
keep them honest.

## Assets

1. Customer feedback and vote data (tenant-private).
2. Developer credentials: secret keys, MCP keys, studio tokens, OIDC sessions.
3. Tester identity and reputation.
4. The developer's coding agent — the most valuable target, because it can
   change the customer's code. ADapptive never runs it, but it consumes what
   ADapptive emits.
5. Reward budgets and payout instructions (once the economy is live).

## Actors and boundaries

```mermaid
flowchart TB
  subgraph untrusted [Untrusted]
    T[Testers and anonymous sessions]
    W[The public web and crawlers]
  end
  subgraph platform [ADapptive]
    API[API]
    DB[(Postgres)]
    DASH[Dashboard]
    MCPH[Remote MCP]
  end
  subgraph dev [Developer's trust domain]
    AGENT[Coding agent]
    REPO[Source code]
  end
  T -->|publishable key + tester JWT| API
  W --> DASH
  API --> DB
  MCPH -->|mcp key or OIDC| API
  AGENT --> MCPH
  AGENT --> REPO
```

Three boundaries matter: tester → platform (input validation, redaction,
screening, rate limits), platform → agent (envelope, CLEARED-only, least
privilege key), and tenant → tenant (every query scoped by `customerId`).

## Controls

### Keys and tokens

| Kind | Storage | Grants | Regression tests |
|---|---|---|---|
| `adp_pk_*` publishable | plaintext (public by design) | submit, vote, read zones | key-kind permission matrix (`integration.test.ts`) |
| `adp_sk_*` secret | SHA-256 hash | everything in the tenant | same |
| `adp_mk_*` MCP | SHA-256 hash | read feedback/zones/campaigns; write feedback status, analysis, deployments, logs, stages, rollback | `mcp-key.test.ts`: cannot delete zones, list testers, grant rewards, rotate keys |
| studio token | HMAC, TTL | zone CRUD | cannot manage people or rewards |
| pairing code | SHA-256 hash, 10-minute TTL, single use | redeem for one studio token | replay rejected |
| OIDC JWT | verified against issuer JWKS (`OIDC_ISSUER`, `OIDC_AUDIENCE`) | workspace by role; tester surface | invalid token fails closed, never downgrades to anonymous |

Secrets are shown once and never persisted in plaintext. Rotation invalidates
the previous hash atomically.

### Tenant isolation

Every read and write includes `customerId`; Socket.IO rooms are
`zone:<customerId>:<zoneId>`. Tested with two tenants sharing a `zoneId`.

### Voting integrity

Weight is resolved server-side from the voter's pool tier; anonymous and
account votes count once. Votes upsert in one transaction with a row lock and a
counter recompute; dedupe per session and per tester identity. The threshold
flip requires the zone's weighted `threshold`, `minParticipation`, **and**
`minTrustedVoters` distinct trusted voters (ACTIVE pool members or accounts
older than `TRUSTED_ACCOUNT_MIN_AGE_HOURS` with at least one cleared
contribution).

### Safety pipeline (tester → agent)

Runs synchronously on `POST /api/feedback` and on poll options; results in
`Feedback.safetyStatus` and `Feedback.safetyReport`.

| Layer | What it does | Outcome |
|---|---|---|
| Redaction | emails, phone numbers, card numbers (Luhn), SSNs, IBANs, IPv4/IPv6, JWT and API-key shapes → `[redacted:type]` in `redactedTitle`/`redactedDescription` | always applied; count recorded |
| Normalisation | strips C0/C1 control characters, bidi overrides, zero-width characters; collapses whitespace | always applied |
| Injection screening | scores imperative agent-directed phrasing ("ignore previous instructions", "you are now", "run", "execute", "add to .env", tool-call syntax, shell pipelines, base64 blobs, excessive URLs) | score ≥ flag threshold → `FLAGGED`; hard patterns → `BLOCKED` |
| Classifier (optional, `SAFETY_CLASSIFIER=anthropic|openrouter|off`) | structured-output judgement with a daily USD cap (`SAFETY_DAILY_BUDGET_USD`, alert at 80 %) | can only move CLEARED → FLAGGED |
| Moderation | humans approve, edit-and-approve, or block from `/moderation`; each decision writes `AuditEvent` | FLAGGED → CLEARED or BLOCKED |

Only `CLEARED` items are returned by `get_ready_feedback` and by the
`THRESHOLD_MET` filter. Agent-facing text is the redacted copy, wrapped in
`<untrusted_tester_content>` with attributes for id, field and source.

### Sybil and abuse controls

Per-key rate limits (existing), per-tester and per-session submission and
vote limits, account-age gating for vote weight, burst detection on `ipHash`
clusters recorded in `safetyReport.signals`, invite usage caps, and House Pool
tiers that start at zero weight.

### Extension

Mandatory permissions are localhost-only; live-site access is an
`optional_host_permissions` grant per origin (enforced by a manifest unit
test). No credentials ship with the extension. Developer mode stores only
expiring studio tokens obtained through a pairing code; tester sign-in is OIDC
PKCE against a public client.

### Remote MCP

Streamable HTTP behind Traefik; bearer `adp_mk_`/`adp_sk_` or Authentik
access tokens; `/.well-known/oauth-protected-resource` for discovery; per-key
rate limits; the transport forwards the caller's credential to the API and
holds no credentials of its own.

### Transport, storage, infrastructure

TLS at Traefik; Postgres and MinIO on the private compose network; secrets
only in Coolify's store; IPs stored as salted SHA-256; screenshots private and
served through authenticated routes with signature validation; no `.env` in
images.

## What we deliberately do not do

- Run customer code or a hosted AI worker.
- Store plaintext GitHub or integration tokens.
- Trust client-supplied roles, weights, or safety verdicts.
- Let studio tokens or MCP keys manage people or money.

## Verification

CI runs lint, build, type-check, and the integration suite against Postgres on
Node 20. Safety-pipeline tests assert: injection samples are FLAGGED or
BLOCKED and absent from agent-facing lists; PII is redacted in agent-facing
fields; quorum blocks a threshold flip; `adp_mk_` is denied on forbidden
routes; pairing codes are single-use. Manual: extension E2E in Chrome,
Playwright golden paths (see [operations](./operations.md)).
