▞ jazzdocsblogpersonas
github

Threat model

Status: draft — v1 to be dated and published. Items marked ☐ are self-audit checks still to be run before any public safety claim.

Jazz’s safety stance is fail closed by construction: when the agent, the classifier, or the operator hasn’t explicitly widened what may run, the answer is “ask a human.” This document lists what that means concretely, what it does not protect against, and how to check both.


What runs without asking

One dial — the approval policy — governs every surface the same way (terminal, CI, bots):

PolicyAuto-approves
falseNothing. Every gated tool asks.
read-onlyReading files, search, web requests
low-risk+ todo tracking, reminders, spawning sub-agents
high-risk+ file changes, shell commands, git commit and push

Everything above the dial blocks and emits approval_required; the run resumes only when an approval_decision comes back — from the terminal, or from your phone via a chat bridge. An unattended run that hits its ceiling waits; it does not die and it does not proceed. Source: tools-and-approval.

Two sharper controls sit under the dial:

  • Toolset omission. An agent’s tool list is explicit; a tool not listed does not exist for that agent. An agent without execute_command cannot run shell commands regardless of policy. This is the strongest control and the recommended one for anything unattended.
  • autoApprovedCommands. A persisted allowlist that admits single commands without raising the whole tier. Matching is a parsed key (binary + first subcommand) with word-boundary comparison — git status does not also permit git status && rm -rf /. Source: packages/core/src/agent/tools/command-risk.ts, configuration.

Shell commands fail closed

Commands with no static risk annotation are classified before approval. The classifier’s instruction is explicit: “high-risk = anything else, including uncertainty” and “a clearly mutating command is high-risk even if the conversation asked for something milder.” Text inside the command is treated as data to classify, never as instructions. An ambiguous command on an unattended run therefore blocks rather than runs. Source: packages/core/src/agent/tools/command-risk.ts.

Where secrets live

  • API keys are stored in the OS keyring, not in config files (packages/adapters/src/secrets/keyring.ts; JAZZ_DISABLE_KEYRING opts out).
  • Child processes are scrubbed. Any environment variable whose name matches API|KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|AUTH is stripped before a shell command or custom command tool spawns. Exceptions require an explicit per-agent envAllowlist entry; SSH_* names can never be allowlisted. Source: configuration → envAllowlist.
  • Known weakness, stated plainly: conversation transcripts are plaintext JSON under ~/.jazz/history/. Treat that directory as sensitive. Anything the agent read during a run may be in there.

Network posture

  • The CLI opens no listening port. jazz run is a process that starts, works, and exits.
  • The Telegram bridge defaults to long-polling (getUpdates): outbound connections only, no public URL, works behind NAT. Webhook mode exists but is opt-in and requires a secret. One caveat stated honestly: the bridge container always runs a minimal /health HTTP endpoint for container health checks. Source: packages/telegram-bot/bridge.ts.
  • JAZZ_OFFLINE=1 stops every outbound request Jazz makes on its own behalf except model inference itself. Source: airgapped.

Runaway protection

Unattended runs are budgeted, not trusted: an iteration ceiling with escalating wrap-up pressure, loop detection keyed on tool-name plus arguments, context compaction, and cost reported on every run. Source: agent-loop.

What Jazz does NOT protect against

Claiming less is part of the model. Jazz does not currently defend against:

  • Prompt injection steering permitted actions. Content the agent reads (a web page, an email, a PR body) can influence what it does within its approved tier. The mitigations are structural — narrow toolsets, low tiers for unattended runs, approval walls for everything mutating — not content analysis.
  • A hostile deployment operator. customTools command handlers run what the deployment configured; they are deployment-authored trust, always registered high-risk.
  • A compromised model provider. Inference traffic goes to whichever provider you configured. Local models via ollama remove that dependency.

Self-audit checklist (run before every public safety claim)

Failure classes below are the ones that burned other agent deployments in 2026. Each gets re-checked, on the released binary, before we say the word “safe” anywhere:

  • ☐ Fresh install: no plaintext key written anywhere under ~/.jazz when the keyring is available.
  • ☐ Bridge .env handling: bot token never logged, never echoed into transcripts.
  • ☐ Default agent toolset after the wizard: confirm execute_command posture and that the default approval policy asks before mutations.
  • read-only tier semantics: enumerate exactly which outbound requests it permits, and document that list.
  • ☐ Port scan of a default docker compose up bridge: nothing listening except /health.
  • jazz bench safety tripwire suite passes 10/10 on the release candidate (planned — see the eval harness).
  • ☐ Webhook mode: secret required, requests without it rejected.

Reporting

Vulnerabilities: see SECURITY.md. Reports that demonstrate any checked item above failing are treated as release blockers, not enhancements.

machine-readable: /docs/internals/threat-model.md · /llms.txt · /llms-full.txt