---
title: "Security architecture"
description: "MainSDK's trust boundaries, the STRIDE threat model per interface and driver class, how CI keeps the models current, and the limits we close."
section: "architecture"
status: "Planned for 0.4"
spec: "threat-models"
canonical: "https://mainsdk.dev/docs/architecture/security-model"
updated: "2026-10-28"
---

# Security architecture

> MainSDK's trust boundaries, the STRIDE threat model per interface and driver class, how CI keeps the models current, and the limits we close.

Status: Planned for 0.4 · Canonical: https://mainsdk.dev/docs/architecture/security-model · Updated: 2026-10-28

This page is about internals: where the trust boundaries sit, how threats map to controls inside the SDK, and how the threat models themselves are kept honest. For what protects your app day to day (secrets, network permissions, redaction, signed delivery), read the [security model](https://mainsdk.dev/docs/concepts/security-model).

We publish a STRIDE threat model for every interface and every class of driver, kept next to its contract and versioned with it. The models are published per contract version from milestone 0.4.

## Trust boundaries

```mermaid
flowchart LR
  user(["User or agent"]) -->|"B1 · request"| app
  subgraph app["Your app process"]
    iface["Interface + guarantees"] --> pe["Policy engine"]
    pe --> drv["Driver"]
  end
  drv -->|"B2 · TLS, network allowlist"| prov["Provider API"]
  tuf["TUF repository"] -->|"B3 · signed packs"| app
  app -.->|"B4 · telemetry, after login"| cloud["MainSDK Cloud"]
  dev(["You and your agent"]) -->|"B5 · CLI, MCP server"| repo["Repo and CI"]
  repo --> app
```

## Threats and controls by layer

Each layer of the [four-layer call path](https://mainsdk.dev/docs/architecture/how-it-works) and each driver class has its own threats. The model files cover four driver classes (remote HTTP, in-process, local and WebAssembly-sandboxed) plus three platform subjects: TUF delivery, the CLI and the MCP server.

STRIDE: S spoofing, T tampering, R repudiation, I information disclosure, D denial of service, E elevation of privilege.

| Layer | Threat | STRIDE | Control |
| --- | --- | --- | --- |
| Interface and guarantees | A user reads another user's records | I | Owner scope on every query (`db.orders.for(user)`), rule `MAIN-DATA-0007` |
| Interface and guarantees | A key in code or the browser bundle | I | `secret:` references resolved on the server; `MAIN-SECRET-*` rules and `MAIN-CONFIG-0004` |
| Interface and guarantees | A retry charges or sends twice | T | Idempotency key in every contract that changes the world |
| Policy engine | A repo or overlay quietly loosens an org rule | E | Layers only tighten; `MAIN-POLICY-0004` on load |
| Policy engine | A broken or unsigned policy set leaves the app open | E, D | Deny by default; last good set kept, `MAIN-POLICY-0009` (ADR-0003) |
| Policy engine | A decision can't be traced later | R | Every decision logged with id, policy version, layer and mode, in a hash-chained OCSF audit log |
| Remote HTTP driver | A poisoned dependency sends secrets to a new domain | I | Network allowlist at the SDK chokepoint, `MAIN-NET-0002`; DNS answers checked, redirects to undeclared hosts refused |
| Remote HTTP driver | A driver touches data it never declared | I, E | Manifest of data labels, regions and hosts; the engine refuses the rest |
| In-process driver (for example Cedar) | A malformed input crashes the app | D | Panics never cross the binding; typed errors with stable codes |
| Local driver | Local setup reaches production | T | `MAIN-CHECK-0026 local driver in production` |
| Wasm-sandboxed driver | Driver code reaches beyond its permissions | E | WebAssembly sandbox where possible, plus manifest permissions |
| Delivery (TUF) | Rollback, freeze or mix-and-match of packs | T | TUF roles and expiry; `MAIN-TUF-0001` rollback refused; last good set on expiry (ADR-0005) |
| CLI | A tampered release of MainSDK itself | T | SLSA level 3, reproducible builds, Sigstore, no install scripts |
| CLI | `info` shows a forged app record as genuine | S | Signature checked on `/.well-known/mainsdk.json`; `MAIN-INFO-0002` |
| MCP server and actions | Tool poisoning or an agent running a destructive action | S, E | Tools exposed only when marked; signed tool descriptions; a person's yes for destructive actions; short-lived, narrow tokens through OAuth token exchange |
| MCP server and actions | A second door with weaker checks | E | One `action()` runs the same `access` check, owner scope, rate limit and validation on every surface |
| Cloud (phase 2) | The project id used to send fake data | S | The id is never a credential; only an ingest key or platform OIDC token sends |
| Cloud (phase 2) | Personal data leaks through telemetry | I | Redaction in process; `telemetry@1` field list; no bodies, cookies or auth headers |
| Cloud (phase 2) | Cloud outage takes apps down | D | Never in the request path (ADR-0003) |

## How a threat model is written

```yaml title="contracts/email/threat-model.yaml"
schema: threat-model@1
subject: { interface: email, contract: "email@1.4" }
assets: [message-body, email-address, provider-key]
boundaries:
  - app process → driver → provider API (TLS 1.3, network allowlist)
threats:
  - id: EMAIL-T-003
    stride: tampering
    threat: Marketing email sent without an unsubscribe link
    mitigations: [{ guarantee: email.unsubscribe }, { rule: MAIN-EMAIL-0001 }, { test: "email.AC4" }]
    residual: low
reviewed: { by: [security-reviewer-id], on: 2026-12-01 }
```

- Every model covers all six STRIDE categories, or says why one doesn't apply.
- Every mitigation points to something that exists: a guarantee, a check rule id or a tested acceptance criterion. A broken link fails CI.
- A contract version change without a threat model update fails CI.
- Each model records who reviewed it and when; the reviewer is never the only author of the contract change.

Models live beside the code they describe:

```text
contracts/email/email.tsp            the contract, email@1.4
contracts/email/threat-model.yaml    its threat model, same version
threat-models/drivers/               remote-http, in-process, local, wasm-sandboxed
threat-models/platform/              tuf-delivery, cli, mcp-server
```

`mainsdk contract check` runs the coverage report in CI. A new interface without a model fails with `MAIN-CONTRACT-0021 missing threat model: <interface>`. Each model renders on the docs site at `/security/threat-models/<interface>/<version>`, with older versions kept.

Still open: who reviews threat models in phase 1, before outside maintainers exist.

## Limits we close on purpose

- **Code that bypasses the SDK.** A raw socket or another HTTP library can escape an in-app check. `mainsdk check` blocks raw network use outside the SDK at build time (`MAIN-NET-0001`), and `mainsdk network export` writes the same allowlist into the platform's own egress controls as a second, independent wall.
- **No remote shell.** We never offer a shell or a debugger attached to production. Live tail, debug sessions and replay answer the same questions without opening that door (phase 2).
- **Production access.** Cloud access to production data needs SSO or a passkey with phishing-resistant MFA, and live tail and debug sessions need a fresh passkey no older than 5 minutes (phase 2). Fleet critical actions reuse the same step-up and need two people (phase 4).
- **Fleet push.** Fleet sends only signed, declarative policy and data, signed with keys the tenant holds, never code (phase 4).

## Independent review

An independent security audit of the TypeScript core and SDK happens before 1.0 and is published in full, then yearly. Our own release signing and vulnerability handling are in [supply chain](https://mainsdk.dev/docs/architecture/supply-chain).

## Next

- [Security model](https://mainsdk.dev/docs/concepts/security-model): the user-facing view of the same controls.
- [Policy engine](https://mainsdk.dev/docs/architecture/policy-engine): the decision point most controls rely on.
- [Supply chain](https://mainsdk.dev/docs/architecture/supply-chain): how our releases and packs are signed and delivered.
