Skip to content

Added OIDC data:read and data:write scopes - #555

Merged
kjlippold merged 4 commits into
release/v2.0.0from
553-oidc-scopes
Oct 7, 2026
Merged

kjlippold merged 4 commits into
release/v2.0.0from
553-oidc-scopes

Conversation

@kjlippold

Copy link
Copy Markdown
Contributor

Split OIDC token auth into read and write scopes for each API endpoint.

@kjlippold kjlippold self-assigned this Oct 5, 2026

@daniel-slaugh daniel-slaugh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues were flagged by the review agents. Both security issues that should be resolved, but you have a few open PRs and you mentioned you'll merge these and the changes from the v1.12 release into one branch, so I'm approving this PR in case you want to merge all these branches and resolve security issues while we test the v2.0 release candidate.:

OIDC write scope can mint permanent API keys and administer accounts

  • service_account.py:56, :166; collaborator.py:54, :82, :112; workspace.py:135, :154; oidc_adapter.py:20
  • oidc_write_auth guards more than data. It also covers creating service accounts and regenerating their keys, managing collaborators, deleting a workspace, and transferring workspace ownership. The consent screen says only "Create, modify, and delete your HydroServer data". The service-account endpoints return the new API key in plaintext, and that key never expires.
  • Scenario: A user approves a third-party app for data:write, thinking it only edits observations → the app calls POST /service-accounts with its 1-hour token → it gets back a permanent API key → the user revokes the app, but the key still works. The same token can also transfer or delete the user's workspace. Nothing on the consent screen discloses any of this.
  • Fix: Before release, keep OIDC tokens off the service-account create and regenerate routes. Put collaborator, workspace delete and transfer routes behind a separate scope (e.g. account:admin) with honest consent text. At minimum, disclose these powers in the data:write consent text.

OIDC read scope returns stored upstream credentials in plaintext

  • data_connection.py:26, :91; schemas/etl/data_connection.py:262
  • Both data-connection GET routes accept oidc_read_auth. DataConnectionResponse returns auth_header_value, which is read straight from a plaintext TextField. Anyone who can view the workspace already gets it back through any login method. This PR also hands it to third-party apps under a "View your HydroServer data" label.
  • Scenario: A workspace stores an upstream token in a data connection → a user approves a read-only dashboard app → the app lists data connections → it receives the upstream token in plaintext and can call that third-party service as the workspace.
  • Fix: Stop returning auth_header_value from GET for every login method. Replace it with a hasAuthHeader: bool, and only accept the value on POST and PATCH. Track this as its own issue, because the leak predates the scope change.

@kjlippold

Copy link
Copy Markdown
Contributor Author
  1. I added the following additional scopes to help admins avoid giving overly broad permissions to apps. The new iam:write scope still allows apps to create and modify collaborators and service accounts, but users will be warned when opening an app with that scope.
  • workspace:read
  • workspace:write
  • iam:read
  • iam:write
  • task:read
  • task:write
  • task:run
  1. I'm creating a separate issue to address this, since it predates the addition of these OIDC scopes.

@kjlippold
kjlippold merged commit e2f062b into release/v2.0.0 Oct 7, 2026
10 of 12 checks passed
@kjlippold kjlippold mentioned this pull request Oct 8, 2026
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