Repository navigation
feat: agents ask for a secret through a masked card, and get a reference, never the value (BRA-2860) - #211
Draft
Stener1 wants to merge 3 commits into
Draft
feat: agents ask for a secret through a masked card, and get a reference, never the value (BRA-2860)#211Stener1 wants to merge 3 commits into
Stener1 wants to merge 3 commits into
Conversation
|
@Stener1 is attempting to deploy a commit to the seanbrydonio's projects Team on Vercel. A member of the Team first needs to authorize it. |
Contributor
Author
|
Could a maintainer add |
Stener1
force-pushed
the
secret-request-card
branch
from
November 4, 2026 13:33
2a1a4e8 to
e2ec3ce
Compare
…nce, never the value (BRA-2860) An agent runs `berthd secret request NAME --reason …` (or the berth_secret_request tool); its session reads as needing you and a masked card shows in the chat, on the dashboard and as a notification. Save securely sends the value from the app to the box in one call the box's own socket can't make, into a store behind one provider interface: Local (Keychain, Secret Service, or an encrypted file) or GCP Secret Manager. The agent hears back with box://NAME or gcp://… and uses it in one command with `berthd secret exec --env` or `--file`. Settings → Secrets lists and deletes saved secrets and sets where they go, per box and per project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… agent can't move where secrets go From the review: a save claims its request, so a decline, the timeout or a second save can't settle it while the value is written; the expiry sweep looks again under the stores' lock, so a replacement saved since is never deleted; an expired secret is gone from memory too; a project's store can't be changed through the location config from the box's own socket, and a config written without it keeps it; resetting a project to the box's default in Settings clears its choice; secret-tool's newline is trimmed. Also: report cards name the secret, the CLI and the MCP tool share one wait, and gcloud is found where it installs itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vate project from the card (BRA-2860)
A name template in Settings → Secrets, for the box or one project, says
what Secret Manager calls a secret ({NAME}_{SERVICE}_{ENV}); the agent
keeps the plain name, and each environment is its own secret. A private
project set once in Settings is offered on the card with the box's other
projects, and the person picks at save time. Also fixes a project's own
store being ignored for agents in its worktrees, and the live script
grepping the wrong folder for the agent's transcript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRBpGMdZnu8NsP1S8UGDRj
Stener1
force-pushed
the
secret-request-card
branch
from
November 4, 2026 13:34
e2ec3ce to
16e9a53
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An agent that needs an API key asks for it by name instead of having it pasted into the chat. The person types it into a masked card, the value goes from the app to the box only, and the agent gets a reference (
box://NAME,gcp://…), never the value.sequenceDiagram participant Agent as Agent (box) participant Box as berthd participant App as Shipyard app Agent->>Box: berthd secret request ACME_TOKEN --reason "…" (box socket, X-Berth-Caller) Box-->>Agent: request id, at once (or the ref, if saved and unexpired) Box-->>App: secret.requested · session reads "waiting" (soft) with secret_request App->>Box: POST secrets/requests/{id}/save {value} (pinned wire only; the box socket is refused) Box->>Box: SecretStore.Save → Keychain / Secret Service / encrypted file / GCP Box-->>Agent: <berth-notification> kind="secret" ref="box://ACME_TOKEN" Agent->>Agent: berthd secret exec --env T=box://ACME_TOKEN -- cmd (or --file)secret execand Settings only go throughSecretStore;TestAThirdProviderPlugsInFromItsOwnFileregisters a fake third provider from its test file and runs requests, resolution, listing and delete through it.--store, then the project's choice (box-local location config, never the repo's file), then the box default (Settings → Secrets).guides/secrets.mdx(asking, using, where they're kept, GCP minimum role, adding a provider), security page reworded where it said no value is written to disk, API/CLI/events references, changelog. The berth agent skill tells agents to ask rather than have keys pasted.Evidence
Go:
go vet ./... && go test -race ./...green. New: save refused on the box socket, request only from an agent session, sentinel absent from events and state files, reuse/--new/1 h timeout on a fake clock, the session hearing back in its own terminal, claim vs decline/timeout, an agent can't move a project's store via location config, encrypted file round-trip with 0600/0700 and a separate key, Keychain and Secret Service against fakes with the value never in argv, Secret Service fallback, expiry sweep and expiry over the cache, GCP against a fake gcloud (4 labels, refuses unlabelled, new version on labelled, single-secret binding, names the missing permission),secret exec --env/--fileand the temp file going on SIGTERM, MCP tool returns at once. App: typecheck, titles, type, grid, csp, themes, 642 unit tests, build, e2e tsc; Playwright a11y scenes for the card and Settings; docs build.Live, real Claude on a dev box (
.claude/skills/verify-berthd/scripts/secret-request-live.sh), terminal and structured: the agent runsberthd secret request ACME_TOKEN, the app saves a random sentinel through the card, the agent'ssecret exec --envand--filesha256 checks pass, the temp file is gone, and the sentinel greps to 0 hits in the agent's transcript, the structured host log, the box log, events,turns.json,inbox.json, run records,notify.jsonand the laptop'squeue.json.More: board cards · revealed · saved · refused save keeps the value in the field · typed name. Scenarios:
drive.mjs secret-request,settings-secrets(mock),secret-request-live(dev box).Real boxes: on a build of this branch in the Mac app, on a Linux box: the card saved through the app,
secret exec --envand--filematched the value's sha256, the temp file went, Settings listed and deleted it, and deleting from the box socket was refused (403). On a Mac in its GUI session, the local store's save, read and delete worked against the real login Keychain; over SSH the Keychain refuses, and the store says so. A full card round on a Mac box wasn't run. The name template and project picker are not on a real box yet: they're proven by Go tests against a fake gcloud, the mock scenarios, and a live local-store regression run.Not run live: GCP Secret Manager (no scratch project), so the labels,
--grant, template and picker are covered by the fake-gcloud tests only.Devbox baselines:
TestTheCatalogIsCappedandTestPaneDeadOfASignalIsItsShellStatusfail in the full run here; the first counts the machine's real commands, the second is a timing test under load, and neither touches this change.Review: Codex review + adversarial review, one fix pass (second commit): a save claims its request so a decline/timeout/second save can't race it; the sweep rechecks under the stores' lock so a replacement isn't deleted; expiry is enforced ahead of the in-memory cache; the location config endpoint can't change a project's store from the box socket and keeps it when omitted; Settings' "Box default" clears a project's choice;
secret-tool's trailing newline. A second adversarial pass on the template and picker found a private secret could be reused across projects and environments, and a third project's wasn't found for rotation; both fixed with tests. Also fixed: a project's own store was ignored for agents in its worktrees (session location isproject/worktree), and the live script was grepping the wrong folder whenCLAUDE_CONFIG_DIRis set, so it now fails if the agent's transcript isn't there. Left as a follow-up: the secret wait is an overlay on the session list (enrich), not a ledger state, because a request outlives the turn that made it; so a parent agent watching the child doesn't get a "waiting" report for it.Merge Danger
Door: two-way
Additive: new endpoints, ref schemes (
box://,gcp://), a Settings section and new state files under the box's state dir (secrets/,secret.key,secret-requests.json), no migration. Reverting is a plain revert; secrets already saved stay in the Keychain / Secret Service / encrypted file until deleted (the guide says how). GCP side effects are a labelled secret and an optional single-secret IAM binding, both deletable; berthd never touches project IAM or unlabelled secrets.Blast Radius: secrets
box:///gcp://are now recognised as references whereverop://is (project env,env.json), and a session with a pending request shows as waiting (soft: it still takes prompts).go.mod:golang.org/x/cryptomoves from indirect to direct (same version).🤖 Generated with Claude Code
https://claude.ai/code/session_01QRBpGMdZnu8NsP1S8UGDRj