🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

feat: add enable/disable support for user secrets - #27537

Merged
zedkipp merged 3 commits into
mainfrom
zedkipp/plat-189-add-enable-and-disable-support-for-user-secrets
Jul 28, 2026
Merged

feat: add enable/disable support for user secrets#27537
zedkipp merged 3 commits into
mainfrom
zedkipp/plat-189-add-enable-and-disable-support-for-user-secrets

Conversation

@zedkipp

@zedkipp zedkipp commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Adds an enabled flag to user secrets so a secret can be stored but temporarily kept out of workspaces without deleting it. The first commit adds the backend changes, the second commit adds the enable/disable toggle to the user secrets management page

Behavior

  • New column enabled BOOLEAN NOT NULL DEFAULT true on user_secrets.
  • Existing rows with both env_name and file_path empty are migrated to enabled = false, preserving their current not-injected behavior.
  • Every write must set at least one of env_name / file_path. Explicit enabled = false is now the only way to express "stored but not injected."
  • The agent manifest layer no longer special-cases both-empty rows; it simply skips disabled secrets.

REST API / SDK / CLI / UI

  • REST: POST defaults enabled to true; PATCH toggles it. Exposed through codersdk and generated site types.
  • Audit logs: enabled tracked in the field map.
  • CLI: coder secret enable / coder secret disable .
  • Web UI: per-row toggle on the secrets page. A target-less secret can't be enabled from the UI (toggle disabled with a tooltip), mirroring the API invariant.

Behavior notes

  • A PATCH that clears both env_name and file_path previously silently disabled injection; it now returns 400 and directs callers to use enabled = false.
  • Existing sessions keep injected values until the agent manifest is refetched (workspace restart); live revocation is tracked separately.
Screenshot 2026-07-27 at 10 43 43 AM

@linear-code

linear-code Bot commented Jul 27, 2026

Copy link
Copy Markdown

PLAT-189

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

@zedkipp
zedkipp force-pushed the zedkipp/plat-189-add-enable-and-disable-support-for-user-secrets branch 2 times, most recently from c99977f to a9d7b61 Compare July 27, 2026 20:17
@zedkipp
zedkipp marked this pull request as ready for review July 27, 2026 21:02
@zedkipp
zedkipp requested a review from a team as a code owner July 27, 2026 21:02
@coder-tasks

coder-tasks Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

Re-checked at 907bb7f4 (feat(coderd): enforce user secret injection-target invariant in the database). All previously flagged items are addressed. Table formatting (markdown-table-formatter) and the emdash rule are clean on the changed pages.

Updates Needed

  • docs/user-guides/user-secrets.md - "How user secrets work" now lists the enabled flag, states that an enabled secret must have an env or file target, that enabled = false is how you store a secret without injecting it, that violating writes return 400, and that pre-existing target-less secrets were migrated to disabled.
  • docs/user-guides/user-secrets.md - "How your secrets reach a workspace" documents disable semantics: not injected from the next workspace start, running sessions keep injected values until the agent manifest is refetched, and a previously written file stays on disk. Added as rows in both the env and file tables.
  • docs/user-guides/user-secrets.md - The --env "" / --file "" rows now say clearing a target only succeeds if the secret keeps the other target or is disabled in the same request, and Delete the secret is split into its own row.
  • docs/user-guides/user-secrets.md - "Update a secret" now reads "At least one of --value, --description, --env, --file, or --enabled", the --file "" example carries the caveat explaining why it succeeds, and a new "Enable and disable a secret" section documents coder secret disable / coder secret enable.
  • docs/user-guides/user-secrets.md - "Manage secrets from the dashboard" documents the per-row toggle and that a target-less secret cannot be enabled from the dashboard.
  • docs/user-guides/user-secrets.md - coder secret list gained an enabled column (cli/secret.go, reflected in the regenerated docs/reference/cli/secret_list.md), and the guide's list section mentions it.
  • docs/admin/security/secrets.md - The "User secrets" section now notes a disabled secret is stored but not injected until re-enabled.

New in this push

  • docs/reference/cli/secret_create.md, docs/reference/cli/secret_update.md - New --enabled flag on coder secret create / coder secret update is generated and present, and the guide documents it in a "Create a disabled secret" section.
  • The user_secrets_enabled_requires_target CHECK constraint maps to the same 400 already described in the guide, so it needs no separate docs surface.

No outstanding documentation work.


Automated review via Coder Agents

@dylanhuff-at-coder dylanhuff-at-coder 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.

Overall looks good, left two comments

Comment thread coderd/usersecrets.go
if req.Enabled != nil {
postEnabled = *req.Enabled
}
if postEnabled && postEnvName == "" && postFilePath == "" {

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.

I think this may introduce a race condition. If two concurrent PATCHes can both read a secret with env and file targets, separately clear one target, pass this check, and serialize to an enabled row with neither target.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep you're right. I added a database constraint to seal this hole up.

delete Delete a secret
list List secrets, or show one by name
update Update a secret
create Create a secret

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.

It would be nice to have enabled be passed as a flag to create and update

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Def worth it for ergonomics. Added.

zedkipp added 3 commits July 28, 2026 15:30
Adds an enabled flag to user secrets so users can temporarily stop a
secret from being injected into workspaces without deleting it.

- Add enabled BOOLEAN NOT NULL DEFAULT true to user_secrets. Existing
  rows with both env_name and file_path empty are migrated to
  enabled = false, preserving their current not-injected behavior.
- Require at least one of env_name or file_path on every write.
  Explicit enabled = false is now the only way to express "stored but
  not injected"; the agent manifest layer no longer special-cases
  both-empty rows and instead skips disabled secrets.
- Expose enabled through the REST API (POST default true, PATCH
  toggle), codersdk, and generated site types.
- Track enabled in the audit log field map.
- Add coder secret enable <name> and coder secret disable <name>.

Existing sessions keep injected values until the agent manifest is
refetched (workspace restart); live revocation is tracked separately.

Deprecation note: PATCHes that clear both env_name and file_path used
to silently disable injection and now return 400 with guidance to use
enabled = false instead.
Adds a per-row Switch control on the user secrets management page so a
user can enable or disable each secret. Disabled secrets remain visible
and editable but are not injected into workspaces.

A target-less secret (no environment variable and no file path) cannot
be enabled from the UI; the toggle is disabled with a tooltip prompting
the user to add an injection target first. This mirrors the API
invariant that an enabled secret must have at least one target.

Toggle failures are surfaced via toast. Storybook stories cover the
submit, error, and target-less tooltip cases.
…atabase

Close a read-modify-write race in the user secret PATCH handler. The
handler computes the post-update env_name/file_path/enabled from a stale
pre-read, so two concurrent PATCHes that each clear a different target
could both pass the check and serialize to an enabled row with no
target. Add a user_secrets_enabled_requires_target CHECK constraint
(NOT enabled OR env_name <> '' OR file_path <> '') as the race-safe
source of truth, and map the violation to the same 400 the API returns.

Also add CLI ergonomics and docs requested in review:
- coder secret create/update gain an --enabled flag; --enabled=false
  stores a secret without injecting it.
- coder secret list gains an enabled column.
- Document the enabled field, disable semantics, and the new commands
  in the user secrets guide and admin secrets overview.

Add a constraint regression test and update tests that created
target-less enabled secrets to be disabled.
@zedkipp
zedkipp force-pushed the zedkipp/plat-189-add-enable-and-disable-support-for-user-secrets branch from 907bb7f to ae9168b Compare July 28, 2026 15:36
@zedkipp
zedkipp merged commit 85984ff into main Jul 28, 2026
31 of 32 checks passed
@zedkipp
zedkipp deleted the zedkipp/plat-189-add-enable-and-disable-support-for-user-secrets branch July 28, 2026 15:58
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 28, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants