Skip to content

feat(rbac): protect Scorch, Builder, and Tunneler - #385

Draft
GhostofGoes wants to merge 9 commits into
sandialabs:mainfrom
GhostofGoes:feat-rbac-services
Draft

GhostofGoes wants to merge 9 commits into
sandialabs:mainfrom
GhostofGoes:feat-rbac-services

Conversation

@GhostofGoes

@GhostofGoes GhostofGoes commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

Description

tl;dr: Adds Builder, Scorch, and Tunneler permissions, new Builder and Scorch roles, and fixes related access-control gaps.

Changes

These are the CHANGELOG entries this PR adds.

Added

  • RBAC: Protect Builder, Scorch, and Tunneler with the service-level builder (get, post, put), scorch (get, post, delete), scorch/terminals (write), and tunneler (get) permissions across REST routes, Scorch websocket updates, the standalone Builder and Tunneler download links, and the web UI. Custom roles need these permissions added explicitly.
  • RBAC: Built-in roles get service access on first startup, once, so administrators can later remove it:
    • Every viewer role (Global Viewer, Experiment Viewer, VM Viewer, and the new Scorch Viewer) can open the Builder and save topology files locally.
    • Roles that can control VMs (Experiment Admin, Experiment User, and VM Admin) can start and cancel Scorch runs for their experiments.
    • Typing into and exiting Scorch terminals needs the separate scorch/terminals write permission, which only Global Admin and Scorch Admin have. A Scorch terminal, such as the one a break component opens, is a shell running as the phēnix server process, so this permission gives control of the phēnix server.
    • Experiment Admin, Experiment User, and VM Admin can download the Tunneler and create port forwards for their VMs; Experiment User gains vms/forwards create and delete.
  • RBAC: New built-in roles, created once on existing installs:
    • Scorch Viewer: view Scorch pipelines, component output, read-only Scorch terminals, and Scorch run files for assigned experiments.
    • Scorch Admin: everything Scorch Viewer can do, plus start and cancel runs, write to Scorch terminals, and view VMs and screenshots for assigned experiments.
    • Builder: use the Builder and the Configs page for Topology, Scenario, and Experiment configs, create and update experiments, and list disks, topologies, scenarios, applications, hosts, and options. Not scoped to experiments, and cannot read or change User, Role, or Image configs.

Fixed

  • RBAC: Role and user configs saved by phēnix no longer store resourceNames: null for unscoped policies, which failed schema validation when an administrator later edited the role.
  • Web UI: Permission checks no longer throw for policies with no resource names, and Configs page buttons now check the same Kind/name as the server.
  • Store: etcd no longer panics when checking a store component that has not been initialized.
  • Users: Creating a user with a role that doesn't exist, through the Users page, the API, or --users, is now rejected instead of storing a user without a role. Creating a user whose name is taken returns 409 Conflict instead of failing mid-request.
  • RBAC: experiments/captures list now filters captures by <experiment>/<vm>, like every other VM check, instead of the bare VM name.
  • RBAC: The Experiment Viewer role's vms/mount permission now applies to the user's VMs. Its policy came after a policy with resource names, so assigning the role never scoped it. Existing roles and users are fixed on first startup.
  • Web UI: The Logs and Settings tabs now check logs get and settings update, the permissions the server checks, instead of logs list and settings edit.
  • CLI: phenix util role-table now lists every permission phēnix checks, including the users permissions, and phenix ui --users help describes the entry format.

Security

  • Scorch: Scorch terminals and component output now require read access to the experiment, and typing into or exiting Scorch terminals requires scorch/terminals write. Previously, any authenticated user could stream or write to them. Scorch pipeline and terminal websocket updates now go only to users with Scorch access to the experiment instead of every connected user.
  • Scorch: Starting or canceling Scorch through POST or DELETE /api/v1/experiments/{name}/trigger?apps=scorch now requires scorch post or delete in addition to experiments/trigger. Starting and canceling runs on the Scorch pipeline routes now needs scorch post or delete and read access to the experiment, instead of experiments/trigger.
  • Configs: configs create is now checked against the new config's Kind/name, on both POST /api/v1/configs and POST /api/v1/workflow/configs/{branch}, and renaming a config or changing its kind needs configs create for the new name. Previously, any role with configs create could create User or Role configs and grant itself more access.
  • Builder: PUT /api/v1/experiments/builder now checks experiments update for the named experiment, and experiments create when it creates the experiment.
  • Builder / Tunneler: GET /builder, POST /builder/save, and GET /downloads/tunneler/{name} now require authentication and builder get or tunneler get.
  • VNC: The VNC websocket (GET /api/v1/experiments/{exp}/vms/{name}/vnc/ws), which carries the VNC session itself, now requires vms/vnc get for the VM, like the VNC page. Previously any authenticated user could open a VNC session to any VM.
  • Logs: GET /api/v1/logs no longer sends logs after rejecting a request without logs get, and live log messages now go only to users with logs get instead of every connected user.
  • Users: The configs API no longer returns the password hashes and API tokens in User configs. Any role that could read User configs, including Global Viewer, could use another user's token to act as that user. Updating a User config through the configs API keeps its stored password and tokens.
  • Users: Creating an API token for another user now needs the new users/tokens create permission for that user, in addition to users patch. By default, only Global Admin has it.

Not covered by the CHANGELOG:

  • Go and Vitest tests for the new permissions, roles, migration, and fixes.
  • The phēnix agent skill documents the new permissions, and its API auth examples now use Bearer <token> and the user/pass login body.

Background

Before this PR, Builder, Scorch, and Tunneler had no permissions of their own:

  • GET /builder, POST /builder/save, and tunneler downloads needed no authentication at all.
  • Any authenticated user could stream Scorch component output and terminals for any experiment, and type into Scorch terminals. A Scorch terminal is a shell on the phēnix server.
  • Scorch pipeline and terminal websocket events went to every connected user.

Reviewing these paths also turned up access-control gaps in config creation, Builder experiment updates, and user creation. A later review of the permissions docs found more:

  • The VNC websocket, which carries the VNC session, had no permission check.
  • GET /logs sent logs after rejecting a request, and live logs went to every connected user.
  • User configs exposed password hashes and live API tokens to any role that could read them. See the security comment on this PR for an example.
  • users patch on another user let the holder create API tokens for them.

Request for comments: which roles should have which permissions?

Please comment on the defaults below. The table is what this PR ships, and the questions after it are the open decisions. Custom roles are not changed; they only gain these permissions when an admin adds them.

"Scoped" means the role follows the experiments set on the Users page. For the scoped roles, the Users page scopes VM-level permissions, including port forwards, only when the admin enters exp-a/* as well as exp-a.

Role builder scorch scorch/terminals tunneler Other access in this PR Scoped
Global Admin all all write get everything no
Global Viewer get get none get read everything no
Experiment Admin get post put get post delete none get port forwards via vms/* yes
Experiment User get post get post delete none get new: vms/forwards create delete yes
Experiment Viewer get get none get yes
VM Admin none get post delete none get port forwards via vms/* yes
VM Viewer get none none none yes
Scorch Viewer (new) get get none none experiments list get; experiments/apps and experiments/files read yes
Scorch Admin (new) none all write none Scorch Viewer's experiment access; vms list get; vms/screenshot get yes
Builder (new) get post put none none none configs list get create update for Topology/*, Scenario/*, Experiment/*; experiments list get create update; disks list get; topologies, scenarios, applications, hosts, options list; schemas get no

Open questions:

  1. Scorch terminals are shells on the phēnix server. Typing into them is now its own permission, scorch/terminals write, and only Global Admin and Scorch Admin get it. Roles that control VMs can start and cancel runs, but can only watch terminals. A run paused at a break waits until a terminal writer exits it, or until someone with scorch delete cancels the run. Is that the right split, and should any other role get terminal access?
  2. Is it right that starting and canceling Scorch runs no longer needs experiments/trigger? It kept roles that control VMs from also gaining the right to trigger every other app.
  3. Every viewer role, including VM Viewer, can now open the Builder and save topology files locally. Is that right for VM Viewer, which otherwise sees only screenshots and VNC?
  4. Experiment User has builder post, and Experiment Admin has post and put. Neither has experiments create or configs access, so the Builder's Save to phēnix and Open fail for them. This limit existed before this change. Should we drop these verbs, or grant the missing permissions?
  5. The Builder role:
    • It is global, not scoped to experiments. Should it be scoped?
    • It cannot delete configs, or start, stop, or delete experiments. Should it?
    • disks get means disk download. Keep it?
    • Image configs are excluded because administrators run their build scripts as root. Agreed?
    • Editing any Experiment or Scenario config also means it can add Scorch components, such as a break, that other roles later run.
  6. The Scorch roles:
    • Scorch Admin can see VMs and screenshots, but has no VNC, no experiment start or stop, and no file uploads.
    • Scorch Viewer has no VM access.
    • Neither role gets Tunneler.
    • Any changes?
  7. Experiment Viewer can download the Tunneler but cannot create port forwards. Keep it?

Related Issues/PRs

Type of Change

  • Bugfix (fix)
  • Feature (feat)
  • Documentation (docs)
  • Refactor (refactor)
  • Chore (CI, build, dependencies, etc.) (chore)
  • Other (please describe):

Checklist

Testing

Automated (macOS, Go 1.27, Node 26):

  • go test -race ./... passes, except for tunneler and util/mm/mmcli. Those two packages fail the same way on the base commit because of macOS port reuse and UNIX socket path length.
  • New Go tests cover:
    • Default role matrix and schema validation of every built-in role.
    • Users-page scoping of the new and changed roles.
    • Builder Kind limits.
    • One-shot migration: revoke then restart, forward scope copied from vms/*, and wildcard grants not duplicated.
    • One-shot creation of the new roles, and that a deleted role stays deleted.
    • Configs create and rename checks, including the workflow config API, and PUT /experiments/builder scoping.
    • Scorch pipeline scoping, terminal claims, and websocket delivery.
    • Auth middleware in dev, signed-JWT, and proxy modes.
    • scorch/terminals write: scorch * and read-only wildcards don't grant it.
    • User creation: an unknown role is rejected by the API and by --users, and a duplicate username returns 409.
    • Download headers.
    • The VNC websocket and GET /logs reject users without permission.
    • User configs from the configs API omit password hashes and tokens, and edits keep them.
    • Creating a token for another user needs users/tokens create.
    • experiments/captures filtering by <experiment>/<vm>, and the Experiment Viewer vms/mount migration.
  • The configs, Builder, trigger, migration, resourceNames, null-guard, terminal-permission, user-creation, VNC, logs, User-secret, token, captures, and mount tests fail when their fix is reverted.
  • golangci-lint run --new-from-rev=<base> reports 0 issues. npm test (34 tests), eslint, prettier --check, and npm run build pass.
  • Local upgrade smoke test:
    • A store seeded by the base-commit binary was served by this branch with --jwt-signing-key and --features tunneler-download.
    • All 38 checks passed, covering migrated users, the new roles, Builder config limits (User and Role configs denied, Topology created), Scorch control scoping, and Tunneler.
    • Revoking tunneler from Experiment Viewer survived a restart.
    • On a fresh store:
      • Terminal exit returns 403 for Experiment User. For Scorch Admin, it passes the permission check and fails the ownership check.
      • An unknown role is rejected by the API (400) and by --users, and nothing is stored.
      • A duplicate username returns 409.
    • The User config token exploit described in the security comment works before the fix and fails after it (401).
    • The VNC websocket returns 403 for a user without vms/vnc get on the VM.
    • GET /logs returns only a 403 for a user without logs get.
    • Over the websocket, Global Admin and Global Viewer receive live logs, and Experiment User receives none.
How to test on a deployed phēnix

1. Prepare

  1. Back up the store: docker stop phenix && cp /etc/phenix/store.bdb /etc/phenix/store.bdb.pre-rbac. For etcd, take a snapshot.

  2. To test the upgrade path, create these users on the current release. Use the Users page or phenix ui --users 'name:Pass1234!:Role Name:exp-a exp-a/*':

    User Role Resource names
    admin Global Admin
    euser Experiment User exp-a exp-a/*
    eviewer Experiment Viewer exp-a exp-a/*
    eadmin Experiment Admin exp-a exp-a/*
    vmviewer VM Viewer exp-a/*
  3. Build with auth enabled: docker build -f docker/Dockerfile --build-arg PHENIX_WEB_AUTH=enabled -t phenix:rbac .

  4. Add --jwt-signing-key=<secret> and --features=tunneler-download to the phenix ui command, and start the container.

  5. After the upgrade, create sviewer (Scorch Viewer, exp-a exp-a/*), sadmin (Scorch Admin, exp-a exp-a/*), and builder (Builder, no resource names).

  6. Create two running experiments, exp-a and exp-b. Give exp-a a Scorch app with a break component:

    - name: scorch
      metadata:
        components:
        - name: break
          type: break
          metadata: {}
        runs:
        - start: [break]

2. Upgrade

  • docker exec phenix phenix config list: role/builder, role/scorch-viewer, and role/scorch-admin exist.
  • docker exec phenix phenix config get role/experiment-user -o yaml shows:
    • builder get post.
    • scorch get post delete.
    • tunneler get.
    • vms/forwards create delete.
    • The phenix.rbac/service-permissions: "true" annotation.
  • docker exec phenix phenix config get user/euser -o yaml shows:
    • The same service policies.
    • vms/forwards with the same resourceNames as vms/*.
    • An unchanged experiment scope.
  • Delete role/scorch-viewer and restart. It must not come back.

3. API checks

Get a token with:

T=$(curl -s -X POST $PHENIX/api/v1/login -d '{"user":"euser","pass":"..."}' | jq -r .token)

Then send it as -H "X-Phenix-Auth-Token: bearer $T". The Builder rows send it as a query parameter or form field instead.

Request admin euser eviewer eadmin vmviewer sviewer sadmin builder none
GET /builder?token=$T 200 200 200 200 200 200 403 200 403
POST /builder/save, form token=$T&filename=t.xml&xml=%3Cx%2F%3E 200 200 200 200 200 200 403 200 403
GET /downloads/tunneler/phenix-tunneler-linux-amd64 200 200 200 200 403 403 403 403 403
GET /api/v1/experiments/exp-a/scorch/pipelines 200 200 200 200 403 200 200 403 403
GET /api/v1/experiments/exp-b/scorch/terminals 200 403 403 403 403 403 403 403 403
POST /api/v1/experiments/exp-a/scorch/pipelines/0 (cancel the run between users) 204 204 403 204 403 403 204 403 403
POST /api/v1/experiments/exp-a/trigger?apps=scorch 204 403 403 403 403 403 403 403 403
POST /api/v1/experiments/exp-a/scorch/terminals/1/exit/x (body says forbidden when the permission is missing) ownership error forbidden forbidden forbidden forbidden forbidden ownership error forbidden forbidden
POST /api/v1/configs with a User config 201 403 403 403 403 403 403 403 403

Builder role:

  • POST /api/v1/configs with a valid Topology config returns 201.
  • A Role config returns 403.
  • PUT /api/v1/configs/topology/<name> with a body of kind: User returns 403.
  • DELETE /api/v1/configs/topology/<name> returns 403.
  • GET /api/v1/configs lists no User, Role, or Image configs.
  • POST /api/v1/experiments/exp-a/start returns 403.

Scoping: PUT /api/v1/experiments/builder as eadmin with "name":"exp-b" returns 403.

4. UI checks

  • Navigation: Builder shows for every viewer role and the Builder role. Scorch shows for roles with scorch get and experiments list. Tunneler shows for Experiment Admin, User, and Viewer, and VM Admin.

  • Scorch as euser:

    • Start run 0 from the Scorch list.
    • The break terminal opens read-only with Close only, because Experiment User has no scorch/terminals write.
    • As sadmin: the same terminal has Exit and accepts input. Exiting it lets the run continue.
    • As sviewer, in a second browser: the same terminal is (read-only), typing has no effect, the status tags cannot be clicked, and run status updates live.
  • Builder as builder:

    • File > Open... lists Builder topologies.
    • Save to phēnix with a scenario creates an experiment.
    • On the Configs page, Topology, Scenario, and Experiment configs have Edit but no Delete.
    • The Experiments page can create experiments, but has no start or stop.
  • Builder as vmviewer: the Builder opens, and File > Save... downloads the file.

  • Port forwards as euser: on a running exp-a VM, create and delete a port forward.

  • Websocket: as a Scorch Viewer for exp-b, the /api/v1/ws frames carry no apps/scorch events for exp-a.

  • Users page: creating a user with a role that doesn't exist (API: POST /api/v1/users with "role_name":"Nope") returns 400, and no user is stored. Creating an existing username returns 409.

5. Other auth modes

  • Proxy (--jwt-signing-key proxy-jwt --proxy-auth-header X-Forwarded-User): all of the above works through the proxy. Requesting /builder?token=... without the proxy headers returns 400.
  • Auth disabled: everything works as before.

6. Revocation survives restart

  1. On the Configs page, remove the tunneler policy from role/experiment-viewer. The save must succeed.
  2. Reassign eviewer on the Users page.
  3. The tunneler download returns 403, and still does after a restart.

Rollback

Stop the container and restore store.bdb.pre-rbac. Older releases do not remove the added policies, annotations, or roles.

Additional Notes

  • Behavior changes:
    • Custom roles need the new resources added explicitly.
    • Custom roles with configs create need */* or kind patterns to create configs, the same as they already needed for list, get, and update.
    • Scorch control now follows scorch verbs plus experiment read access instead of experiments/trigger.
    • The Builder page previously had no authentication.
  • Not changed, noted for follow-up:
    • /builder still takes the JWT as ?token=, so it can appear in browser history and proxy logs.
    • openapi.yml does not document the Scorch, Builder, or Tunneler routes.

🤖 Generated with Claude Code

GhostofGoes and others added 8 commits September 23, 2026 13:20
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Require scorch post/delete when the generic app trigger endpoints start
  or cancel Scorch, matching the Scorch pipeline routes.
- Migrate built-in roles once, marked by the
  phenix.rbac/service-permissions annotation, so administrators can revoke
  service access without a restart granting it again. Keep Scorch and
  Tunneler read access for Experiment Viewer and VM Admin.
- Omit empty resourceNames when saving roles and users so migrated roles
  still pass schema validation when edited.
- Re-check Scorch write permission when a terminal stream connects and make
  terminal client IDs single use.
- Quote download filenames, send nosniff on Builder saves, close tunneler
  files, and reject non-file tunneler downloads.
- Download tunnelers with axios and FileSaver, share the Scorch control
  check between views, and show read-only Scorch status.
- Add Go and Vitest coverage, and update the CHANGELOG and phenix skill.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Give every viewer role Builder access, and let Builder saves, which only
  return the posted file, use builder get.
- Let roles that control VMs (Experiment Admin, Experiment User, VM Admin)
  start and cancel Scorch runs and write to Scorch terminals. Scorch
  pipeline routes now need scorch post or delete plus read access to the
  experiment instead of experiments/trigger, and Scorch run events go to
  everyone who can view Scorch for the experiment.
- Let Experiment User create and delete port forwards for its VMs.
- Add Scorch Viewer, Scorch Admin, and Builder default roles, created once
  on existing installs through a new service-roles store component.
- Check configs create against the new config's Kind/name, including
  renames, so a role cannot create User or Role configs outside its scope.
- Scope PUT /experiments/builder to the named experiment and require
  experiments create when it creates one.
- Migrate existing users' new policies with the scope of their matching
  policy, and skip permissions a role already has.
- Fix a UI crash on policies without resource names, check Configs page
  buttons against Kind/name, and guard etcd component checks.
- Add Go and Vitest coverage and update the CHANGELOG and phenix skill.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Image configs carry build scripts that administrators run as root, so the
Builder role no longer reads or edits them. Also note in the default roles
and the CHANGELOG that scorch post allows typing into Scorch break
terminals, which are shells on the phenix server.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…oles

- Add the scorch/terminals write permission for typing into and exiting
  Scorch terminals. A Scorch terminal, such as the one a break component
  opens, is a shell running as the phenix server process, so writing to it
  gives control of the server and bypasses RBAC. Only Global Admin and the
  Scorch Admin role get it; Experiment Admin, Experiment User, and VM Admin
  can still start and cancel Scorch runs.
- Check a new user's role before storing the user, so the Users page, the
  API, and --users reject unknown roles instead of storing users without a
  role, and return 409 for an existing username instead of panicking.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The auth header value must be "Bearer <token>", and the login body uses
"user" and "pass", so the skill's curl examples returned 401 and 400.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
POST /api/v1/workflow/configs/{branch} created new configs after only an
unnamed configs create check, so a role limited to Topology configs, such
as the new Builder role, could create User or Role configs and grant itself
more access. It now uses the same Kind/name check as POST /api/v1/configs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Move the Scorch permission check shared by the trigger handlers into a
helper so TriggerExperimentApps stays under the funlen limit, and remove
the funlen directive StartPipeline no longer needs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Security:
- Check vms/vnc get on the VNC websocket, which carries the VNC session;
  only the VNC page was checked, so any user could open any VM's console.
- Stop GetLogs after rejecting a request, and send live log messages only
  to users with logs get instead of every connected user.
- Never return User config password hashes or API tokens from the configs
  API; any role that could read User configs, including Global Viewer,
  could use another user's token to act as that user. Updates through the
  configs and workflow APIs keep the stored password and tokens.
- Require the new users/tokens create permission to create API tokens for
  another user; users patch alone now only covers your own tokens.

Fixes:
- Filter experiments/captures list by <experiment>/<vm>.
- Scope the Experiment Viewer vms/mount policy, and fix existing roles and
  users on first startup.
- Check logs get and settings update for the Logs and Settings tabs.
- Generate the permission list from parsed Go source so checks written
  with constants are included, and describe the --users entry format.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@GhostofGoes

Copy link
Copy Markdown
Contributor Author

Security issue: User configs exposed password hashes and live API tokens

Fixed in de2c78e.

Before the fix

GET /api/v1/configs/user/<name> and POST /api/v1/configs/download returned the full User config. That included:

  • spec.password: the bcrypt password hash.
  • spec.tokens: each key is a base64-encoded, currently valid JWT for that user. This includes the session token created every time the user signs in.

Who could read them: any role with configs get on User configs. The built-in Global Viewer role has that through its */* policy.

Impact: a read-only user could take over any account, including a Global Admin.

Example

The server was started with phenix ui --jwt-signing-key <key> --users 'admin:<pw>:Global Admin' --users 'viewer:<pw>:Global Viewer', and the admin had signed in once.

P=https://phenix.example.com/api/v1

# The viewer signs in.
VIEWER=$(curl -s -X POST "$P/login" -d '{"user":"viewer","pass":"<pw>"}' | jq -r .token)

# The viewer reads the admin's User config.
curl -s -H "X-Phenix-Auth-Token: Bearer $VIEWER" "$P/configs/user/admin" | jq '.spec | {password, tokens}'
# {
#   "password": "$2a$10$…",
#   "tokens": { "ZXlKaGJHY2lPaUpJVXpJ…": "2026-09-23T13:27:05-06:00" }
# }

# Each token key is the admin's JWT, base64-encoded.
STOLEN=$(curl -s -H "X-Phenix-Auth-Token: Bearer $VIEWER" "$P/configs/user/admin" \
  | jq -r '.spec.tokens | keys[0]' | base64 -d)

# The viewer creates a Global Admin account with the admin's token.
curl -s -X POST -H "X-Phenix-Auth-Token: Bearer $STOLEN" -H 'Content-Type: application/json' \
  -d '{"username":"mallory","password":"<pw>","first_name":"m","last_name":"m","role_name":"Global Admin","resource_names":[]}' \
  "$P/users"

Results on a local phēnix built from this branch before the fix:

  • The same POST /api/v1/users sent with the viewer's own token returned 403.
  • With the stolen token it returned 200, and mallory could sign in as a Global Admin.

When it works:

  • The attack only needs the admin to have signed in within the session lifetime (--jwt-lifetime, 24 hours by default).
  • API tokens created on the Users tab stay usable until they expire.

The exposed password hash also allows offline cracking.

The fix

  • The configs API no longer returns password or tokens from User configs, for a single config or a download.
  • Updating a User config through the configs or workflow API keeps the stored password and tokens. Editing a User config on the Configs page therefore doesn't wipe them.
  • Tokens are only managed through the token and logout routes.

Result after the fix: the same steps return "password": null, "tokens": null, and the request with the stolen token is rejected with 401.

TestConfigAPIHidesUserSecrets and TestUpdateUserConfigKeepsSecrets cover the fix, and both fail if it is reverted.

For existing deployments: anyone who could read User configs before upgrading may have copied tokens. Rotating --jwt-signing-key after upgrading revokes every existing token.

This branch has not been deployed

No deployments
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.

1 participant