improvement(secrets): gate Copilot code mounting at use level - #7004
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryHigh Risk Overview
Docs in Reviewed by Cursor Bugbot for commit 3ea842f. Configure here. |
Greptile SummaryThis PR aligns Copilot secret mounting with use-level credential grants while retaining active-status checks, masking, and usage attribution.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains. No blocking failure remains.
|
| 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]
Reviews (2): Last reviewed commit: "docs(secrets): stop implying Personal se..." | Re-trigger Greptile
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>
75c0d6f to
3ea842f
Compare
|
@cursor review |
There was a problem hiding this comment.
✅ 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.
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) plusrun_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) andgetPersonalAndWorkspaceEnv(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 matchingeq(credentialMember.role, 'admin')removed from the query.Shared-personal is relaxed too, not just workspace:
getPersonalAndWorkspaceEnvalready 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
writeis 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
write+role=memberonSTRIPE_SECRET_KEY""""CopilotCodeSecretAccessErrorOne behavioural note:
selectDistinctOnnow picks among member-granted shared-personal rows too, so for a name shared by two people the most-recently-updated credential wins — converging on theupdatedAt descrulegetPersonalAndWorkspaceEnvalready 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 ornull) and is deliberately untouched — an unmatched sender still mounts nothing. Separately, an allowlisted external sender executes asws.ownerIdwithautoExecuteTools: trueand no permission gating (tool permissions are interactive-only,lifecycle/run.ts:205-215); whether the headless toolset exposesedit_workflow/run_workflowthere is untraced and worth its own issue.Test plan
secret-mount-materializer.server.test.ts— 26 passed. Added a member/active mount case, addedmember/revoked+member/pendingto 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-checkclean, biome clean.🤖 Generated with Claude Code