Security model

How HEADING OS protects your data and your principal. The controls here are

How HEADING OS protects your data and your principal. The controls here are mechanical, enforced by code and tests, not by policy prose. This page explains the model; the SECURITY.md policy covers how to report a vulnerability.

The whole design answers one question: an agent reads your private data and reads untrusted text from the outside world all day. How do you keep it from being steered into leaking the first through a channel to the second?


1. The threat: the lethal trifecta

An AI agent becomes dangerous to its principal when three capabilities meet in the same run:

  1. Access to private data (CRM, the brain, mail, the datastore).
  2. Exposure to untrusted content (inbound mail, web pages, messages, anything a stranger wrote that the agent reads and might be steered by).
  3. The ability to send externally (email, messages, any outbound channel).

Any one or two legs is recoverable. All three together is the failure mode: untrusted content instructs the agent, the agent reaches private data, the agent exfiltrates it, with no human in the loop. HEADING OS handles private data and reads untrusted content constantly, so legs 1 and 2 cannot be removed without removing the assistant’s value. The mitigation is therefore to keep leg 3 permanently human-gated, and to keep the private data physically out of the shareable engine.


2. Engine and data, kept apart

The engine clone holds no private or personal data, and your data cannot leave on a push, regardless of how a file was written. This is not a convention you have to remember: it is enforced by several mechanical layers, each catching a different way the boundary could be crossed:

The authoritative specification, the honest boundaries, and the proof (the layers are asserted by tests, not just claimed) are in the engine/data segregation contract. Read it before adding code that writes files or touches the data seam.


3. Outbound send is always human-gated

Every skill, script, and daemon inherits one control: anything that can send to the outside world is drafted, queued, and recommended, never sent autonomously. A human approves before anything leaves.

This is enforced in code, not prose. Any action type that can send is floored to the gated tier no matter what a config file claims, and an unknown or unclassified type also resolves gated (fail-safe). A config/tool-risk.json edited to mark email “autonomous” still resolves gated. The test suite asserts that a tampered ledger cannot auto-send: the ledger is data, the send-gate is code.

An advisory layer may inspect a queued draft and attach a second opinion, but it can only annotate. The human click is the only path from draft to sent.


4. The Action Queue (the approval surface)

The Action Queue is the one lane where proactive skills (cold-sweep, email-intel, viraid) deposit a drafted action for your go or no-go. It is terminal-native and daemon-free: the queue file is the source of truth, and you drive it from the CLI or from chat.

uv run python scripts/action-queue.py list          # what is waiting
uv run python scripts/action-queue.py show <id>     # inspect one draft
uv run python scripts/action-queue.py approve <id>  # SENDS, synchronously, watched
uv run python scripts/action-queue.py edit <id>     # adjust a draft before approving
uv run python scripts/action-queue.py dismiss <id>  # drop it
uv run python scripts/action-queue.py retry <id>    # re-send a failed one

/queue is the chat equivalent. Each card carries a risk tier:

Tier Meaning Flow
autonomous read-only / display (a note, an alert) surfaced; no click; never sends
notify reversible state edit auto-applied, with one-click undo
gated irreversible outbound send hard review gate: sends only on your explicit approve

Your typed approve IS the human approval click, and the send happens in that same command. There is no autonomous background send; the gate holds even with every daemon down.


5. Secrets never reach a remote

Credentials load only from a gitignored .env, never from a tracked file. Two gates back this:

Run pre-commit install once per clone to arm the commit-time gate. If a secret is ever exposed, treat it as compromised: rotate it first, then scrub it from history.


6. Other controls


7. Your responsibilities

The engine enforces a great deal, but the first line of defense is you:


8. Reference

File Role
THREAT-MODEL.md Threat to control to test evidence map (every control, the test that proves it)
SECURITY.md Reporting policy + posture summary
engine-data-segregation-contract.md The engine/data guarantee, layers, and proof
scripts/action-queue.py The terminal-native approval surface
scripts/utils/tool_risk.py, config/tool-risk.json The tier model and the send-gate invariant
scripts/push-all.py The push path with the unbypassable content scan
scripts/sanitize-text.py Hidden-character scanner

Manual security drills

Some enforcement layers cannot be exercised headlessly, so they are verified by a periodic manual drill rather than a green test. Listing them here keeps the gap explicit rather than silent. The mechanical leak-path matrix (tests/security/test_leak_path_matrix.py) attacks every headless-testable segregation layer on purpose (write-vector by data-class-target, asserting each leak is blocked by the expected layer); the drills below cover the layers it cannot reach.

data-path-redirect hook (PreToolUse Write/Edit)

The data-path-redirect hook fires only inside the Claude Code runtime, so no pytest cell can exercise it without asserting a simulation instead of the control. Drill it by hand, on a cadence:

  1. In a live Claude Code session on an engine clone, attempt to write a data-class path inside the engine tree (for example, ask Claude to create crm/contacts/drill-check.md).
  2. Confirm the write is redirected into the data overlay (.heading-os-data/crm/contacts/drill-check.md), not created under the engine clone.
  3. Confirm the engine working tree stays clean: git status shows nothing under crm/.
  4. Remove the drill file from the overlay afterward.

Expected observable: the file lands in the data overlay, never in the engine clone; the engine tree remains code-only.


HEADING OS · Security model · maintained by Misha Hanin · see also Extending the engine for the developer-side gates and Architecture for how the controls sit in the whole.