Security and privacy
Security and privacy
Holaryn Agent is local-first: the agent loop, memory, scheduler, and web dashboard all run on your machine, with no account and no vendor backend. This page explains where your data and secrets live, what the agent exposes on the network, and the layers that keep an autonomous agent safe to run.
Local-first by default
Everything durable — memory, run journals, schedules, parked approvals, chat transcripts — lives in SQLite databases inside a state directory on your machine. The only outbound traffic in a default setup is to the model providers you configure in Providers & Models. Optional features that add network surface (peering, webhooks, Telegram) are strictly opt-in.
Secrets
- Provider API keys and other secrets live in a write-only secret store (
secrets.jsonin the state directory), managed through Settings or the JSON API. The UI never reads secrets back to the browser. When encrypted local state is enabled, the entire secret store is authenticated and encrypted; missing or wrong keys fail closed and never reset it. - Configuration is environment-driven (
HOLARYN_*andANTHROPIC_*variables); env values override the store. Secrets are never written to config files — fields like the memory database password (HOLARYN_MEMORY_DB_PASSWORD) or a Qdrant API key are env-or-store only, and the settings editor cannot smuggle them into the config file. - For an OS service, set provider keys in the service account's or machine's environment, not in the generated service definition. See Running the agent.
State directory permissions
The host secures its state directory before loading anything from it, and fails closed if it cannot:
- POSIX: the directory is created (and kept) mode
0700— private to the owner. - Windows: the default state path lives under
ProgramData, whose inherited ACL commonly grants the localUsersgroup write access. The host replaces that with an exact protected DACL granting full control only to the current user, SYSTEM, Administrators, and the explicitly authorized service operator — so another local user cannot plant files the service would execute. - Paths that are symlinks or junctions are rejected outright.
Permissions are one layer, not offline protection. Optional
encrypted local state adds per-domain authenticated encryption for
classified secrets, journals, memory, artifacts, device state, and recovery backups. It is
opt-in pending independent security review. Its published inventory calls out uncovered
control-plane stores and residual metadata; full-disk encryption remains recommended.
The scoped secret broker keeps credential values out of prompts,
configs, transcripts, and general extension context. Exact, expiring leases resolve only inside
trusted provider, connector, signing, or process adapters; redaction remains defence-in-depth.
Network exposure
- The web UI binds
127.0.0.1:8765by default — reachable only from your own machine. Serving on any non-loopback address requires an explicit--auth-token(the operator token), and a DNS-rebinding guard rejects requests for other Host names. - The peer inbox does not exist until you set a peer token (
holaryn peer token set): it answers 404, so peering is opt-in. Peer tokens are scoped — they open only the inbox, never operator APIs — and are checked in constant time. - Open network mode removes the token requirement on trusted LANs; senders must still resolve to a registered, enabled peer, but any device on the network could then claim a peer's name. Use it only on a network where you trust every device. See Peers and networking.
- The host listener speaks plain HTTP. For cross-machine traffic, use Tailscale (or another WireGuard-class private network) or terminate TLS in front. Never expose the listener to the open internet.
- The local web boundary is hardened: streaming request-size limits, bounded voice and peer payloads, sandboxed artifact responses with inert content types, anti-framing and no-sniff headers, a restrictive baseline content-security policy, and a no-referrer policy.
The prompt-injection guardrail
Tool results and web content are untrusted input. A guardrail screens every tool result inside the loop before it is journaled or reaches the model. Three modes, set via Settings → Capability Center → "Prompt-injection scan" or HOLARYN_INJECTION_SCAN:
- warn (default) — flagged output passes through with a caution banner telling the model the content is untrusted data and instructions inside it must not be followed.
- block — flagged output is quarantined: the model receives only a withholding notice naming the matched rules, never the payload.
- off — no scanning.
The detector is a curated, offline, high-precision rule set: instruction overrides, covert
directives, prompt exfiltration, role smuggling, hidden HTML/Unicode, terminal escapes,
markdown-image exfiltration, encoded directives, and source data impersonating a control message.
Every flag records its detector version, source, bounded plain-text evidence, confidence, and
recommended control. Open the assistant message's Tool activity disclosure to inspect those
facts safely. Inbound peer and teammate messages, attachments, memory, repository/URL context,
connectors, MCP, and tool results retain untrusted provenance through replay and summarization.
Heuristics are a screen, not a proof — novel phrasings can pass. The hard backstop is an
information-flow policy: untrusted data cannot silently drive a write, command, secret access,
memory save, connector call, or outbound message. Keywordless transitions require your explicit
approval. A source already suspected of injection is blocked from consequential actions; that
block cannot be overridden from the approval dialog. Start a clean operator-directed turn or
remove the source instead. A new operator turn resets active lineage so an old warning does not
create permanent approval noise.
To verify the shipped policy offline, run holaryn bench security --fail-on-bypass. If you suspect
an incident, stop the run, record the event id/source/rule versions without copying the payload,
quarantine the source, and follow the private reporting instructions in SECURITY.md. Never paste
a credential or active attack document into a public ticket.
The approval gate is the safety boundary
Every tool carries a consequence (reversible or not, plus a category), and the autonomy posture decides what happens before a consequential action runs. At the default posture, reversible actions proceed and irreversible ones ask first. In unattended runs (schedules, teams, peers) nothing auto-approves: gated actions park in your Approvals inbox and the run waits. No message from a peer, teammate, or delegated child can approve anything — approval authority is yours alone. Details in Autonomy and approvals.
Enterprise deployments can add explicit organization/workspace scope, directory-backed roles,
deny-overrides organization policy, session revocation, legal-hold-aware retention planning, and a
tamper-evident audit chain. This layer can tighten but never weaken the local autonomy and
information-flow floors. See Organization governance.
Signed updates
The desktop app updates only from published GitHub Releases, after you consent. Every update artifact carries a minisign signature, and the public key is baked into the app bundle — the updater verifies the signature before installing, so a tampered or substituted artifact is rejected. A release-time guard also verifies each published signature was made by the key matching the bundled public key.
Other hardening
- Plugins and skills are inert until enabled. New, updated, and legacy plugins require explicit activation; install flows disclose the MCP commands and URLs a plugin would run, and installs go through validated staging trees that reject symlinks and junctions.
- Marketplace trust is layered. Signed packages and indexes are reverified against configured
trust roots, but signatures are not safety claims. Compatibility, permissions, data access,
dependencies, scans, human review, advisories, organization policy, and runtime containment are
enforced as separate signals. Revoked packages roll back or disable. - Subprocess output is bounded — a runaway child's stdout/stderr cannot exhaust memory, and its process tree is terminated on overflow.
- MCP calls that may have caused side effects are never silently replayed after a failure; the same principle drives the resume flow after a host crash, which parks unknown in-flight side effects for your decision instead of retrying them.