# THREAT MODEL — OutcomeGuard

## What OutcomeGuard is

A single-container, self-hosted watchdog for n8n automations. It accepts
check-ins from workflows (heartbeat/outcome modes), optionally polls the n8n
Public API with read-only scopes (passive/hybrid modes), evaluates outcome
contracts, and fires alerts.

## Assets

| Asset | Where | Sensitivity |
|---|---|---|
| n8n API key (read-only) | process memory, env | Medium — grants read access to the operator's n8n |
| Check-in tokens (per monitor) | config, process memory | Medium — forgery allows faking healthy outcomes |
| Metrics payloads | RAM, transient | Low–High (operator-defined; may contain business counts) |
| Incident/alert history | SQLite DB | Low |
| Alert webhook URLs / tokens | env, config | Medium |

Explicitly NOT stored: workflow payloads, execution data, credentials. The
database records timestamps, state, and violation strings only.

## Trust boundaries

1. **Workflow → OutcomeGuard**: any process that can reach the listen port can
   attempt check-ins. Auth = per-monitor bearer token.
2. **OutcomeGuard → n8n**: outbound read-only API calls with a scoped key.
3. **OutcomeGuard → alert channels**: outbound HTTPS with channel secrets.
4. **Operator → config**: YAML file, mounted read-only into the container.

## Threats and mitigations

### T1 — Check-in forgery (attacker fakes "success")
- **Vector**: attacker with network access to the port sends forged check-ins.
- **Mitigation**: per-monitor random bearer token (`openssl rand -hex 24`),
  constant-time comparison (`hmac.compare_digest`). Tokens are never logged.
- **Residual**: an attacker who can read the config (file access) already owns
  the host; no additional protection claimed.

### T2 — n8n API key over-privilege
- **Mitigation**: the client is GET-only by construction (enforced by test:
  no write-shaped methods can exist on `N8nClient`). Documentation mandates
  least scopes: `workflow:read/list`, `execution:read/list`.
- **Residual**: scope enforcement is n8n's job; OutcomeGuard cannot escalate.

### T3 — Injection via metrics/assertions
- **Mitigation**: assertions are typed dataclasses; no `eval`/`exec` anywhere
  (test enforces no dynamic constructs in assertion evaluation). Metrics are
  JSON-parsed with strict typing; violation strings are formatted, never
  interpolated into code.

### T4 — SSRF via config
- **Vector**: `n8n.url` and webhook URLs come from the operator's own config.
- **Mitigation**: config is operator-controlled by definition; the container
  runs with `read_only`, `no-new-privileges`, non-root UID 65532, and no
  shell. SSRF by a third party requires config write access = host compromise.

### T5 — DoS on the check-in endpoint
- **Mitigation**: request body capped (16 KB), header size capped by stdlib
  http.server, constant-time token check, no DB writes for rejected requests.
- **Residual**: single-threaded-per-connection stdlib server is not built to
  face the open internet. **Deployment guidance: bind to localhost, a Docker
  network, or a reverse proxy with network ACLs. Do not expose directly.**

### T6 — Secret leakage into logs
- **Mitigation**: access logs record paths only. Tokens and API keys are never
  printed. Config errors reference variable names, not values.

### T7 — SQLite tampering (attacker with disk access)
- **Mitigation**: none claimed. Disk access = game over, standard for
  single-container self-hosted tools. DB contains no secrets.

### T8 — Dependency supply chain
- **Mitigation**: zero runtime dependencies beyond Python 3.12 stdlib +
  PyYAML. No telemetry, no phone-home, no auto-update.

## Out of scope (explicitly)

- Multi-tenancy, RBAC, audit trails
- Host-level security (container escapes, kernel)
- n8n-side vulnerabilities
- Protecting against an operator attacking themselves

## Security invariants (enforced by tests)

1. `N8nClient` exposes no non-GET HTTP capability.
2. Assertions never execute dynamic code.
3. Rejected requests (bad token/JSON/size) never mutate state.
4. Recovery and incident transitions cannot leave inconsistent state rows.