Skip to content

fix(users): stop leaking password digests and SSO tokens - #461

Merged
stakach merged 3 commits into
masterfrom
fix/users-sensitive-fields
Nov 11, 2026
Merged

stakach merged 3 commits into
masterfrom
fix/users-sensitive-fields

Conversation

@stakach

@stakach stakach commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Summary

GET /api/engine/v2/users/metadata/search returned raw Model::User records. Their default JSON includes password_digest and the Microsoft access_token / refresh_token, plus admin-only fields such as sys_admin, and every authenticated user received all of it. POST /users and PATCH/PUT /users/:id had the same leak.

  • New user_details(user, admin_view) helper renders to_admin_struct / to_public_struct. Neither includes secrets.
  • search: admin view for admin or support (user_support?), public view for everyone else
  • create: admin view for user_support?
  • update: admin view for user_support? or a user editing themselves (matching /current)
  • index / show: now also use user_support? for the admin view, so support users see sys_admin etc. consistently. Listing users in other authorities stays admin-only.

Tests

New specs in spec/controllers/users_spec.cr:

  • search as non-admin, support and admin: no secret keys, and no digest or token values anywhere in the body
  • search as non-admin: admin-only fields absent; as support or admin: present
  • create and update responses: no digest or token values
  • index and show (with and without include_metadata) as non-admin, support and admin: right view, no secrets

Companion models PR adds the model-level serialisation specs.

🤖 Generated with Claude Code

/users/metadata/search, create and update rendered the raw Model::User,
whose default JSON includes password_digest, access_token and
refresh_token. Render the public / admin views instead: admin and support
users get the admin view on search and create, and on update so does the
user editing their own profile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the type: bug something isn't working label Nov 11, 2026
Align index and show with search: admin and support users get the admin
view (sys_admin, support, misc, ...), everyone else the public view.
Cross-authority listing remains admin-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added type: bug something isn't working and removed type: bug something isn't working labels Nov 11, 2026
The view structs omit nil fields, so login_name and card_number were
missing from the admin view of the fixture users.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added type: bug something isn't working and removed type: bug something isn't working labels Nov 11, 2026
@stakach
stakach merged commit bec7f75 into master Nov 11, 2026
11 checks passed
@stakach
stakach deleted the fix/users-sensitive-fields branch November 11, 2026 06:07
stakach added a commit that referenced this pull request Nov 12, 2026
* fix(group_users): stop leaking password digests and SSO tokens

The group_users index hydrates each row with its user so the frontend
can render a member list without an N+1 fetch. It embedded the raw
`Model::User`, whose default JSON includes `password_digest`,
`access_token`, `refresh_token`, `expires_at` and `expires`. Any group
manager (not only admins) could read them for every member.

Embed `User::PublicResponse` (`to_public_struct`) instead, matching
what #461 did for the users controller.

OPENAPI_DOC.yml is regenerated: `GroupUser.user` now references
`PlaceOS.Model.User.PublicResponse`, and it also picks up the /users
response schema changes from #461, which were never regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: stop leaking chats, OAuth secrets, pending mail and API key owners

- chatgpt: show, destroy and websocket resume only resolve the caller's
  own chat; anyone else's is a 404 (resume is checked before the upgrade)
- oauth_apps: index returns a secret-free view to non-admins
- flux: refuse guest tokens, any signed in user can still query
- emails: index and show are support / admin only
- api_keys: embed the owner as User::AdminResponse instead of the raw
  model, which serialised the password digest and SSO tokens

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Stephen von Takach <steve@place.technology>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: bug something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant