Skip to content

Security

Security is a first-class subsystem in Graphorin, not an afterthought. @graphorin/security ships:

  • SecretsSecretValue wrapper, SecretRef URI scheme, OS keychain integration, optional encrypted-file store. See Secrets for the full sub-page.
  • Sandbox tiers'none', 'isolated-vm', 'docker'.
  • Server-token authentication — HMAC-SHA256 with a deployment-wide pepper.
  • Audit log — SQLite database with mandatory encryption-at-rest and a SHA-256 hash chain.
  • OAuth 2.1 with PKCE — outbound flows for MCP servers and skill registries.
  • Supply-chain helpers — Ed25519 signature verification for distributed skills.
  • Lateral-leak defense layer — composes orthogonally with the agent runtime's safety primitives.

Sandbox tiers

TierBacked byDefault for
'none'The Node.js process.First-party tools.
'isolated-vm'isolated-vm (peer dependency, ISC).Untrusted JavaScript skills.
'docker'dockerode (peer dependency, Apache-2.0).Untrusted binaries / full subprocess isolation.

isolated-vm and dockerode are opt-in peer dependencies — they are not installed by default, so a base install pulls in zero native sandbox code. Add them only if you load untrusted code.

Sensitivity model

Every message, memory row, tool result, and trace attribute carries a Sensitivity tag:

TagMeaningWhere it can flow
publicNo restrictions.Anywhere.
internalOperator-private but not user-secret.Local trace + opt-in collectors; never to providers without acceptsSensitivity: ['internal'].
secretUser secret.Never leaves the process. Memory rows tagged secret are filtered before any payload reaches a provider.

The default for an unfamiliar provider is deny everything except public until you opt in. The default for an exporter is never secret, and you cannot override it.

Server-token authentication

The standalone server (@graphorin/server) requires every authenticated REST / WebSocket / SSE connection to present a bearer token signed with HMAC-SHA256 against a deployment-wide pepper. The unauthenticated /v1/health probe is exempt so liveness checks work before token verification is wired. Tokens are generated and rotated through graphorin token:

bash
graphorin token create --scope agents:invoke --ttl 30d
graphorin token list
graphorin token revoke <token-id>

The pepper itself is resolved at server boot through a SecretRef (typically stored under keyring:graphorin_server_pepper or the encrypted-file store). See Secrets for the resolution pipeline.

Audit log

Every privileged operation writes one row to the audit log:

  • secret access (read / write / list);
  • tool execution (start / end / approval);
  • memory mutations (write / supersede / forget);
  • skill installs (with signature verification result);
  • token issuance / revocation;
  • OAuth flows (initiation / token issuance / refresh).

The audit log lives in a dedicated SQLite database with mandatory encryption-at-rest (via better-sqlite3-multiple-ciphers) and a SHA-256 hash chain that links every row to its predecessor. Tampering breaks the chain.

The CLI commands graphorin audit list / graphorin audit verify walk the chain and report any breaks.

OAuth 2.1 with PKCE

The client is built on openid-client (MIT). Token storage uses the configured secrets store (OS keychain by default). Refresh happens lazily on the next call — no background daemon ever phones home.

Supply-chain pipeline

Loading from npm-package or git-repo always:

  • runs the install with --ignore-scripts enforced (no postinstall execution);
  • fetches the publisher's Ed25519 public key from the configured well-known URL;
  • verifies the package's bundled signature against the resolved key;
  • writes one audit row recording success or failure.

Local folder installations are trusted-by-default but flow through the same validator pipeline.

Lateral-leak defense layer

The agent runtime's defense layer composes orthogonally with the security primitives above:

LayerPurpose
causalityMonitor (createAgent({ causalityMonitor }))Implements an Agentic Reference Monitor pattern. Every cross-agent flow is checked against the stated capability.
mergeGuard (createAgent({ mergeGuard }))Per-child trust scoring + bias detection on the 'judge-merge' fan-out strategy.
protocolGuard (createAgent({ protocolGuard }))Control-character escape catalogue applied at protocol boundaries.
Commentary-phase trace sanitisationAt the session-output boundary, before any export.
Inbound sanitisation preambleWhen non-trusted content is in the message list, a locale-resolved preamble is appended after the cache breakpoint.

Threat model

Graphorin's design assumes a STRIDE threat model across eight trust boundaries:

  1. User application <-> Graphorin runtime.
  2. Runtime <-> provider adapter.
  3. Runtime <-> tool execution.
  4. Runtime <-> skill loader.
  5. Runtime <-> MCP server.
  6. Runtime <-> storage layer.
  7. Runtime <-> standalone server (REST / WebSocket / SSE).
  8. Standalone server <-> operator (CLI, OAuth flows, audit).

The full threat model is summarised in Design principles.

Hardening

The CLI ships graphorin doctor — a single command that audits POSIX file modes on the secrets store, the audit log, and the database, plus the systemd unit template (where applicable):

bash
graphorin doctor

Failures are categorised by severity and emit actionable remediation steps.

Next steps


Graphorin · v0.1.0 · MIT License · © 2026 Oleksiy Stepurenko