G
YouTube TikTok Games
Skip to content

Security: osaurus-ai/osaurus

Security

docs/SECURITY.md

Security Policy

We take the security of Osaurus seriously. If you believe you have found a security vulnerability, please follow the process below.

Supported versions

Two products ship from this repository (see RELEASING.md):

  • Osaurus 1.x — main and the latest 1.Y.Z release are actively maintained.
  • Osaurus Classic 0.25.x — the classic branch gets fixes only, including security fixes, shipped as the next 0.25.N release, until Classic support ends.

Older releases of either line may not receive security updates. When reporting, say which product and version you are on (Osaurus menu → About Osaurus, or osaurus --version).

Reporting a vulnerability

Please do not disclose security issues publicly. Instead, use one of the following private channels:

  1. Open a private report via GitHub Security Advisories for this repository
  2. If you prefer email, contact the maintainers privately (do not use a public issue)

What to include in your report:

  • A clear description of the issue and impact
  • Steps to reproduce, including sample input and configuration
  • Any known mitigations

We will acknowledge receipt within 72 hours, assess the impact, and work on a fix. We may request additional information for reproduction.

Disclosure

Once a fix is available, we will credit reporters who wish to be acknowledged and include mitigation instructions in the release notes when applicable.

Hardening notes

These are the boundaries Osaurus relies on. Detailed mechanisms live in IDENTITY.md, SANDBOX.md, and STORAGE.md.

  • Sandbox bridge identity is bound to a per-agent token written into the guest VM as mode 0600 files owned by the agent's Linux user. The host bridge fails closed (401) on missing or unknown tokens; caller-supplied identity headers are not trusted. See SANDBOX.md → Bridge authentication.
  • Bridge route scoping: agent dispatch rejects body agent_id mismatches with 403; agent memory query filters to the calling agent's pinned facts.
  • Pairing credentials issued by relay invites (/pair-invite) are agent-scoped and expire in 90 days by default. The iPhone joins the user's Sync account and its secrets reach the Mac sealed to a one-use key (see SYNC_MOBILE_CONTRACT.md). The 6-digit HTTP pairing (/pair/code, SPAKE2) remains for iPhone apps without Sync until an iPhone joins Sync, after which those routes answer 410. Minted keys are HPKE-sealed to an ephemeral connector key so they never cross the wire in plaintext, the unauthenticated pairing endpoints are rate-limited per source IP, and fresh keys are redacted from request logs. The old Bonjour LAN /pair flow is retired; see IDENTITY.md → LAN pairing (retired).
  • Removed sync devices: other devices turn away a removed device's sessions, but a revoked device that wasn't wiped still holds the account secret. Remove Device… therefore defaults to Remove and Change Phrase, which replaces the account identity; Remove Only is for a wiped device. Showing the recovery phrase or a pairing code, removing a device, restoring and signing out ask for Touch ID or the login password. Writes made before removal can still arrive through another device; the Mac's iPhone feeds ignore requests and answers older than 24 hours and only settle items that are still pending. See SYNC.md → Removing a device.
  • Relay tunnel traffic is never loopback-trusted: requests proxied from the public relay into 127.0.0.1 carry an internal origin marker and always pass the full auth gate, CORS origin rules, and remote blocks — even when network exposure is off. /pair-invite supports the same HPKE sealed-key delivery, so the relay operator never observes plaintext credentials. See IDENTITY.md → Relay Tunnel Trust Model.
  • Agent-to-agent traffic is end-to-end encrypted (Osaurus Secure Channel v1): a signed-ephemeral X25519 handshake with per-session ChaCha20-Poly1305 keys gives forward secrecy; the server signs the transcript with its agent key and the client verifies it against the address pinned at pairing (anti-MITM). Bearer tokens, prompts, and responses travel only inside the ciphertext — the relay becomes a blind pipe and a LAN observer sees nothing after pairing. Replays are rejected by a sliding sequence window, streams end with an authenticated fin frame so truncation is detected, and non-loopback plaintext requests to /agents/{id}/run and /dispatch are refused with 426 Upgrade Required (no downgrade path). See SECURE_CHANNEL.md for the full feature document and IDENTITY.md → Secure Channel for mechanism details.
  • Access key scoping and freshness: agent-scoped keys are rejected with 403 on other agents' routes — both the run route (/agents/{id}/run) and the metadata route (GET /agents/{id}, which exposes the agent's name, description, and effective model), so a paired peer cannot enumerate other agents' metadata — and the server's validator snapshot is epoch-invalidated on key generation, revocation, whitelist, and agent changes so revocations apply without a restart. Loopback callers can omit a real key only while the server is not exposed to the network; LAN and relay callers always pass the auth gate. User-triggered revocation records the key in the revocation store and removes the key metadata from access-key lists so revoked credentials do not linger as selectable keys.
  • Per-agent working folder (authenticated remote file access): an agent has at most one sticky working folder, set from the chat composer's Folder chip or Agents → the agent → Abilities → Working Folder, stored as a machine-local security-scoped bookmark on the agent (Agent.workingFolderBookmark; legacy key hostWorkspaceBookmark is still decoded) that never crosses the wire to a paired peer. In-app chats and folder-less background dispatches use it as their host working folder under the normal host-folder execution mode. For remote callers, only an authenticated remote agent run (Secure Channel, agent-scoped) over /agents/{id}/run mounts it, and only the host file tools (file_read, plus file_write / file_edit, the only externally denied tools it unlocks) confined to that folder — shell_run, git_commit, and file_undo stay denied. The folder root is bound as a task-local only after the secure-transport, built-in-agent, and agent-scope gates pass, so loopback, plaintext, /mcp/call, and cross-agent callers never reach the relaxation. Concurrent host-folder runs are serialized so the process-wide folder-tool registration can't be corrupted, and a stale/unresolvable bookmark fails closed (the run falls back to sandbox/none). See OpenAI_API_GUIDE.md → External surface deny list.
  • Master identity overwrite protection: MasterKey.generate(allowReplace:) defaults to false and refuses to silently overwrite an existing master. Replacement requires the explicit Reset Identity or Restore From Recovery Phrase flows in Settings → Osaurus ID → Danger Zone. Drift between the current master and persisted agent / access-key derivatives is detected by IdentityHealthCheck and surfaced in a banner with three exit doors. See IDENTITY.md → Master Key Backup and Recovery.
  • Master identity backup: a BIP39 24-word mnemonic, reopened any time with View recovery phrase in Settings → Osaurus ID, is the only artifact that can rebuild the local secp256k1 master if the iCloud Keychain entry is ever lost or replaced. The one-time OSAURUS-XXXX-… recovery code is a separate server-side claim token and cannot restore the local key. See IDENTITY.md → Master Key Backup and Recovery.
  • Pre-auth request size limits on both HTTP servers: pairing routes 64 KiB, other public routes 32 MiB, sandbox bridge 8 MiB. Rejected with 413 before the auth gate runs.
  • Sandbox runtime artifacts are pinned and verified: GHCR image by multi-arch index digest, Kata kernel and initfs by SHA-256. Mismatches are fail-closed. See SANDBOX.md → Artifact Integrity.
  • At-rest encryption is opt-in (plaintext by default since 0.21.0): memory, knowledge, methods, tool index, tasks, automation and workflow runs, file history, the activity log, plugin and agent databases, sync session state, and large attachments are stored as plaintext by default (conversations are the exception: the osaurus-sync store is always encrypted with its own Keychain key), relying on macOS FileVault for full-disk at-rest protection. This is a deliberate walk-back from always-on SQLCipher: an always-required Keychain key coupled every store's open to a secret that breaks on Mac migration, re-signing, or a Keychain wipe, and failed closed with no recovery. Opening is now detection-first (the on-disk file header decides plaintext vs. encrypted), so a missing key can never brick a plaintext store. Users can opt in to SQLCipher / AES-GCM whole-database encryption (per-device DEK in macOS Keychain, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, not biometric-gated) from Settings… (⌘,) → Privacy & Security → Data & Storage, and existing encrypted installs are migrated invisibly on first launch of 0.21.0+ — decrypted to plaintext only when macOS FileVault is enabled (the disk is already encrypted at rest), or kept encrypted when FileVault is off so the migration never silently removes their only at-rest protection. See STORAGE.md → Why encryption is opt-in.
  • Other processes and agents under your account: FileVault doesn't protect ~/.osaurus/ from software running as you — a coding agent, a script, a compromised package. In plaintext mode they can read every store directly. With encryption on, the files they can reach are ciphertext, but the key is in your login Keychain, so a process running as you can still request it; macOS shows a prompt for apps outside the key's access list, and Always Allow grants it for good. Treat encryption as raising the cost of a casual or scripted read, not as isolation from code you run. See STORAGE.md → Threat model.
  • Turning encryption on never reports success it didn't achieve: the toggle resolves the storage key before changing the posture, reports a key it can't create or read instead of an empty "Encrypted 0 stores", and a key that can't be read at launch is recorded and shown in Data & Storage rather than silently disabling memory, methods, file history and the tool index (#3385). Key rotation is two-phase with a pending Keychain key, so a failure or crash part-way never leaves stores under a key that isn't installed. See STORAGE.md → Migration and Convergence.
  • Screenshot slash-command boundary: /screenshot is a local chat action, not a model-callable agent tool. It is gated by macOS Screen Recording permission, stores PNGs only in the chat artifact directory, records local artifact metadata without adding model-visible tool history or artifact host paths, does not auto-dispatch artifacts to plugins, and is unavailable to HTTP, MCP, plugin, and other external tool surfaces.
  • Key recovery posture (encrypted mode): there is no escrow key. In the default plaintext mode nothing depends on the Keychain, so key loss has no impact. When a user opts in to encryption, losing the Keychain entry (wiping the Mac without bringing the key, or copying ~/.osaurus/ to a different account) makes the encrypted artifacts unopenable. Osaurus never auto-deletes them: degraded stores are classified (locked / corrupt / migration) and surfaced in Settings… (⌘,) → Privacy & Security → Data & Storage and Memory → Diagnostics with Retry / Reset… (Data & Storage) and Retry open / Reset store… (Memory → Diagnostics) actions (quarantine-to-~/.osaurus/quarantine/, never delete), plus a one-click plaintext export to run before any risky migration. See STORAGE.md → Recovery.
  • Build reproducibility: SPM dependencies that previously tracked branch: "main" are pinned to commit revisions; CI is pinned to a specific runner image and Xcode version.

For ongoing development, prefer adding new boundaries via the same patterns: identity bound to file permissions or signed credentials (never headers), fail-closed defaults, finite expirations, redacted logging, and immutable digests for any external runtime artifact.

There aren't any published security advisories