Skip to content

feat(secrets): implement secrets storage and management functionality#3425

Merged
pedrofrxncx merged 5 commits into
mainfrom
feat/secrets
May 21, 2026
Merged

feat(secrets): implement secrets storage and management functionality#3425
pedrofrxncx merged 5 commits into
mainfrom
feat/secrets

Conversation

@pedrofrxncx
Copy link
Copy Markdown
Collaborator

@pedrofrxncx pedrofrxncx commented May 21, 2026

  • Added a new migration to create the "secrets" table with necessary constraints and indexes.
  • Introduced SecretStorage class for managing secrets, including create, update, delete, and list operations.
  • Implemented tools for creating and listing secrets, ensuring values are encrypted and access is controlled based on user and organization scopes.
  • Updated context and tests to integrate the new secrets functionality across the application.

This commit enhances the security and management of sensitive information within the application.

What is this contribution about?

Describe your changes and why they're needed.

Screenshots/Demonstration

Add screenshots or a Loom video if your changes affect the UI.

How to Test

Provide step-by-step instructions for reviewers to test your changes:

  1. Step one
  2. Step two
  3. Expected outcome

Migration Notes

If this PR requires database migrations, configuration changes, or other setup steps, document them here. Remove this section if not applicable.

Review Checklist

  • PR title is clear and descriptive
  • Changes are tested and working
  • Documentation is updated (if needed)
  • No breaking changes

Summary by cubic

Implements an encrypted secrets vault with org/user scopes and integrates secret-backed env vars into virtual MCPs. Secrets are resolved on VM start and sent to the sandbox daemon; values are stored encrypted and never returned.

  • New Features

    • Secrets vault and storage: DB table + SecretStorage (create/update/delete/list/resolve), encrypted via Credential Vault with cross-user checks.
    • Tools/Registry: SECRET_CREATE, SECRET_LIST, new "Secrets" category, and secrets:manage permission.
    • Virtual MCP env: metadata.runtime.env supports literals or secret refs; resolved and PUT to the daemon before install/dev. Runtime card adds an EnvVarsField with .env paste and a restart notice; autosave skips incomplete rows; removed the old VM drawer env panel.
    • UI: Organization Settings → Secrets page to create and list secrets.
    • SDK: export RuntimeMetadata and RuntimeEnvEntry from @decocms/mesh-sdk.
  • Migration

    • Run migration 082-secrets to create the secrets table and indexes.

Written for commit e54cae1. Summary will update on new commits. Review in cubic

- Added a new migration to create the "secrets" table with necessary constraints and indexes.
- Introduced SecretStorage class for managing secrets, including create, update, delete, and list operations.
- Implemented tools for creating and listing secrets, ensuring values are encrypted and access is controlled based on user and organization scopes.
- Updated context and tests to integrate the new secrets functionality across the application.

This commit enhances the security and management of sensitive information within the application.
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Benchmark

Should we run the Virtual MCP strategy benchmark for this PR?

React with 👍 to run the benchmark.

Reaction Action
👍 Run quick benchmark (10 & 128 tools)

Benchmark will run on the next push after you react.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Release Options

Suggested: Minor (2.338.0) — based on feat: prefix

React with an emoji to override the release type:

Reaction Type Next Version
👍 Prerelease 2.337.4-alpha.1
🎉 Patch 2.337.4
❤️ Minor 2.338.0
🚀 Major 3.0.0

Current version: 2.337.3

Note: If multiple reactions exist, the smallest bump wins. If no reactions, the suggested bump is used (default: patch).

…mponent

- Introduced a new migration to create the "secrets" table with necessary constraints and indexes.
- Updated the EnvVarsField component to accept additional props for virtualMcpId and orgSlug, enhancing its functionality.
- Integrated a new RunningSandboxNotice component to inform users about the need to restart their sandbox for environment variable changes to take effect.

This commit enhances the management of secrets and improves user experience in the environment variable handling process.
…ndling

- Introduced a new migration to create the "secrets" table with necessary constraints and indexes.
- Updated the EnvVarsField component to improve entry updates, ensuring better handling of environment variable changes.

This commit enhances the management of secrets and optimizes the user experience in the environment variable handling process.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 36 files

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread apps/mesh/src/web/views/settings/secrets.tsx
Comment thread apps/mesh/src/tools/secrets/create.ts
Comment thread apps/mesh/src/web/components/vm/runtime-card/env-vars-field.tsx
@pedrofrxncx pedrofrxncx merged commit 068337d into main May 21, 2026
17 checks passed
@pedrofrxncx pedrofrxncx deleted the feat/secrets branch May 21, 2026 19:34
tlgimenes added a commit that referenced this pull request May 21, 2026
…n with prod 082-secrets

origin/main now has 082-secrets (PR #3425) at the position the earlier
renumber commit (edb42f3) parked 082-thread-run-locally. Two
migrations sharing the same 082- numeric prefix breaks Kysely's
ordering guarantee (and the locally-run 082-thread-run-locally errors
out with corrupted-migrations when the directory now sorts
082-secrets first).

Shift all six branch migrations up by one slot:

  082-thread-run-locally           -> 083-thread-run-locally
  083-drop-host-sandbox-rows       -> 084-drop-host-sandbox-rows
  084-rename-runner-kind           -> 085-rename-runner-kind
  085-thread-pins-and-vm-map-rekey -> 086-thread-pins-and-vm-map-rekey
  086-fix-vm-map-rekey             -> 087-fix-vm-map-rekey
  087-purge-cli-activate-keys      -> 088-purge-cli-activate-keys

Updates the index.ts imports / migrations map and the two test files
that self-imported their own migration module (086/087 test files).
All 88 migrations apply cleanly in the test runner; 8/8 renamed
tests pass.

Local devs who already ran any of the old-numbered migrations on this
branch must either reset their postgres data dir or rename the rows
in kysely_migration to the new numbers before pulling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tlgimenes added a commit that referenced this pull request May 21, 2026
…me (#3417)

* feat: harness implementation — laptop link daemon, agent pill, inlined runtime

Squashes 19 commits from tlgimenes/harness-impl-research.

Laptop link daemon: `deco link` boots a local Bun.serve, opens a Warp
tunnel to deco.host, registers with the cluster's /api/links to receive
a linkSecret, then exposes an HMAC-signed control plane for sandbox
lifecycle + reverse proxy. Capability probe surfaces claude-code, codex,
and decopilot-sandbox to the cluster. SIGINT/SIGTERM deregister cleanly.

Sandbox provider abstraction: RunnerKind → SandboxProviderKind across
type/storage column/helpers. New remote-user provider plugs the user's
link daemon in via ctx.linkRegistry; docker + agent-sandbox keep an
env-singleton fallback. vmMap re-keyed to (user, branch, providerKind)
with tolerant readers for legacy 2-level + runnerKind shapes; migration
086-fix-vm-map-rekey performs the rename that earlier migrations no-op'd
on a dropped table.

Chat UI: single Agent pill replaces Runner+Harness, eligibility-aware.
Clonable agents (Start Website + GitHub-imported) skip the no-AI-provider
empty state when the laptop link is online with a CLI harness, and the
two CLI options (Claude Code / Codex desktop) surface without a cloud
key — they run on laptop-stored credentials. Decopilot fallback for
agents with no GitHub repo. Connect-laptop empty-state tile + dialog.
cli-activate provider path removed (now on the laptop link); migration
087 purges sentinel keys.

Inlining refactor (vs prior research branch): apps/link → apps/mesh/src/
{cli/commands/link.ts, link-daemon/}, @decocms/harnesses → apps/mesh/src/
harnesses/, @decocms/link-protocol → apps/mesh/src/links/protocol/.
Drops three single-consumer packages, keeps one binary (deco), folds
session/tunnel/login plumbing into the existing CLI. packages/sandbox
reaches into apps/mesh via relative imports — documented inline as a
trade-off accepted per "abstractions to a minimum".

Migrations 082-087 are renumbered to start strictly after main's latest
applied (081-async-research-jobs-result-content) so prod's executed list
stays a clean prefix and the new migrations apply fresh on deploy.

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

* docs(chat): spec for merging AgentPill into AgentModelTrigger

Captures the design decisions from brainstorming: collapse the
AgentPill (Decopilot/Claude Code/Codex) into the chat-input model
trigger as a sectioned popover with green styling on local-CLI
sections, mirror existing lock semantics, and fix the gap-collapse
bug on the trigger button.

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

* docs(chat): drop decopilot-laptop, lock spec scope

Q6 resolved: remove the cloud-Decopilot-on-local-sandbox option
entirely (existing localStorage validation already migrates stale
values to null). Adds the verified "what stays vs. what goes"
inventory after exploring real call sites.

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

* docs(chat): implementation plan for merged model selector

Nine bite-sized tasks: trim agent-options.ts, build getAgentSections
(+ tests), AgentSection component, AgentModelPopover shell, rewrite
AgentModelTrigger with green styling and gap fix, plumb new props
through input.tsx, slim ThreadPills, delete AgentPill, and final
type/lint/fmt/test verification.

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

* refactor(chat): drop decopilot-laptop AgentOption and computeAgentOptions

Removes the cloud-Decopilot-on-local-sandbox option (decopilot-laptop)
and computeAgentOptions, which gets replaced by getAgentSections in a
following commit. Keeps AGENT_OPTION_PINS + pinsForOption / pinsToOption
since chat-context.tsx still needs them. The localStorage load in
chat-context already validates against AGENT_OPTION_PINS, so stale
'decopilot-laptop' values silently migrate to null.

* feat(chat): add getAgentSections for merged model selector

Introduces AgentKind/AgentSection types and a pure getAgentSections
function that returns the three sections (Decopilot, Claude Code,
Codex) shown in the new merged chat-input popover. Mirrors the gates
computeAgentOptions used (sans decopilot-laptop) and flags CLI
sections with isLocal: true to drive the green styling. Preserves
the existing getAgentModelSet helper used by the settings page.

Renamed agent-models.ts to agent-models.tsx because the Decopilot
tier entries now hold JSX icon nodes (Lightning01/Stars01/Atom01).

* feat(chat): add AgentSection component for merged model selector

Renders one section in the new sectioned popover — header with the
agent title (plus " · on this laptop" suffix for CLI agents), three
tier rows with descriptions, and the existing "On" indicator. Local
sections sit on a faint bg-success/5 band; disabled sections render
opacity-40 + pointer-events-none + a small lock icon.

Also adds Bun test infrastructure (bunfig preload + happy-dom +
@testing-library/react + jest-dom matchers) so React component tests
can render in a DOM and use toBeInTheDocument(). No prior precedent
existed in the repo, so the setup file is new.

* fix(chat): scope happy-dom preload to apps/mesh, revert spurious package.json sort

Code review flagged that the root bunfig.toml preloaded happy-dom for all
workspace tests, replacing Node natives like fetch/Request/Response/URL in
packages/* tests that depend on them. Switched to Option B: removed both
bunfig.toml files (root and apps/mesh) and made apps/mesh/src/test/setup.ts
an importable module with side effects. React component tests now `import
"../../../../test/setup"` (or equivalent) at the top, guarded by
GlobalRegistrator.isRegistered to avoid double-registration. This keeps
happy-dom contained to component tests that opt in, while packages/* tests
keep running against Node natives (804 pass / 0 fail vs the prior
470/31/15 baseline).

Also reverts an unrelated @decocms/sandbox alphabetical re-sort that crept
into the Task 3 commit -- restores its prior position between marked and
mesh-plugin-workflows.

* feat(chat): add AgentModelPopover shell for merged selector

Composes AgentSection rows from a getAgentSections result. Handles
lock semantics — when lockedAgent is set, only the matching section
is interactive; the others render disabled. Row click fires onSelect
with (kind, tier) and the locking is verified by tests.

* feat(chat): merge agent picker into AgentModelTrigger

The trigger now opens the new sectioned AgentModelPopover (Decopilot,
Claude Code, Codex) instead of the old SimpleModeTierDropdown / CLI
tier list split. Closed pill goes text-success + bg-success/10 when
the active agent is a CLI variant, matching the 'Desktop connected'
green elsewhere. Picks both agent (via setPendingAgentOption) and
tier in a single click and fires the eager VM start for CLI agents
when a branch is set. Also fixes the phantom 6px gap that appeared
when the label collapses at narrow container widths.

Also passes a concrete URL to the happy-dom GlobalRegistrator in the
shared test setup so component tests whose transitive imports reach
better-auth/react createAuthClient (which reads window.location at
module init) no longer blow up on the default about:blank URL.

* fix(test): clean up DOM between component tests

bun:test doesn't auto-call RTL's cleanup() the way Jest does, so DOM
from one component test was leaking into the next file's render, e.g.
a popover trigger from agent-model-trigger.test.tsx persisting into
agent-section.test.tsx and breaking getByText queries. Add an
afterEach in setup.ts that calls cleanup() to mirror the implicit
Jest behavior.

Because bun:test caches this setup module across every test file in
a run, the top-level afterEach only registers in whichever test file
imports setup first. To cover the remaining files we also install a
Bun.plugin onLoad hook that prepends an afterEach -> cleanup()
registration to any test source that already imports test/setup, so
each file's own scope gets the hook regardless of load order.

* refactor(test): drop Bun.plugin magic, use explicit setupComponentTest()

The plugin onLoad transform that injected afterEach() into every test
file was clever but surprising — future contributors would have no idea
why test files appear to have shifted line numbers in stack traces.
Replace it with an exported setupComponentTest() function that each
component test calls at module top-level. Registration lands in the
right scope without source rewrites or runtime magic.

* chore(chat): pass currentBranch + virtualMcpId to AgentModelTrigger

The merged AgentModelTrigger needs both props so it can fire the eager
VM start that ThreadPills used to own when the user picks a CLI agent.
Plumbs them through the input mount without otherwise touching the
composer layout.

* refactor(chat): drop AgentPill from ThreadPills

The agent picker now lives inside the chat-input's AgentModelTrigger.
ThreadPills shrinks back to a single BranchPill (still locked when the
thread has messages). The eager VM-start logic moves into the merged
AgentModelTrigger row click.

* chore(chat): delete obsolete AgentPill component

Replaced by the sectioned popover inside AgentModelTrigger. No
remaining importers.

* fix(chat): pin native streams + augment bun:test Matchers in setup.ts

Two regressions surfaced during T9 verification when running the chat
test directory as a whole:

1. happy-dom's GlobalRegistrator overwrites globalThis.TransformStream
   and WritableStream but leaves ReadableStream alone. Once a component
   test triggered registration, store-level tests that exercise ai-sdk's
   readUIMessageStream blew up with "readable should be ReadableStream"
   because stream.pipeThrough received a happy-dom TransformStream while
   the stream itself was the native ReadableStream. Snapshot the native
   classes before register() and restore them after.

2. The published @testing-library/jest-dom type augmentation targets the
   jest/vitest globals, so .toBeInTheDocument() flagged TS errors on
   bun:test's Matchers<T>. Declare the augmentation against "bun:test"
   so the matchers extended at runtime are visible to the type checker.

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

* fix(chat): guard nullable modelId before indexing lookup in laptop-cli

AgentTierEntry.modelId is `string | null` (Decopilot tiers leave it
null). The existing guard checked `!model` after `lookup[entry.modelId]`,
which TypeScript correctly flagged because the index access itself
rejects a null key. Add the explicit pre-guard so the type narrows
cleanly and the runtime semantics stay identical.

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

* fix(migrations): shift branch migrations to 083-088 to avoid collision with prod 082-secrets

origin/main now has 082-secrets (PR #3425) at the position the earlier
renumber commit (edb42f3) parked 082-thread-run-locally. Two
migrations sharing the same 082- numeric prefix breaks Kysely's
ordering guarantee (and the locally-run 082-thread-run-locally errors
out with corrupted-migrations when the directory now sorts
082-secrets first).

Shift all six branch migrations up by one slot:

  082-thread-run-locally           -> 083-thread-run-locally
  083-drop-host-sandbox-rows       -> 084-drop-host-sandbox-rows
  084-rename-runner-kind           -> 085-rename-runner-kind
  085-thread-pins-and-vm-map-rekey -> 086-thread-pins-and-vm-map-rekey
  086-fix-vm-map-rekey             -> 087-fix-vm-map-rekey
  087-purge-cli-activate-keys      -> 088-purge-cli-activate-keys

Updates the index.ts imports / migrations map and the two test files
that self-imported their own migration module (086/087 test files).
All 88 migrations apply cleanly in the test runner; 8/8 renamed
tests pass.

Local devs who already ran any of the old-numbered migrations on this
branch must either reset their postgres data dir or rename the rows
in kysely_migration to the new numbers before pulling.

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

* fix(migrations): guard vmMap aggregate with COALESCE in 085-rename-runner-kind

Wrap inner and outer jsonb_object_agg calls in COALESCE(..., '{}'::jsonb) so
rows whose vmMap is an empty object — or whose nested user_map is empty —
don't end up with metadata = NULL after the rewrite (jsonb_set returns NULL
when any argument is NULL). Also restrict the UPDATE to rows where
metadata->'vmMap' is an object so non-object vmMap values are skipped.

Addresses PR review on #3417.

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

* fix(link-daemon): tighten handle validation to non-empty string

Reject non-string truthy bodies (arrays, objects, numbers) with 400 instead
of letting them flow through to ensureSandbox.

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

* fix(cli): detect --port=X inline form for deco link

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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