Security model
honeycomb captures coding sessions and memories, which is some of the most sensitive data a developer tool handles. Its security model rests on one structural decision: the daemon is the only process that talks to storage, so the storage-facing attack surface is a single chokepoint where scoping,…
Security model
Derived from the honeycomb knowledge base, captured 2026-06. Written for an external practitioner. This page describes the design and the contracts; it does not include internal operational runbooks. Confirm any version-specific detail against your installed version.
#The concept
honeycomb captures coding sessions and memories, which is some of the most sensitive data a developer tool handles. Its security model rests on one structural decision: the daemon is the only process that talks to storage, so the storage-facing attack surface is a single chokepoint where scoping, escaping, encryption, and isolation all live. Everything else (hooks, the CLI, the SDK, MCP tools) is a thin client that asks the daemon to do work and never holds a storage handle.
#Trust boundaries
flowchart TD
userBrowser([User browser])
agentProcess([Coding assistant process])
hookProcess([Hook / thin-client process])
daemon([honeycomb daemon, port 3850])
credFile([~/.deeplake/credentials.json])
store([Storage: GPU SQL/vector])
tenant([Org/workspace partition])
userBrowser -- "OAuth approval over HTTPS" --> daemon
agentProcess -- "spawns hooks, same OS user" --> hookProcess
hookProcess -- "read 0600 file" --> credFile
hookProcess -- "local loopback RPC" --> daemon
daemon -- "the only path to storage" --> store
store -- "org/workspace scoped rows" --> tenantThe single most important property of the map is that no process other than the daemon has a line into storage.
| Zone | Trust level |
|---|---|
| User browser (OAuth approval page) | User-trusted, separate from the assistant |
| Coding assistant process | Host OS user |
| Hook / thin-client process | Same OS user as the assistant |
| honeycomb daemon | Same OS user; the sole storage authority |
| Credentials file | Mode 0600, OS user only |
| Storage | Reached only by the daemon; tenant isolation enforced here, encrypted at rest |
Consequences of the chokepoint: a compromised hook can ask the daemon to do work on the user's behalf, but it cannot reach storage directly and cannot read another organization's data, because the daemon re-derives scope from the validated token on every request. SQL construction and escaping live in the daemon, so a thin client cannot smuggle raw SQL to storage. Tenant isolation is enforced at the storage layer, not at a client a user could patch.
#Authentication and authorization
honeycomb separates who you are from what you can do.
Identity comes from an OAuth 2.0 Device Authorization Flow that mints a long-lived, organization-bound token, written to a local file at mode 0600. No password is ever sent, and the short-lived access token is discarded rather than persisted.
Authorization is mode-aware. The daemon runs in one of three modes:
| Mode | Posture |
|---|---|
local |
No authentication; the daemon binds to localhost. For a single developer. |
team |
Every request needs a valid token or API key; unauthenticated requests get 401; all operations are rate-limited and scoped. The default for a shared deployment. |
hybrid |
Localhost requests are trusted by the TCP peer address from the socket (not the spoofable Host header); remote clients must present a token; missing socket info fails closed. |
In team and hybrid modes, four roles (admin, operator, agent, readonly) map to permission sets, and admin, token, diagnostics, source, connector, secret, ontology-mutation, and org or workspace routes always carry an explicit permission check. Remote connectors use named API keys: revocable, stored hashed, prefixed hc_sk_..., printed once at creation, narrowable with an explicit permission list, and bindable to a connector, harness, agent, and allowed projects.
#Token handling at boundaries
The access token is read from disk at hook startup and handed to the daemon. It is never passed as a command-line argument (which would be visible in a process list) and never written to a child process environment. Only the daemon makes the network call to storage, over TLS, with the token in an HTTP header rather than a URL parameter. Token-adjacent log messages are written to standard error, not standard output, so callers that read hook output as structured data cannot parse them.
#Resolving token drift
If a user switches organizations, the stored active organization can disagree with the token's organization claim, which would otherwise query the wrong tenant. The daemon heals this on session start: it decodes the token's organization claim, compares it to the active organization, and re-mints a corrected token if they disagree, before any request reaches storage.
#Scoping and visibility
honeycomb scopes memory in two rings, and both must hold for a row to be visible.
The outer ring is tenancy: organization and workspace, enforced at the storage partition so two workspaces never share a row, partition, or index. The organization and workspace passed with every request are validated server-side against the token's organization claim, so a token minted for one organization cannot read another by editing a header or the credentials file.
The inner ring is the agent: within a workspace, every read and write threads an agent_id, and an agent's roster row carries a read policy:
| Policy | What the agent sees within its workspace |
|---|---|
isolated (fail-closed default) |
Only its own memories |
shared |
Workspace-global memories plus its own |
group |
Global memories from agents in the same policy group, plus its own |
The inner ring is compiled into a SQL clause that every memory query carries, so a new code path either includes it or does not, which makes scoping auditable. The outer ring is enforced beneath it, so even a buggy inner clause cannot cross a workspace boundary.
#The authorization boundary in recall
Recall's candidate channels (full-text, vector, graph traversal, hints) cast a wide net, so the defense is ordering: those channels produce identifiers only, and the scope clause authorizes candidates before any content loads. Every content-bearing stage that follows runs only on the authorized set, so a strong vector hit or a high-degree entity can surface an identifier but cannot leak content past the read policy.
#Fail-closed posture
The subsystem leans toward refusing rather than over-sharing. A malformed caller falls back to isolated rather than widening access. Tenancy, scope, graph policy, mutation gates, and source access all fail closed. Failures return structured errors with enough context to diagnose, rather than silently downgrading. When in doubt, deny.
#Secrets: usable, never readable
If an assistant could read an API key, a single prompt injection could exfiltrate it. honeycomb breaks that link: secrets are encrypted at rest, an assistant can cause them to be used, and an assistant never receives the decrypted values. Secrets are the one class of data that does not live in the shared store; they sit encrypted on the daemon host, so even a full dump of the store yields no credentials.
- Encryption. Secrets are stored as encrypted files (mode 0600, directories 0700) using an audited, zero-dependency cipher. The key is derived from a machine-bound identifier and scope-bound, so copying the encrypted tree to another machine yields nothing usable.
- No read path. The API exposes secret names but never values. There is deliberately no "read a secret value" endpoint, through the API, the SDK, MCP, the dashboard, a connector, or diagnostics.
- The exec model. To use a secret, a caller queues an exec job. The daemon resolves the references, spawns a subprocess with the secrets in its environment under a timeout and a bounded worker pool, and redacts any secret value from the output before the caller sees it. A command can authenticate to an external service without the credential ever passing through the agent's context.
The local secret store generalizes into a single machine-bound encrypted vault that holds typed record classes behind one seam, with each class declaring its read posture (a value-returning setting versus an internal-only secret) as data, so a secret can never be read through the settings path. A credential-copy migration into the vault is non-destructive by construction: it copies the login token and performs zero writes to the original credentials file, which stays authoritative.
#Telemetry egress
honeycomb may emit anonymized operator telemetry from the daemon to an operator-owned analytics backend, for install-funnel attribution and operational health. This is the one outbound boundary other than the daemon-to-storage path, and it is governed by a single non-negotiable rule.
The content versus operation bright line. Telemetry may describe how the tool behaves (counts, durations, versions, states, error classes). It must never describe the content the tool handles (memory or session text, code, prompts, recall queries, file paths, working directory, repository or branch names, organization or workspace names, identities, secrets). The test for any property is the shrug test: would the user shrug if they saw this value in plaintext? If they would lean in and squint, it does not ship.
Boundary invariants worth knowing as a practitioner:
- Daemon-only emitter through a single chokepoint with a hardcoded allow-list; a structural test asserts the banned set is absent from every event.
- No item-level egress: no per-memory, per-query, or per-file events, because the cardinality itself is a signal.
- Tiered consent: operational lifecycle events are opt-out; usage-count events are opt-in. Setting
DO_NOT_TRACK=1orHONEYCOMB_TELEMETRY=0silences all of it, and an unkeyed build emits nothing. - Glass-box:
honeycomb telemetry --showrenders, in plaintext, exactly what has been and would be sent, so the displayed set is provably the egress set. - Anonymous identity: the distinct id is a random per-machine install id, never an email or a content-derived hash, and the ingest key is write-only.
This boundary is independent of capture opt-out: HONEYCOMB_CAPTURE=false governs what your memory records into storage; the telemetry switches govern what operational metadata leaves for the operator.
#Hook consent
honeycomb installs hooks into assistant lifecycle events, and each assistant platform enforces its own consent model before running foreign hooks (a trust prompt, a marketplace approval, an operator-controlled config file, or a user-controlled directory). No hook runs silently without an explicit user action, and the install command shows a one-line consent notice before opening the browser for authentication.
#Data classification
| Data type | At rest | In transit | Access scope |
|---|---|---|---|
| Access token | Plaintext, mode 0600 | Bearer header, daemon to backend over TLS | OS user only |
| Secrets | Encrypted, decrypted in the daemon on demand | TLS | Scoped per the secrets rules; never returned |
| Session traces | Encrypted at rest in the tenant partition | TLS | All members of the organization workspace |
| Memory summaries | Encrypted at rest | TLS | Organization workspace members |
| Operator telemetry | No content at rest beyond the local event log | TLS, write-only ingest key | Operator only; opt-out and glass-box; never carries content |
Workspace-level isolation is the outer boundary; within a workspace, members share the trace and skill surface by design, with the agent read policy narrowing where the engine enforces it.
#Where to read next
- Architecture overview: the daemon-as-chokepoint design.
- Recall and retrieval: the authorization boundary in recall.
- Data and storage: how tenant isolation is enforced at the storage layer.
- API reference: the auth modes, roles, and status codes.