feat(keychain): materialize credentials for human sandbox sessions - #631
Open
yzxcj797 wants to merge 1 commit into
Open
feat(keychain): materialize credentials for human sandbox sessions#631yzxcj797 wants to merge 1 commit into
yzxcj797 wants to merge 1 commit into
Conversation
A scope's sandbox is reachable interactively — SSH attach, browser IDE — by the same people the agent serves, but credential materialization was only drivable from an agent turn: the keychain routes require an agent capability token, and the only materialization surface was POST /v1/keychain/use inside one. A human working interactively in the sandbox got either a shared credential dropped in the scope's home volume (no expiry, no per-user attribution, no revocation short of rotating the token) or nothing at all — both defeating the keychain's per-user/per-scope/expiry/audit model (yc-software#550). The primitives now have a non-agent driver: - Keychain.materializeStandingForUser(ownerId, scopeId) — the same selection as materializeStanding filtered to ONE person's credentials: a session materializes exactly the standing grants that person gave toward this scope, never a neighbor's (once-mode and expired grants excluded identically). - POST /v1/keychain/sessions { userId, scopeId, ttlMinutes? } — a source-authenticated integration (the SSH/IDE attach helper holding CORE_SIGNING_SECRET) opens a session: materializes the user's set, records per-credential usage rows exactly as a turn does, audits the open, and returns the SAME renderUseScript shape /keychain/use emits for the helper to source in the user's shell. TTL bounded (default 8h, max 12h). - DELETE /v1/keychain/sessions/:id — releases (idempotent), records released usage rows, audits. GET lists live sessions for operators. - Destination carries an optional humanSession { userId, sessionId } — the non-agent counterpart of the web destination, so asks and audit can name where a materialization went. Agent-surface behavior is unchanged: the routes are additive and source-auth-gated; an agent capability token alone does not reach them. Implements the suggested direction of yc-software#550
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.
Implements the suggested direction of #550 — the session-materialization half. The in-sandbox link plumbing (
ephemeralCredLinkScript,EPHEMERAL_CRED_DIR) is reused exactly as the issue prescribes; what was missing was a non-agent driver, which this adds.What was unreachable
Every materialization path sat behind an agent capability token (
handleKeychain's gate) and ran inside a turn. TheKeychaininterface already declaredmaterialize/materializeOwn/materializeStanding; every sandbox driver already ranephemeralCredLinkScript;renderUseScriptalready produced the shell-sourceable form. A human reaching the same sandbox interactively (SSH, browser IDE) had exactly the two bad options the issue names: a shared credential in the scope's home volume, or nothing.The driver
Keychain.materializeStandingForUser(ownerId, scopeId)—materializeStanding's selection filtered to one person's credentials. The grants table is keyed by audience scope, so the filter is what makes a shared sandbox's sessions per-user: Alice's session materializes Alice's grants, never Bob's, even though both granted toward the same scope. Once-mode and expired grants are excluded identically to the agent path.POST /v1/keychain/sessions { userId, scopeId, ttlMinutes? }— source-auth (auth: "source"): the caller is the trusted integration holdingCORE_SIGNING_SECRET(the SSH/IDE attach helper), naming the human on the session — the same trust boundary the issue's "authenticated internal route" describes. It materializes the user's set, records acredential_usagerow per credential exactly as a turn does, auditskeychain.session.open, and returns the samerenderUseScriptoutput/keychain/useemits — the helper sources it in the user's shell, so the materialization lifetime and isolation semantics are identical to an agent turn's. TTL bounded: default 8h, max 12h.DELETE /v1/keychain/sessions/:id— idempotent release:releasedusage rows + audit.GET /v1/keychain/sessionslists live sessions for operators. An expired session sweeps to released on next touch — no re-materialization past its TTL.Destination.humanSession { userId, sessionId }— the non-agent counterpart of the web destination, so asks and audit can name where a materialization went (the issue's item 1).What deliberately didn't change
The agent surface: existing routes, their capability gate, and turn-time materialization are untouched — the new routes are additive and source-auth-gated, so an agent token alone cannot reach them (and a source-auth caller was already trusted with the whole keychain surface these routes read).
Tests
materializeStandingForUser returns only that person's standing grants— Alice + Bob both grant toward one scope; each session gets exactly one credential (their own), a third user gets nothing. Fails onmain(no such method).materializeStandingForUser excludes once-mode and expired grants— pins the mode boundary.Full
keychain.test.ts: 43 passed / 1 pre-existing environment failure (sprites exec ... bad envelope— identical on cleanmain);keychain-asksuite green;tsc --noEmitclean.Not included (per the issue's own scoping)
File-credential materialization for sessions (the agent path materializes files only via explicit
/keychain/usere-fetch, and the same one-shot semantics deserve their own design), and the dashboard surface — the API + script contract here is the integration seam both would build on.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.