Run

wippy run bee/credentials

bee.credentials

The owner-local credential broker. A definition names a credential in a workspace from a source the host admits (bee.credentials:credential_sources: an env.variable entry, the workspace or *, the audience or *, a provider, the projection kinds allowed); its digest covers configuration and source identity, never bytes, and redefining it moves to the next revision so existing projections stop resolving. define accepts expected_revision: zero creates only when absent; a positive revision replaces only that exact definition. The revision check, write and returned view share one transaction. A mismatch returns CONFLICT without invalidating projections. Omission preserves explicit unconditional replacement. First-use setup must use zero and inspect a conflict rather than replace an existing login configuration. The allowlist is checked again at every check and materialize, so removing a source or an audience takes effect for projections already issued. A projection binds the authenticated subject, an audience, one attempt, the profile, binding and launch-policy digests, the provider's frozen declared destination, the materializer identity, an expiry and the workspace authorization epoch. check re-checks the bindings without bytes and reports file-source source_present from a stat when its host-selected source is available; an unavailable source leaves this field absent. availability is a manager-only metadata probe for an existing file definition and reports whether its admitted login file is present; materialize re-checks them for an admitted materializer and returns the value once, in a reply nothing persists, recording the generation key, the generation and the materializer's actor. A generation key is accepted once per projection: a lost reply is never repaired by a silent second read; the runner refuses the attempt instead. The source is read at materialization, so a rotation at an unchanged reference reaches the next materialization. A value that an environment cannot carry (NUL, line breaks, over 8 KiB) is refused without being echoed. Materializer authentication is entry-scoped: the materialize action is attached to the placement service and runner entries and to no caller-selectable scope.

define accepts optional: true for file and environment sources. A missing optional source consumes the generation and returns its definition metadata with present: false and no value; the next generation can try again. Populated projections return present: true and the optional flag alongside their bounded bytes. Optional environment absence also carries its fixed destination and UTF-8 encoding so placement can validate the reply before omitting the variable. Missing required sources, permission failures, invalid JSON and other source failures remain errors. File materialization opens the admitted path directly and treats only a NOT_FOUND open error as absence. An existence probe cannot distinguish a missing file from an unreadable path; permission-denied open errors refuse an optional projection too.

The host's data.formats map selects reviewed component-owned registry entries whose data declares an environment destination or a relative login layout. Claude and Codex declare their API-key destinations and JSON login files; Agy declares an opaque file. File sources name a host-selected fs.directory using source.kind: fs_directory. The host source row selects the source path; when omitted, it uses the declared login basename. Callers cannot choose a path, filename or mount. A file source may also carry one host-selected setup declaration: setup_path, an optional retained setup_destination, and setup_content_format (json or opaque). The setup_initialize_empty flag admits an empty opaque base only where the driver composes generated configuration into that file. Ordinary optional settings files stay absent when their source file is missing. write_back: true separately admits the broker to return a changed login file to that exact source path; read access alone never grants this capability. A source may also list exact-path rules in auxiliary_files using a source prefix, destination prefix, suffix and content format. A private driver may request an individual file matching such a rule; the broker reads only that path and includes it in the transient home format. Each driver request marks the file optional or required: an absent optional file is omitted, while an absent selected Codex named profile refuses the launch. These rules cover named-profile files such as .codex/ds-flash.config.toml; they do not scan the source tree. Materialization reads that optional path from the same source. JSON setup is bounded to 4 KiB; opaque setup is bounded to 64 KiB. It appends the bytes to the transient returned format as an initializer that may be installed even when an optional login is absent. The setup declaration and its contents are never stored in a definition or projection, and a missing setup file is allowed. The host's bee.credentials.security:credential_file_policy grants filesystem access separately from source metadata for availability, checks and bounded materialization reads. Only the write_back binding receives the separate credential_file_write_policy. Write-back serializes the source comparison and atomic replacement, revalidates the projection, attempt, generation, provider path and original source digest, and replaces only that login file. A newer source login causes a conflict and remains untouched. Configuration and state files are never written back. Registry source metadata in bee.credentials:credential_sources alone cannot grant filesystem read: if a source ref is admitted by metadata but absent from bee.credentials.security:credential_file_policy, availability and materialization fail closed (UNAVAILABLE). Adding another source requires an explicit host policy grant naming the login root.

Ordinary callers (workspace managers, projection subjects, and outsiders) cannot directly read login files or invoke check or materialize. The broker methods accessible to ordinary callers (define, issue_projection, list, availability, revoke, revoke_all, capabilities) and error replies carry only identifiers, digests and status views; they never echo secret bytes. Secret file contents are strictly absent from persisted database state: definitions, projections, generations, epochs, and the schema migration ledger (bee_credential_schema_migrations) never hold secret bytes. Only the admitted placement materializer holding bee.credentials.security:credential_materialize_policy receives bytes once per generation key in a transient RPC reply that nothing persists.

Login file reads stop after 64 KiB plus one byte; supplemental JSON and opaque setup reads stop after 4 KiB and 64 KiB plus one byte respectively. Login and present setup files reject empty or oversized content. An explicitly admitted empty opaque composition base is the only exception. JSON formats additionally require a JSON object or array; opaque formats preserve arbitrary bytes and report encoding bytes. Both return bytes only in the authorized transient materialization reply. Definition IDs/revisions accompany file replies so placement can bind retained login state to the selected source without hashing its contents. The broker never searches or exports the OS keyring: when credentials exist only there, a file projection is unavailable. Provider-specific JSON fields are opaque.

Database migration 2 (file_sources) introduces fs_directory source and file projection kinds by rebuilding bee_credential_definitions with strict SQLite CHECK constraints (provider IN ('claude', 'codex'), source_kind IN ('env_variable', 'fs_directory'), projection_kind IN ('environment', 'file')), while preserving all existing populated definitions, projections, consumed generations, and migration ledger records.

Migration 3 (optional_files) adds the constrained optional flag to definitions with a default of false; it is additive and preserves existing definitions, projections and the applied migration ledger.

Migration 4 (declared_providers) removes the storage-level Claude/Codex enum while retaining a bounded, nonempty provider label and the other constraints. The populated upgrade proof preserves definition identities, projection receipts, consumed generations and earlier migration records across reopen. Provider declarations do not authorize source reads.

Migration 5 (frozen_formats) adds nonsecret format_json to definitions and projections and backfills the historical Claude/Codex layouts without reading registry declarations or credentials. Existing definition digests, identities, receipts and retained-home markers are preserved. Unknown historical formats remain unbound and are refused. New definitions freeze the decoded host-selected layout; projections copy it. Issue, availability and use compare the saved layout with the current declaration; use also checks the projection's saved layout. Changing the declaration requires explicit redefinition and fresh projection. It cannot redirect a previously admitted credential.

The pure formats decoder bounds paths, environment names and initialization files, rejecting traversal, sparse arrays, duplicate files and file/directory collisions. Native placement admits only the broker's frozen format and refuses its reserved identity-marker path. The component owns its layout; callers still select a credential name and never supply a materialization path.

Test suites enforce these invariants using synthetic workspace-scoped fixtures (.wippy/*-fixture) and never touch actual host credential files or OS keyrings.

Built-in private batch routes use the following home-relative files from the machine home. Placement projects only these admitted files into a fresh attempt home; it never walks or exposes the rest of the user's home. Bee's generated provider configuration is composed separately by the selected driver.

| Provider | Login file | Ambient configuration/state | Private CLI home | |---|---|---|---| | Claude Code | .claude/.credentials.json | .claude/settings.json; Bee initializes .claude.json only when login bytes are present | CLAUDE_CONFIG_DIR points to the attempt's .claude | | Codex | .codex/auth.json | .codex/config.toml; one selected .codex/<name>.config.toml when a named profile is used | CODEX_HOME points to the attempt's .codex | | Agy | .gemini/antigravity-cli/antigravity-oauth-token | .gemini/antigravity-cli/cache/onboarding.json | private HOME | | Grok | .grok/auth.json | .grok/config.toml is the private composition base | GROK_HOME points to the attempt's .grok | | Muse | .config/muse/auth.json | .config/muse/settings.json is the private composition base | private HOME | | OpenCode | .local/share/opencode/auth.json | .config/opencode/opencode.json is the private composition base | private XDG config and data roots |

The host source allowlist admits token write-back for these login files only. When a child refreshes its login, the runner returns the changed file after exit; the broker requires the same active projection generation and an unchanged original source digest. Codex private routes preserve --profile NAME by projecting only .codex/NAME.config.toml through its host-admitted auxiliary-file rule.

The host links the exact placement binding persisted on new projection receipts; an unlinked materializer fails projection issuance closed, and caller or artifact input cannot select it. Existing projection rows retain their recorded binding.

Native placement accepts file projections only with a selected retained session home or a driver's declared private attempt home. Retained homes preserve provider-refreshed bytes when the recorded definition identity matches; attempt homes are disposable and return token changes through guarded write-back. Changed identity or a partial retained seed refuses reuse. See native placement for the delivery and filesystem guarantees. File contents never enter the environment projection route.

First-use harness setup can create definition-declared credential names from host-selected source configuration, without reading the secret. Built-in batch definitions for all six providers select optional login files under the host's existing home directory. An absent login leaves the private destination empty and the window keeps its existence-only login hint; host credential directories are never created. Agy may import its onboarding JSON, and Grok may import only .grok/config.toml into retained .grok/.bee-global-config.toml. Placement structurally inserts Bee's scoped MCP subtree into the private .grok/config.toml; it never writes the machine or project trees. The host allowlist owns the relative source path; callers cannot supply it. Source metadata and path are bound in the definition digest and rechecked before availability or projection use. A changed source requires explicit redefinition. Existing definitions with an older digest are refused rather than silently retargeted. Fixture acceptance covers each driver's declared files and a confined worker whose attempt home excludes unrelated machine-home files. The standard test gates use synthetic logins and fixture CLIs; they never discover or consume a host account. Docker delivery is unimplemented.

Revocation stops future materialization; a live attempt is stopped by placement at its next reconciliation, which the placement sweeper schedules on a fixed delay and which is reported as pending until the exit is proven, with credential.revoked evidence; an environment value already inside a running child cannot be scrubbed.

| Slice | Responsibility | |---|---| | bee.credentials | persist/broker: the nine contract operations and the store opened through bee.persist; root sources: host allowlist, provider destinations, linked references; contract contract with binding local |

Actions: bee.credentials.manage (define, list, revoke any, revoke_all; bee.credentials.security:credential_manage_policy), bee.credentials.issue (issue for oneself; bee.credentials.security:credential_issue_policy, only in the workspace the caller's host-issued identity is bound to through actor.meta.workspace_id), bee.credentials.materialize (check and materialize; bee.credentials.security:credential_materialize_policy, attached to node-level placement service and runner entries, which serve every workspace; an application that runs its own placement holds bee.credentials.security:credential_materialize_workspace_policy, limited to its bound workspace), and bee.credentials.write_back (runner-only return of an admitted provider login file through the same materialization policy).

OpenCode declares .config/opencode/towers.key as an optional auxiliary file under the host-admitted OpenCode .key rule. The broker projects that exact requested file without interpreting its bytes or writing it back. Placement rewrites a configuration reference only when this file is materialized; a configuration naming a missing or undeclared dependency refuses before start.

Machine login links

Bee selects link_policy: owner_safe only on bee.env:machine_login_source. The existing broker availability/check and projection reads use that directory; there is no separate absolute-path credential reader. On Unix, external symlink chains have a 40-link limit and loop detection. The canonical target must be a regular file. Its owner and every canonical parent's owner through filesystem root must be the process UID or root, and every mode must satisfy mode & 022 == 0. Sticky directories have no exemption. This uses the owner/mode rule from OpenSSH misc.c safe_path.

Runtime refusals name the path and cause. Availability, projection checks and materialization retain that reason; the Agent catalog shows it in the login-needed state. A successful metadata probe checks presence only, without opening login contents. Projection still reads only the provider's host-admitted files. Writes, creates, renames and deletes retain root containment. Windows keeps the contained behavior because equivalent ownership/ACL evidence is unavailable.

External-link support requires the runtime release containing wippyai/runtime#890. Bee's current runtime pin remains unchanged: it safely ignores the new field and retains containment, so external links remain unavailable until the runtime is upgraded. The proof-only BEE_RUNTIME=/path/to/local/tool make login-links-check exercises synthetic accepted and refused files through broker, locate and the Agent model.