Skip to content

improvement(secrets): gate Copilot code mounting at use level - #7004

Merged
icecrasher321 merged 2 commits into
stagingfrom
improvement/copilot-secret-mount-use-level
Aug 23, 2026
Merged

improvement(secrets): gate Copilot code mounting at use level#7004
icecrasher321 merged 2 commits into
stagingfrom
improvement/copilot-secret-mount-use-level

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

What

Mounting a saved secret into Copilot code required credential-admin on that key. A workflow Function block resolves the same secret for the same person at use level, via getPersonalAndWorkspaceEnv. This aligns the two.

Why the admin bar wasn't buying anything

Copilot can reach the workflow path itself — edit_workflow (lib/copilot/tools/handlers/workflow/mutations.ts) plus run_workflow (lib/copilot/tools/workflow-tools.ts:11-16). "Add a Function block containing {{KEY}}, run it, remove the block" reads the value using tools with no view gate. So the gate only redirected a Credential Member through a detour that mutates a persisted workflow, while the direct path is ephemeral and files a usage row.

The inconsistency was also internal to Copilot. The secret names advertised to the model come from getAccessibleEnvCredentials (chat/workspace-context.ts:524) and getPersonalAndWorkspaceEnv (vfs/workspace-vfs.ts:3091), both role-agnostic — so Copilot listed every secret the caller could use, then refused to mount all but the admin ones.

Change

activeAdmin (role === 'admin' && status === 'active') → activeGrant (status === 'active'), applied to both the workspace and shared-personal predicates, with the matching eq(credentialMember.role, 'admin') removed from the query.

Shared-personal is relaxed too, not just workspace: getPersonalAndWorkspaceEnv already resolves a member-level grant on someone else's personal secret, so leaving that strict would reproduce the same half-consistency one level down.

Unchanged: workspace write is still required; revoked and pending grants still refused; no grant still gets nothing; model-egress projection untouched; usage rows still filed (function-execute.ts:801).

What still holds the line

The view gate stays where Copilot cannot route around it — values stay masked under Settings → Secrets (maskWorkspaceEnvForViewer), and See usage stays admin-only (lib/secrets/application/use-cases.ts:370-409). A member using a secret in code is recorded for whoever can rotate it.

Verified no redaction anywhere is conditional on view rights, so this cannot open a masking hole.

Behaviour

Member with write + role=member on STRIPE_SECRET_KEY Before After
Settings → Secrets "" ""
Ask Sim to run code CopilotCodeSecretAccessError mounts, usage row filed
Function block on canvas mounts mounts
Revoked / pending / no grant denied denied
See usage trail admin-only admin-only

One behavioural note: selectDistinctOn now picks among member-granted shared-personal rows too, so for a name shared by two people the most-recently-updated credential wins — converging on the updatedAt desc rule getPersonalAndWorkspaceEnv already uses.

Docs

platform/credentials.mdx — the Copilot section now states the use-level rule and why it differs from the read gate. Also corrected two pre-existing inaccuracies in the Workspace vs. Personal table: "Visibility → All workspace members" implied values were visible to all members (only names are), and "Who can edit → Workspace admins" omitted per-secret Credential Admins.

Not in scope

Inbox keeps its own, tighter actor resolution (resolveInboxExecutionActor: sender-match or null) and is deliberately untouched — an unmatched sender still mounts nothing. Separately, an allowlisted external sender executes as ws.ownerId with autoExecuteTools: true and no permission gating (tool permissions are interactive-only, lifecycle/run.ts:205-215); whether the headless toolset exposes edit_workflow/run_workflow there is untraced and worth its own issue.

Test plan

  • secret-mount-materializer.server.test.ts — 26 passed. Added a member/active mount case, added member/revoked + member/pending to the denial matrix, extended the shared-personal case over both roles, and added a revoked shared-personal denial. The two "unauthorized workspace value" precedence tests now use a revoked grant so they keep testing precedence-on-denial.
  • function-execute + resolved-secret-result + environment/utils — 90 passed.
  • bun run type-check clean, biome clean.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview Aug 23, 2026 1:29am

Request Review

@cursor

cursor Bot commented Aug 23, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Widens who can decrypt and inject secret plaintext into Copilot code (authz on credentials). Revoked/pending grants and the Settings mask stay in place, but this is still a security-sensitive permission change.

Overview
Copilot Function/code-execution now mounts a saved secret for anyone with an active use grant (Credential Member or Admin), matching what a workflow Function block already resolves via getPersonalAndWorkspaceEnv. Previously only per-secret admins could mount, which members could bypass by having Copilot edit and run a workflow.

activeAdmin becomes activeGrant (status === 'active'), and the credential query no longer filters role = admin. Workspace write is still required; revoked, pending, and missing grants still fail. Settings masking and admin-only See usage are unchanged.

Docs in credentials.mdx state the use-vs-read split and fix the Workspace vs Personal table (names vs values, Credential Admins can edit). Tests cover member mounts, member/revoked/pending denials, and shared-personal member grants.

Reviewed by Cursor Bugbot for commit 3ea842f. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR aligns Copilot secret mounting with use-level credential grants while retaining active-status checks, masking, and usage attribution.

  • Replaces the active-admin mount predicate with an active-grant predicate.
  • Expands workspace and shared-personal test coverage across grant roles and statuses.
  • Updates the credentials documentation to distinguish secret use from value visibility.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/lib/copilot/tools/secret-mount-materializer.server.ts Broadens Copilot mounting from active admin grants to any active credential grant while preserving status checks and owner attribution.
apps/sim/lib/copilot/tools/secret-mount-materializer.server.test.ts Adds coverage for active member grants and revoked or pending grants across workspace and shared-personal credentials.
apps/docs/content/docs/en/platform/credentials.mdx Documents the use-level grant model, masking boundary, and headless secret-resolution behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Request[Copilot code requests KEY] --> Grant{Active credential grant?}
  Grant -->|No| Deny[Deny secret mount]
  Grant -->|Yes| Scope{Secret scope}
  Scope -->|Workspace| Mount[Decrypt and mount value]
  Scope -->|Own Personal| Mount
  Scope -->|Another owner's Personal| Mount
  Mount --> Usage[Record usage under credential owner]
Loading

Reviews (2): Last reviewed commit: "docs(secrets): stop implying Personal se..." | Re-trigger Greptile

Comment thread apps/docs/content/docs/en/platform/credentials.mdx
icecrasher321 and others added 2 commits August 22, 2026 18:24
Mounting a saved secret into Copilot code required credential-admin on that
key, while a workflow Function block resolves the same secret for the same
person at use level through getPersonalAndWorkspaceEnv. Copilot reaches that
path itself — edit_workflow plus run_workflow — so the admin bar contained
nothing. It redirected a Credential Member through a detour that mutates a
persisted workflow, while the direct path is ephemeral and files a usage row.

The inconsistency was also internal to Copilot: the secret names advertised to
the model come from getAccessibleEnvCredentials and getPersonalAndWorkspaceEnv,
both role-agnostic, so Copilot listed every secret the caller could use and
then refused to mount all but the admin ones.

Widen the workspace and shared-personal predicates to any active grant, and
drop the matching role filter from the query. Workspace write is still
required, revoked and pending grants are still refused, and a caller with no
grant still gets nothing.

The view gate stays where Copilot cannot route around it: values remain masked
under Settings, and See usage remains admin-only, so a member's use is
recorded for whoever can rotate the key. Model-egress projection is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Copilot code-execution paragraph listed "any secret shared with you as a
Credential Member or Credential Admin" among what mounts, which reads as though
a Personal secret can be shared. It cannot through any product surface:
CredentialMembersSection renders only for workspace secrets and OAuth
credentials, and the personal-credential sync only ever grants the owner.

Narrow the sentence to Workspace grants. The comparison table's "Only you can
use" row for Personal was correct and is left alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@icecrasher321
icecrasher321 force-pushed the improvement/copilot-secret-mount-use-level branch from 75c0d6f to 3ea842f Compare August 23, 2026 01:24
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3ea842f. Configure here.

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