Public: anyone can read this; the crew can change it.

A private app can name the people who may enter it, and nothing more #786

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

Closes #752

shipped production is serving it for issue #752 — its issue is already done deploy 910e02f

opened this

Closes #752

Work in progress: the description follows as the first comment when it is ready for review.

🤖 Generated with Claude Code

commented

Closes #752 (P0 Trust; follows #620/#743).

What it is. A private app can now name people who may enter it, beyond the people who can write the repository and its crew. A named person may open the app and its previews, and nothing else.

Where.

  • Page: the app's settings get a Who may enter card (one username a line), and the Access hint names who is listed.
  • API: GET and PUT /api/v1/repos/:owner/:name/app/members with {members, dryRun}.
  • MCP: app_members {repo, members?, dryRun?}.

Who may change the list. Only a person who can write the repository, and never an agent, even one on the crew. On the API a change is a dry run unless dryRun is false. PUT needs an admin-scope token, as the other app settings do. Over MCP an rw token can plan a change, but making it needs an admin-scope token.

Who may be named. A person's account in good standing: not an organisation, not an agent (the crew already enters), and not a suspended or removed account. At most 100 names.

What a name gives. canEnterApp (src/core/repos.ts) now takes the app record and reads the app_member table (migration 53, the last).

  • A named person enters production and every preview, through /apps/open and the proxy, with grant role user, never admin.
  • That works even on a private repository they cannot read; openEnvUrl skips the reader check only for them.
  • The terminal stays the writers' and crew's (named: false).

What a name does not give. Code of a private repository, the crew, owner facts, the member list, settings and the terminal are all refused in tests.

Ending access. The proxy asks again on every request, so removing a name ends a grant already issued. A suspended or removed account now loses a grant at once too: stillEntitled checks isActive, which also covers writers.

Looks. A private app's thumb follows the same rule (mayFetchLook uses canEnterApp). A named person sees it; a reader who is not named does not.

Tests. test/app-members.test.ts is new and in the gate, refusals first:

  • An agent, a reader and an anonymous caller cannot name anyone, through the action, the API, MCP or the form.
  • A plan changes nothing.
  • An agent or an unknown account cannot be named.
  • A named person enters on a public and on a private repository, as a user.
  • A reader who is not named does not enter.
  • A named person cannot read private code, cannot tell the crew, and cannot see usage, the list, settings or the terminal.
  • Removing a name, or suspending the account, kills a held grant.
  • The settings page names who is on the list.

test/looks.test.ts and test/mcp.test.ts are updated. Local runs: reserved-names, studio, check-list, hollow, private-app, apps-adversarial, mcp, app-members and looks give 331 pass, 0 fail. tsc is clean.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs