Architecture

How NoxKey stores, protects, and delivers secrets — without trusting the caller.

System Overview

Two components, one Unix socket, zero network calls.

Developer Spotlight panel · menu bar UI
AI Agent Claude · Cursor · Codex · Windsurf
noxkey_get(...) over MCP · Cmd+Shift+1 in app
NoxKey System
Bundled MCP server noxkey-mcp.mjs (stdio) Process-tree agent detection JSON command protocol
JSON over Unix domain socket
NoxKey App Swift macOS application (single binary) SocketServer · SessionManager KeychainManager · AES encryption
LAContext.evaluatePolicy
macOS Security Framework
Touch ID LocalAuthentication framework Biometric + passcode fallback
Authenticated context
Keychain Services Security.framework kSecGenericPassword · service: "noxkey"
Decrypt from hardware
Secure Enclave Hardware encryption at rest Apple silicon coprocessor
Secret value returned
Agent or
human?
Agent
ChaChaPoly Encrypt Per-message key + 12-byte nonce AEAD-sealed JSON response
Self-Deleting Script Node ChaChaPoly decrypt chmod 600 · rm on source · 120s timeout
export KEY=value Process environment only Never written to disk
Human
Clipboard Auto-cleared after 30s Never printed to terminal

Data Model

Every secret is a Keychain item with structured metadata.

Secret (Keychain Item)

service "noxkey" account org/project/KEY value encrypted by Secure Enclave access Touch ID + passcode fallback

Secret Metadata (JSON label)

field_type api_key | token | password | totp_seed | username | email | url | text kind login | api_key | recovery_codes | custom security_level easy | normal | strict | off_limits peek first 8 chars (for verification, never the full value) bundle group label for organization desc human description

Session

prefix org/project/ pid resolved ancestor PID startTime boot-relative (anti-recycle) timeout default 4h, configurable cache in-memory only, never persisted

Known Caller

path /usr/local/bin/claude firstSeen ISO 8601 timestamp storage app sandbox container purpose first-seen warning on new callers

Security Boundaries

Five layers between a secret and an attacker.

Layer 1

App Sandbox

The app ships without com.apple.security.network.client, so the kernel blocks every outbound socket. No telemetry, no sync, no exfiltration path. Verify with codesign -d --entitlements -.

Layer 2

macOS Keychain

Secrets encrypted at rest by the Secure Enclave. Not readable without biometric or passcode authentication. No custom crypto — Apple’s implementation.

Layer 3

Touch ID Gate

Every read requires biometric auth. Strict mode secrets always require Touch ID, even during an unlocked session. Passcode fallback for non-biometric Macs.

Layer 4

Session Binding

Sessions are bound to a specific process tree ancestor (terminal PID + boot-relative start time). PID recycling is detected. Sessions auto-expire after 4h.

Layer 5

Encrypted Handoff

When an AI agent calls noxkey_get over MCP, the value is ChaChaPoly (AEAD) encrypted, written to a self-deleting temp script (chmod 600) that rms itself on first source, with a 120-second hard timeout as a safety net.

Layer 6

Agent Guardrails

Every noxkey_get response carries an in-tool instruction telling the model not to echo, cat, printenv, or hardcode the loaded value. The MCP server’s system instructions repeat the rule. The agent uses the secret through $ENV_VAR; the raw value never re-enters the conversation.

Agent Access Model

AI agents get secrets through five MCP tools. Raw values never enter the model’s context.

MCP ToolTierWhat happens
noxkey_show Core Browse the tree (no values). With an account: peek the first 8 chars to verify. No Touch ID.
noxkey_get Core Per-request approval card → encrypted self-deleting handoff. Full path = single secret; prefix = batch under one Touch ID. Optional session: '4h' keeps the prefix unlocked.
noxkey_set Core Store via clipboard: true (preferred — the value never enters the conversation). Always pass field_type (api_key, token, password, totp_seed, username, email, url, text). Touch ID required.
noxkey_scan Advanced · user-triggered Read-only scan for .env-family files. Returns key names + 8-char peeks, no values. Pair with noxkey_admin(action: 'import') to write the batch under one Touch ID.
noxkey_admin Advanced Maintenance actions: delete, meta, organize, import, plus org/project/email management.
Bulk-exfil variants Blocked When an AI agent is detected, request shapes that would return raw values (--raw, --copy, load, export, bundle) are refused.

Two path namespaces

Dev secrets use org/project/KEY (e.g. noboxdev/gitpulse/DATABASE_URL; use <org>/general/<KEY> when the project is unclear). Logins use login/<host>/USERNAME + login/<host>/PASSWORD (e.g. login/github.com/USERNAME) — one pair per host. Calling noxkey_get(account: 'login/github.com') returns BOTH USERNAME and PASSWORD in one handoff under a single Touch ID.

Session-based access

For longer tasks, pass session: '4h' (or similar) to noxkey_get. The Swift-side keychain stays unlocked for that prefix — subsequent calls within the window skip Touch ID. Sessions are per-prefix and revoke instantly when you quit the app.

Recovery rule

Handoff scripts are single-use: they rm themselves on first source, with a 120-second hard timeout. If source returns no such file or directory, call noxkey_get again for a fresh path — do NOT retry the same source command.

Forbidden patterns for agents

Agents must NEVER cat/read the handoff file (only source it), echo loaded env vars, or use env | grep / compgen to introspect them — the response already lists what was loaded.

How agent detection works

The bundled MCP server walks the process tree from the calling PID upward (up to 20 levels) using proc_pidinfo, checking each ancestor’s name against known AI agent signatures (Claude, Cursor, Codex, Windsurf, Copilot). The app independently verifies the same check. Both must agree before granting access. This runs on every MCP request — no config needed.

Threat Model

What NoxKey protects against, and what it doesn't.

ThreatProtection
Secret in .env file on disk Secrets stored in Keychain, not files. Drop your .env onto the import sheet — review the keys, Touch ID writes the batch — then delete the file.
Secret pasted into AI chat Encrypted handoff means noxkey_get never returns plaintext to the agent context — the value reaches the agent's shell as an env var via a self-deleting temp script, never as a chat message.
Secret in shell history Humans paste from clipboard via the menu bar UI; agents call MCP. Neither path puts a value on a command line.
Compromised agent exfiltrates keys Per-request approval card surfaces the agent and key before anything leaves the Keychain. Bulk MCP variants are blocked when an agent is detected.
PID recycling after session unlock Sessions store boot-relative process start time alongside PID. Recycled PIDs have different start times and are rejected.
Stolen laptop (disk access) Keychain is encrypted by the Secure Enclave. Without biometric or passcode, values are unreadable.
Malicious process as same user Not fully protected. A process running as your user can read /proc/PID/environ after a secret is loaded into env vars. NoxKey mitigates with single-use handoff scripts that delete themselves on source (with a 120-second hard timeout), but env var injection has inherent limits.
Network interception NoxKey makes zero network calls. All communication is via a local Unix socket with filesystem permissions.

How it compares

NoxKey vs common approaches to secret management.

Capability .env files 1Password CLI Proxy injection NoxKey
Secrets off disk No Yes Yes Yes
Biometric auth No Yes No Yes
AI agent detection No No No Yes
Encrypted handoff No No Yes (proxy) Yes (temp script)
Session scoping No Partial Per-session Per-prefix + PID binding
Zero network calls Yes No No Yes
Works offline Yes No No Yes