skip to content
Threat model

Security & governance

Threat model

A security posture is worth exactly what it admits it cannot stop. Here is the line, with both sides drawn.


Running the model on your own hardware closes a class of risk by construction. There is no inference server to breach, no prompt log to subpoena, no vendor that can rewrite its retention policy on you next quarter. The local-first guarantee spells out why that holds. It is not the same thing as “safe.” Moving the computation onto your disk changes which threats matter; it does not delete the category. This page names the ones Conifer takes responsibility for, the ones it deliberately does not, and the seam between them.

Who the attacker is

Conifer assumes a capable attacker who cannot run code on your machine but can reach it through content: a poisoned document, a web page an agent fetches, a calendar invite, a note synced from a shared drive. The text is hostile. The hardware underneath it is yours. Every defense in the runtime rests on that split.

The corollary carries as much weight as the claim. An attacker who already owns the operating system, the user account, or the silicon sits outside this boundary. So does one who can swap the weights file on disk before you load it. Conifer did not patch those poorly. A local runtime structurally cannot answer them, and a page that pretended otherwise would be worse than one that says so plainly.

What Conifer defends against

The hostile-content attacker has a short list of moves, and each one meets a named defense that owns its own page. The runtime bounds the blast radius. Assume a manipulation lands, then make sure it reaches almost nothing.

The threats inside the boundary, and the page that owns each defense
ThreatWhat stops it
A forged role marker in fetched content fakes a system turn.The untrusted wrap defuses chat-template markers so tool output can’t open a privileged turn.
A model is talked into a tool call it shouldn't make.Grants are deny-by-default and kernel-enforced; a call outside the list is refused before it runs.
Malformed tool output smuggles structure into the parse.Constrained decoding forces every tool call to parse, one decode step at a time.
A captured turn tries to exfiltrate what it read.Local-first means nothing leaves by default; there is nowhere to send it.
A page rebinds a hostname to localhost and POSTs to your server.conifer serve binds loopback and checks the Host header, answering anything else with a 403.

Each row is one page's whole subject. This table is the index, not the explanation.

The shape repeats on purpose. Not one of these defenses trusts the model to behave. The wrap is a string transformation, the grant check is code below the model, the parse is enforced at the token level. A small local model can be talked around by ordinary prose, so the architecture assumes it will be and puts the real gates somewhere a sentence cannot reach.

What Conifer does not defend against

These threats sit outside the line. Some land there because no local runtime can touch them. Others because the defense belongs to you, not to the software. Either way, name them.

A compromised host
Malware already running as your user reads the same files, memory, and keychain the runtime can. On-device security inherits the machine’s posture. It cannot exceed it.
A persuaded human
Grants for running a program or scripting another app act with your authority. The runtime gates each call and the OS prompts per app, but a grant you approve for a tool you shouldn’t trust is your decision, not a bug.
A model that is wrong
Conifer keeps an answer private and structurally valid. It makes no claim that the answer is correct, unbiased, or safe to act on. The output quality is the model’s, and the open weights are whatever their authors trained.
Tampered weights
A weights file you swap in, or pull from a hostile mirror, is loaded as given. Verify the source the way you would any binary. The runtime trusts the file you point it at.

Trusting the software itself

One assumption underwrites everything above: the binary you run is the one Conifer built. The Mac app is signed with an Apple Developer ID and notarized, so Gatekeeper verifies the signature on first launch and the auto-updater installs only builds from a signed release feed. (Windows code-signing trails the Mac build. Until the certificate lands, SmartScreen flags an unknown publisher, which is the honest state to read on the install page.)

The runtime reports on itself honestly too, its own defense against a forged claim. A build can write a signed posture document stating its version, whether the real release key signed it, whether an org policy manages it, and that telemetry is none by construction. A development build reports release_signed: false rather than lie about it. Collecting that across machines without a beacon is posture attestation. Riding policy on MDM and an expiring binary instead of a surveillance server is MDM policy.

Where to go from the boundary

This page draws the line. The neighboring pages each live on one side of it, and every fact stays on exactly one.

Read nextWhich side of the line it covers
The local-first guaranteeWhy on-device removes the server-side threat class in the first place.
Data boundariesExactly what does and doesn't cross the boundary, line by line.
Untrusted outputHow content the model didn't write is contained mid-turn.
The grant modelThe kernel gate that caps what any tool call can reach.