Skip to content

Issued agents as app keys - #1275

Closed
joepio wants to merge 10 commits into
developfrom
cursor/issued-agents-keys-1732
Closed

Issued agents as app keys#1275
joepio wants to merge 10 commits into
developfrom
cursor/issued-agents-keys-1732

Conversation

@joepio

@joepio joepio commented Aug 15, 2026

Copy link
Copy Markdown
Member

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

  • Add changelog entry linking to issue, describe API changes
  • Add or update tests if needed
  • Update docs if needed

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/cb

If 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 returns error=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-requests on 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.

Open in Web Open in Cursor 

cursoragent and others added 10 commits August 15, 2026 18:58
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>
@joepio joepio closed this Aug 18, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants