Issued agents as app keys - #1275
Closed
joepio wants to merge 10 commits into
Closed
Conversation
Document how to mint scoped identities for plugins (Raycast, CLIs) without sharing the account secret. Rights stay the existing read/write ACL; the missing piece is a private registry and a show-once secret UX. Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Add issueAccessAgent / grantAccessAgent / revokeAccessAgent in @tomic/lib so a signed-in session can create a separate agent and grant it workspace rights without switching identity. Settings shows the secret once; /app/token warns that it still acts as you. Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
…cess
`revokeAccessAgent` walked the caller's target list, removed the agent from
each ACL, and returned. Three ways that told the user something untrue:
- It aborted on the first unreachable target, so every workspace after it kept
the grant — and nothing said which.
- It never checked that the save took effect. A rejected or unsent commit left
the in-memory resource looking edited, and the function returned normally.
- It marked the profile "(revoked)" regardless, and the UI toasted
"Key revoked" regardless.
A revoke that quietly leaves access behind is worse than one that fails loudly:
the user reads "Key revoked", stops worrying, and the secret still opens their
workspaces.
Now every target is attempted, verified by re-reading its ACL after the save,
and reported: `{ revoked, untouched, failed }`. The profile is marked revoked
only when nothing was left behind, and the UI says how many workspaces it
checked — or names what it could not revoke.
One limit is now stated in the doc comment rather than implied: `targets` comes
from the caller, and grants live on each workspace rather than on the key, so a
grant on a workspace outside that list survives and cannot be seen from here.
The call site passes the workspaces it has loaded, which is why the success
message says what it checked instead of claiming completeness. Recording grants
on the key's own resource would let revoke be authoritative; that needs a
schema addition and is worth doing before this is relied on.
Tests cover all three: a failed target keeps the key unlabelled, a fully clear
revoke marks it, and a workspace that never granted the key reports untouched
rather than revoked. Each was checked to fail against the previous
implementation.
Playwright walks User Settings: mint a named read-only key, assert the secret is shown once, confirm the session stays the signed-in agent, then revoke. Switch the card's hooks to data-testid so getByTestId works. Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Agents skip signChanges on create, so resource.new stayed true after save. Collection.applyResourceChange treats that as a placeholder and drops ResourceManuallyCreated — the secret dialog appeared but the row never did. Clear new after a successful persist. Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
useChildren can lag the create dialog. Keep minted subjects in local state so the list does not stay empty until a refresh. Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Rights already inherit down any parent. The form only offered whole drives, so a Raycast key that should read one folder still got the entire workspace. Record the real targets on the key, pick any resource in Settings, and revoke from that list. Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
OAuth-shaped consent: the app sends the user to /app/authorize with a name, read/write, and targets. The pending request is stored in a well-known personal-drive folder. Allow binds an app-minted public key (no secret in the URL) or mints one and shows it once. Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Apps that send a public key and a safe redirect_uri skip copy/paste: Allow sends the user back with granted=true&agent=&state=. Deny uses error=access_denied. Secrets never go in the URL. Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
3 tasks
joepio
added a commit
that referenced
this pull request
Aug 28, 2026
Replaces the plugin-grant class added yesterday. That was the parallel permission model planning/issued-agents.md (PR #1275) explicitly says not to build, and its worst property was that it was enforced only where the browser host happened to run — a scheduled or triggered run never consulted it, so an app's permissions meant one thing in a tab and nothing at 3am. An Agent already is a token: the DID is the principal, read and write on resources are the scopes, revocation is taking the DID off those lists. So createApp issues the app one and grants it write on the app itself. Rights inherit down the parent tree, which means 'an app may write its own data' is now something the ordinary rights walk says rather than a rule this codebase restates. The agent resource is parented to the drive, not the app: an app may write its own subtree, so its agent kept there would be a public key the app could replace. issueAccessAgent and its tests are taken from #1275 unchanged. The subtree check in hostStore stays, demoted from authority to early answer — the same verdict the rights walk will reach, arrived at in time to hand the app an error it can show. The secret is returned once and stored nowhere here. Signing as the app is the next step; until then writes still go out under the session's agent, so the ACL is provenance and a plan, not yet a bound.
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.
Related Issues
App keys (issued agents) — extra identities you mint for apps and plugins instead of sharing your account secret. Plan:
planning/issued-agents.md.Checklist
What this does
Mint, list, and revoke app keys from User Settings. Each key is a real Atomic Agent (own DID + secret). The signed-in session stays you.
Requesting rights (OAuth-shaped)
/app/authorize?name=Raycast&write=0&targets=*&agent=did:ad:agent:…&redirect_uri=https://app.example/cbIf the app sent a public key and a safe
redirect_uri, Allow returns the user to the app (granted=true&agent=&state=). Nothing to copy. Deny returnserror=access_denied. Secrets never go in the URL.Without a public key, Allow still shows a secret once (PAT fallback).
Pending requests live in
localId: app-key-requestson the personal drive.Redirect (no copy/paste)
Consent shows where you'll return. Allow with a public key + safe URI lands on
redirect_uri?granted=true&agent=…&state=…— no secret on the page or in the URL.Consent: return to localhost, nothing to copy
app_keys_authorize_redirect.mp4
Landed on /app/about with granted=true, agent, and state
To show artifacts inline, enable in settings.