Skip to content
MoorAI
Get MoorAI
// trust & verification

Don't trust our privacy claim. Verify it.

MoorAI inspects AI prompts, files, and outputs for risk on the device. The console never receives your prompts, code or files unless an administrator turns on a capture tier. It receives which rule fired, the risk level and a keyed one-way hash (plus the login and hostname, which the console pseudonymises on arrival). That follows from how the agent is built, and the agent is open source, so you can confirm it line by line. Three exceptions are covered below, and each stays off until someone chooses it: an admin-enabled capture tier, image OCR in the desktop app where the operating system can't read an image locally, and an opt-in model second opinion whose provider setting sends the text it is asked about to Anthropic.

How MoorAI weighs evidence

An agent session throws off plenty of signals, and most of them prove less than they appear to. Four rules decide what MoorAI treats as a fact.

  • Events that line up are a lead, not proof of intent. In MoorAI: the console’s Diamond view marks every field observed, inferred or not established, so an egress elsewhere in the same session shows as inferred (“same session, not the same call”), and an adversary is named only by an analyst’s link, never filled in from the records.
  • One agent telling another to act grants nothing. In MoorAI: when an agent delegates to a sub-agent, the handoff is recorded, the prompt it passes is scanned for injection, and the sub-agent’s own tool calls are checked against the same policy and limits as its parent’s, so delegation never widens what is allowed.
  • An agent’s account of its own work is a claim, not evidence. In MoorAI: at the end of a turn, the agent’s “done” is compared with what its tool calls actually returned, and a success claim that failed, denied or interrupted calls contradict is reported; the message is read in memory and never stored or sent.
  • Decide from what is observed, label what is inferred, keep the content home. In MoorAI: each call is decided on the device, each alert carries a rule, a risk level and a keyed one-way hash in place of the text, and the console receives content only if an administrator turns on a capture tier.

Tamper resistance: enforcement the agent can’t switch off

A guardrail the agent can turn off is a suggestion. MoorAI is continuous in the literal sense: it checks each tool call before it runs, not the agent once at a point in time, and an enrolled device reports every day whether that is still true. Here is where MoorAI can’t be switched off, by whom, and what is detected rather than prevented.

  • Server mode puts the hooks in Claude Code’s managed settings. For claude -p in CI, the Claude Code GitHub Action and Agent SDK services in containers, the example Dockerfile writes MoorAI’s hooks into /etc/claude-code/managed-settings.json as root and runs the agent as an unprivileged user. Claude Code’s settings reference says “Only managed settings can disable managed hooks”, so a disableAllHooks in the repository’s own settings leaves them running. Server mode enforces without a token, and a verdict that would wait for sign-off is denied, because no approver exists.
  • A settings file can’t redirect it. Claude Code applies a settings file’s env block to the hook, so a pull request’s .claude/settings.json could otherwise point MoorAI at another console or supply its own policy key. In server mode, a MOORAI_* value set by a user, project or local settings file is refused and raised as a Critical, content-free alert naming the variable. On every device, laptop or server, a settings-file value for the policy or break-glass public key, the offline mode or the OpenTelemetry endpoint is ignored and reported. Managed settings, the launching environment and the root-owned anchor files are trusted.
  • In process, with the Agent SDK. @moorai/agent-sdk returns the hooks for the Claude Agent SDK’s query(), so the checks run inside your service and your code calls them; the model does not get to choose. Its PreToolUse decision matches the shell hook’s: a parity test over 214 payloads under two policy states found 0 mismatches. Claude Code keeps “running the hooks the Agent SDK registers in process” even when managed settings disable every other hook. The circuit breaker, session risk, intent alignment and a few other checks are not evaluated in process, and each result lists them.
  • A localhost sidecar for other agent loops. moorai-serve answers POST /v1/tool-call with the decision the hook makes for that call, for an OpenAI Agents SDK, LangGraph, CrewAI or custom loop. It binds loopback only unless started with --allow-remote and a token of 16 or more characters. It also ships as a public container image, ghcr.io/gitayg/moorai-server, that runs in the agent’s pod or compose network and is reached at 127.0.0.1; see Run it as a sidecar.
  • A local gateway for remote MCP servers. moorai-mcp-gateway sits in front of remote MCP servers and applies the stdio proxy’s tool-call and tool-result checks. On an enforcing device an explicitly denied call comes back to the client as a tool error instead of being forwarded; outside server mode, a call that would ask is forwarded. It has been run against a real remote MCP server, and real MCP clients (Claude Code, cursor-agent, the MCP SDK and MCP Inspector) have listed and called tools through it on macOS; Claude Desktop, VS Code, Windows, Linux and OAuth through it are untested, and so are its server-mode paths.
  • A runaway agent can be paused. The same call 15 times in 5 minutes with an unchanged result, or a 2–4 call cycle repeated 5 times, raises a runaway-loop alert; with policy.circuitBreaker.mode: "deny" the session’s tool calls pause for 15 minutes. Report-only by default, and a result that changes counts as progress.
  • When MoorAI can’t decide, the administrator chose the outcome. A check that throws, runs past its decision budget, can’t read its input or scanned only a capped prefix of what a call reads fails open or closed as set for its surface: the coding-agent hook, the model proxy, the MCP stdio guard and HTTP gateway, and the Inference hooks server. The setting comes from the signed console policy or the root-owned machine config (/etc/moorai/config.json), which wins; no environment variable, settings file or user-scope file is read, so nothing the agent can write turns a closed surface open. Each fail-closed refusal raises one content-free FAIL_CLOSED alert naming the cause, moorai-doctor shows the effective mode, and the console shows it per device in Inventory and Coverage, flagged when a device fails open against a closed policy. On the hook only the pre-tool check fails closed; post-tool scans, prompt checks, logging and inventory fail open, and an operator-signed, live break-glass marker turns a hook’s fail-closed deny back into an allow. An unenrolled device is never denied.
  • No alert number is handed out twice. Each alert carries a per-device sequence number and chain head, so the console sees alerts that never arrived. A number is taken only under a per-user lock and handed out only once the new head is on disk, so two alerts never share one and a crash never repeats or restarts the count; a live holder’s lock is never broken. An alert that can’t get a number within 200 ms is sent unnumbered, and the console reports a run of them as the low-severity finding Alerts sent without a sequence number. A power cut can hand out the last number once more, which the console reads as a counter reset, and on an unmanaged device the counters are user files.
  • What entered the context is anchored on every record. On Claude Code, each session keeps a Merkle tree (RFC 6962, the Certificate Transparency hashing) of what entered the agent’s context, one content-free leaf per item, and every alert and allow-trail record carries the tree’s root at the moment of decision. A blob of 256 bytes or more is hashed with plain SHA-256, so anyone holding the original can check it; a shorter one uses the tenant-keyed hash. With the policy set to send leaves, the console recomputes each record’s root, a sub-agent’s against the sub-agent’s own tree, and marks it verified, mismatched (tamper evidence) or leaves not held. The session file lives in the user’s home, so the tree is tamper-evident, not tamper-proof: a rewritten history yields a root that no longer matches the roots already sent. Leaves and roots reach the console with the install token, not device-signed.
  • On a laptop: detected, not prevented. The hook runs as the developer, so the developer, or an agent acting as the developer, can uninstall it or weaken a setting. An enrolled device sends at most one content-free heartbeat per agent host per day, saying whether MoorAI’s hook is registered and current and which settings weaken protection (hooks disabled, bypassPermissions, Codex approval_policy = "never", a sandbox off); the desktop app reports each agent host’s last activity on its own. The console raises Coverage: agent active, no MoorAI hook traffic, Coverage: agent setting weakened and Coverage: MoorAI hook removed or stale. A device under an MDM fail-closed latch keeps enforcing without a token, so removing the token is not a way out. An unenrolled device coaches and never blocks.

Sources: the agent’s README at v1.2.0 (server mode, the Agent SDK and sidecar, coverage integrity, remote MCP servers) and its example Dockerfile; for fail mode and alert numbering, the current README.

Content-free by construction: what leaves the device

Most “privacy-first” AI security tools ask you to trust a data-handling policy. MoorAI doesn't send the data at all. Every detection runs locally, on the endpoint, and by default the plaintext is never transmitted anywhere to be evaluated. What leaves the device in its place is a fingerprint: a keyed HMAC-SHA-256, truncated to 64 bits and tagged h2:, whose key is derived on the device from your tenant's install token. The key itself is never transmitted, but the install token is: the console issues it, and it travels in the header of every alert, so the console can derive the same key.

Why there is a key. An unkeyed digest of a small-space value isn't one-way in practice; it's an encoding. There are only about 1010 US phone numbers and 109 Social Security numbers: enumerate the space, hash every candidate, and read the plaintext straight back out of your own table. That was true of the 32-bit djb2 fingerprint MoorAI used to emit (measured reversible in 0–2 ms on a laptop), and it is equally true of a plain SHA-256, which adds no entropy the input never had. Only a secret mixed into the hash input defeats the enumeration. So there is a key. It is derived on your device and scoped to your tenant. It makes “one-way” accurate against anyone who holds the alerts without your install token; the console, which issued that token, can recompute it.

A sample fingerprint as the agent emits it: h2:9f3c07a1b26d84e5. It defends exactly the places the plaintext is not and the key isn't either: the console's alert store, the SIEM stream, a log copied off the box. It does not defend against an attacker who already owns the endpoint: that attacker reads the plaintext off disk, and no on-device hash changes that.

The console receives verdicts and metadata — what class of risk fired, when, on which device, mapped to which framework. The device is named by its login name and hostname, which the console replaces with keyed per-tenant pseudonyms on arrival and does not store; it does the same with the email of each AI-tool account a device reports. It never receives the text that produced the verdict. And because the on-device agent is licensed MIT, you can check this yourself: read the source, run the agent under a network monitor, and confirm no content egresses.

Leaves the device — verdicts + metadataStays on the device
Risk classification (e.g. OWASP LLM01 match)Prompt text
Keyed one-way fingerprints (h2:) used for matchingFile contents
Detection timestampModel output / responses
Login name and hostname (pseudonymised by the console on arrival) & policy identifiersAny reconstructable plaintext
Severity & rule idThe fingerprint key (derived locally, never transmitted)
W3C trace and span ids (hex) and the context tree’s root; the tree’s leaves (stage, source kind, hashes, size, time) only when the policy sends themThe path, URL or server name behind a context leaf

The right-hand column describes the default configuration; the three exceptions are covered below. Verify it in the source, then confirm it on the wire.

This website counts page views and link clicks with Plausible Analytics, which sets no cookies and stores no personal data.

Capture tiers: the first exception

Content is never stored by default. The one way it can be retained is a choice an administrator makes: an administrator can enable a capture tier for incident investigation or compliance evidence, and a capture tier does store the matched prompt or tool-call text it was turned on to collect. The choice is yours, per tenant, and visible. It is fenced accordingly:

  • Off by default. Out of the box no content is captured or stored — you have to turn it on.
  • Admin-gated. Only an administrator can enable it — not the agent, not a policy push you didn't author, not an end user.
  • Logged. Enabling capture is itself an audited event: who turned it on, when, for which scope.
  • Tier-limited. Scoped by tier rather than all-or-nothing: enable the minimum needed.

If you never enable a capture tier, MoorAI never stores content.

Image OCR: the second exception, desktop app only

A pasted screenshot has to become text before any policy can be run on it. Image inspection runs in the MoorAI desktop app. Where the operating system can read the image itself, it does: the Vision engine on macOS, the built-in OCR engine on Windows (it needs an OCR language pack), and Tesseract on Linux. The image is read locally, with no network call and no model bundled into the installer. Where no engine is available, the app can send the image to Anthropic’s API, using an Anthropic API key already on the device; it says so before it runs, and the extracted text is then checked against the same policy as typed text. With no engine and no key, image inspection is skipped, and the app says so.

That fallback is not content-free: on that path the image reaches a third party, Anthropic. It goes device → Anthropic directly and never reaches MoorAI. We don't see the image, and we don't relay it.

Model second opinion: the third exception, opt-in

Detection is deterministic by default, and no model is consulted. An administrator can turn on a second opinion for scans the rules were not confident about (modelEscalation together with semanticEscalation). In its local setting, only a model on the device’s loopback interface (Ollama on 127.0.0.1) sees the text. In its provider setting, when no local model answers, the text goes from the device to Anthropic’s API, using an Anthropic API key already on the device. Either way the answer is advisory: it never changes an allow or block decision, and the console receives only a content-free verdict.

Detection efficacy you can reproduce

A privacy guarantee is worthless if the tool can't catch anything. MoorAI ships an on-device benchmark that runs the shipped detection engine against the shipped threat matrix and an adversarial red-team corpus. It reports how many detectors and threats ship, how many red-team cases are caught, and which OWASP LLM Top 10 items have at least one on-device detector, with the threat and detector count for each. It does not report precision or recall; recall and false positives are measured on the separate Agentic Security Benchmark. Run it yourself:

npm run benchmark

Results are written to docs/BENCHMARK.md. Because it runs locally against the shipped detection logic, the numbers you produce are the numbers we publish, with no hidden test set and no vendor-tuned demo. Current results: 103 detectors, 102 / 102 red-team cases caught, and all 10 OWASP LLM Top 10 items covered by at least one on-device detector.

Trust roadmap: third-party audit and SOC 2

Two independent validations are planned. Here is what each will prove, and where it stands.

  • Third-party zero-egress audit — planned. An external firm will independently verify that the on-device agent transmits no prompt, file, or output content. Because the agent is MIT, the auditor works from the same source you can read. Status: planned, not yet started; we'll publish the report and scope when it completes.
  • SOC 2 Type II (console) — in progress. The console is pursuing SOC 2 Type II covering security, availability, and confidentiality of the backend that receives verdicts and metadata. Status: in progress; report not yet issued. We won't call MoorAI “SOC 2 certified” until an auditor issues the report.

The content-free mechanism and the reproducible benchmark are available today; the audit and SOC 2 are commitments in flight.

Security reports and advisories

Report a vulnerability through the contacts in our security.txt. Advisories for our own products are published on the security advisories page, each with a permanent URL, the fixing commit, severity reasoning and a timeline.

Check the claim yourself.

Read the code, watch the wire, run the numbers — then decide.

Read the source Back to MoorAI →