Skip to content

feat(secrets): scope secret storage to a team - #2928

Merged
camielvs merged 1 commit into
cvs/team-contextfrom
cvs/team-scoped-secrets
Oct 31, 2026
Merged

camielvs merged 1 commit into
cvs/team-contextfrom
cvs/team-scoped-secrets

Conversation

@camielvs

@camielvs camielvs commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Secrets are stored per caller, so a team's secrets are read and written by sending the impersonation header on the four secrets requests. Every function in secretsStorage now takes an optional team id and sends the header only when one is given. Every caller still passes none, so today's behaviour is unchanged.

  • secretsListQuery(teamId) builds the cache key and the fetch from the same team id, so the cache can never hold one account's secrets under another's key. The three screens that list secrets now use it. Before, they handed fetchSecretsList to React Query directly, which would now receive the query context as its team id.
  • SecretsQueryKeys.All keys the user's secrets as ["secrets", "user"] and a team's as ["secrets", "team", teamId]. The unused Id key is removed, since it no longer fits that shape.
  • addSecret and updateSecret pass description and expires_at, which the API has always accepted as query params. No form sets them yet.
  • Failures now carry the backend's reason instead of [object ReadableStream]. They throw SecretsApiError with the status, or TeamImpersonationError when the backend refuses to act as the team. That lets a later screen tell a refusal apart from an expired session.

Related Issue and Pull requests

Type of Change

  • New feature
  • Improvement

Line Breakdown

Category Added Removed
Production code 67 28
Comments 0 3
Tests 236 0
Total 303 31

Checklist

  • I have tested this does not break current pipelines / runs functionality
  • I have tested the changes on staging

Screenshots (if applicable)

None. There's no UI change.

Test Instructions

pnpm vitest run src/components/shared/SecretsManagement

To check by hand that nothing regressed:

  1. With a backend configured, open Settings → Secrets.
  2. Add, replace and remove a secret.
  3. Open the secret picker on a task argument and pick a secret.

All three should behave as on master.

Additional Comments

The guided tour's mock secrets are unchanged.

🤖 Generated with Claude Code

@camielvs
camielvs added this pull request to stack #2930 October 22, 2026 19:23
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: cvs/team-scoped-secrets/eeb9d79

@camielvs
camielvs force-pushed the cvs/team-scoped-secrets branch from 7f9b852 to 8e9703e Compare October 22, 2026 20:12
@camielvs
camielvs force-pushed the cvs/team-scoped-secrets branch from 8e9703e to 4d5a7f8 Compare October 23, 2026 22:58
@camielvs
camielvs force-pushed the cvs/team-scoped-secrets branch 2 times, most recently from ea8e047 to ff12fc0 Compare October 27, 2026 17:33
@camielvs
camielvs force-pushed the cvs/team-scoped-secrets branch 2 times, most recently from 2bd2220 to d657d94 Compare October 29, 2026 23:33
@camielvs
camielvs force-pushed the cvs/team-scoped-secrets branch from d657d94 to 11fb445 Compare October 30, 2026 18:51
@camielvs
camielvs force-pushed the cvs/team-scoped-secrets branch from 11fb445 to 783a658 Compare October 30, 2026 20:35
@camielvs
camielvs marked this pull request as ready for review October 30, 2026 21:24
@camielvs
camielvs requested a review from a team as a code owner October 30, 2026 21:24
@camielvs
camielvs force-pushed the cvs/team-scoped-secrets branch from 783a658 to 5ec22ef Compare October 30, 2026 21:46
Secrets are stored per caller, so a team's secrets are read and written
by sending the impersonation header on these four requests. Every
function in `secretsStorage` now takes an optional team id and sends the
header only when one is given. Every caller still passes none, so they
behave exactly as before.

- `secretsListQuery(teamId)` builds the cache key and the fetch from the
  same team id, so the cache can never hold one account's secrets under
  another's key. The three screens that list secrets use it instead of
  handing `fetchSecretsList` to React Query directly, which would now
  receive the query context as its team id.
- `SecretsQueryKeys.All` keys the user's secrets as `["secrets", "user"]`
  and a team's as `["secrets", "team", teamId]`. The unused `Id` key is
  gone, since it no longer fits that shape.
- `addSecret` and `updateSecret` pass `description` and `expires_at`,
  which the API has always accepted as query params. No form sets them
  yet.
- Failures carry the backend's reason instead of
  `[object ReadableStream]`. They throw `SecretsApiError` with the
  status, or `TeamImpersonationError` when the backend refuses to act as
  the team, so a later screen can tell that apart from an expired
  session.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@camielvs
camielvs force-pushed the cvs/team-scoped-secrets branch from 5ec22ef to eeb9d79 Compare October 31, 2026 00:12
@camielvs
camielvs merged commit 7697696 into master Oct 31, 2026
18 checks passed
@camielvs
camielvs deleted the cvs/team-scoped-secrets branch October 31, 2026 00:25
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