Sitelet https://github.com/simstudioai/sim/pull/7080
Skip to content

improvement(access-control): wire tool and model permissions into copilot editing - #7080

Merged
waleedlatif1 merged 4 commits into
stagingfrom
wt-copilot-tool-perms
Aug 25, 2026
Merged

improvement(access-control): wire tool and model permissions into copilot editing#7080
waleedlatif1 merged 4 commits into
stagingfrom
wt-copilot-tool-perms

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Permission groups already supported a per-tool denylist (deniedTools) and model restrictions, but only the canvas honored them — the copilot edit path gated on block type alone, so Sim could build workflows using tools and models the user is not allowed to run. The executor refused them at run time instead, so the workflow died on first run.
  • Enforce the operation and model gates in the edit engine: a denied value is dropped and reported as a skipped item, so the block still lands and the model picks something it may use. An edit never clears a value the caller is allowed to keep.
  • Gate agent-block tool entries by their resolved tool id, and gate the trigger-config fan-out, which redistributes a persisted aggregate without passing through input validation.
  • Stop advertising what the viewer cannot use across all four copilot discovery surfaces — VFS stamping, list_integration_tools, the deferred callable-tool payload, and get_blocks_metadata — behind one shared projectIntegrationToolsForViewer. filterExposedIntegrationTools now requires both gates so a new surface cannot apply a partial one.
  • Add createToolAccessGate / createModelAccessGate as the single place a denylist becomes a decision, shared by the client hook and every server path so the two cannot drift on what a tool id means.
  • New model_not_allowed skipped-item type; regenerated the v2 OpenAPI spec and CLI API.

No behavior change for a workspace without a permission group: both gates return a shared allow-all singleton, the denied-operation pass is skipped, and the VFS hands through its process-global schema cache untouched.

Type of Change

  • Bug fix

Testing

Tested manually. 21 new tests, each verified to fail when its gate is disabled. Full suite green (28,583 passing), bun run lint clean, all 33 audits pass including check:api-validation:strict, check:client-boundary, and check:openapi.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…ilot editing

Permission groups already supported a per-tool denylist (deniedTools) and model
restrictions, but only the canvas honored them. The copilot edit path gated on
block type alone, so Sim could build workflows using tools and models the user
was not allowed to run — the executor refused them at run time instead.

Enforce both at authoring time, and stop advertising what the viewer cannot use.
@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 25, 2026 10:10pm

Request Review

@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR applies workspace tool and model permissions consistently across Copilot editing and capability discovery.

  • Adds shared tool and model access gates used by client and server paths.
  • Filters denied tools from Copilot schemas, metadata, VFS output, and integration-tool discovery.
  • Rejects disallowed tool and model values during workflow editing while retaining permitted existing values.
  • Keys cached integration schemas by the effective permission policy and updates generated API contracts.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/lib/permission-groups/model-access.ts Defines the shared model gate and now follows the required absolute-import convention.
apps/sim/lib/permission-groups/operation-access.ts Centralizes normalized tool permission decisions for editing, execution, and discovery.
apps/sim/lib/copilot/integration-tool-projection.ts Projects integration tools through deployment, integration, and per-tool permission gates.
apps/sim/lib/copilot/chat/payload.ts Uses the shared projection and keys schema caching by the effective permission policy.
apps/sim/lib/workflows/editing/operations.ts Wires tool and model permission checks into workflow-edit operations.
apps/sim/lib/workflows/editing/builders.ts Filters disallowed tool and model values while constructing edited blocks.
apps/sim/lib/copilot/vfs/serializers.ts Restricts serialized Copilot metadata to operations available to the viewer.
apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts Applies permission-aware integration and operation projection to block metadata.
apps/docs/openapi-v2-workflows.json Adds the model-not-allowed skipped-item reason to the generated workflow API schema.
packages/sim-cli/src/generated/v2-api.ts Synchronizes the generated CLI types with the updated skipped-item contract.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Permission-group config] --> B[Shared tool and model gates]
  B --> C[Copilot discovery projection]
  B --> D[Workflow edit validation]
  C --> E[VFS and metadata]
  C --> F[Integration tool schemas]
  D --> G[Persisted permitted workflow]
  D --> H[Skipped-item response]
Loading

Reviews (4): Last reviewed commit: "fix(access-control): key the copilot sch..." | Re-trigger Greptile

Comment thread apps/sim/lib/permission-groups/model-access.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

On the deferred schema cache (payload.ts) — leaving this as-is deliberately, reasoning below.

The cache key is [userId, workspaceId, schemaSurface, visSignature], so it already varies with the viewer. Two users under different policies never share an entry; the only staleness is time-based, for the same user, bounded by the 5s TTL.

Three things make that acceptable:

  • It is advisory, not authoritative. executeTool calls assertPermissionsAllowed({ toolId }) before every tool run, and that path is uncached. A tool advertised during the TTL window still cannot execute — the model would just get a permission error instead of never seeing it.
  • The staleness is not new. allowedIntegrations was already gated inside this same cached function before this PR. This change widens what the cached projection covers, not whether it can be briefly stale.
  • Keying on policy would cost more than it saves. getUserPermissionConfig is a DB-backed read with no cache of its own, so folding a policy signature into the key means loading it before the cache lookup — a query on every payload build, which is the cost this 5s cache exists to avoid.

If we want the window closed, the right fix is caching getUserPermissionConfig itself (the way block visibility is already memoized), which would benefit every caller rather than just this one — worth doing separately, not in this PR.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@cubic-dev-ai cubic-dev-ai 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.

Review completed against the latest diff

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/sim/lib/copilot/integration-tools.ts
Comment thread apps/sim/lib/copilot/vfs/workspace-vfs.ts
Comment thread apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts Outdated
Comment thread apps/sim/lib/permission-groups/model-access.ts
The VFS stamped every integration schema from the shared static map before the
per-viewer loop re-authored the permitted subset, so a denied operation's schema
stayed published. Skip the shared copy for integration paths; the viewer loop is
the only projection that knows the denylist.

Block metadata resolved denied operations from the catalog's `operation.toolId`,
which the projection fills only from `tools.config.tool` — a block whose
operation ids are its tool ids left it undefined and read as fully permitted.
Resolve through the shared operation gate instead.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

The deferred integration-tool schemas now depend on the viewer's permission
group, but the cache key encoded only identity and block visibility, so an
admin's change to deniedTools took effect only when the entry expired.

Resolve the config before the key and add a gate signature alongside the
existing visibility signature, mirroring how block visibility already keys the
same cache. The read moves out of the cached section rather than being added:
what the entry caches is a user-tool schema per exposed integration tool, which
dominates it.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Reconsidered the schema-cache point and fixed it in 9438339 — my earlier reasoning was wrong on the cost.

I had argued that keying on policy would mean a DB read per payload build, defeating the cache. That mis-weighed what the cache is actually for: the expensive part of the entry is a user-tool schema built per exposed integration tool, not the permission lookup. Moving the config read out of the cached section and in front of the key keeps essentially all of the benefit, and the file already establishes exactly this pattern — visibilitySignature is resolved before the key and threaded into the builder for the same reason.

So the key now carries an integrationGateSignature (sorted allowedIntegrations + deniedTools) alongside the visibility signature, and the config is passed through instead of being loaded again inside. An admin's change takes effect on the next build rather than when the entry expires.

Added a test that denies a tool between two builds for the same viewer and surface, so only the policy component of the key can force the rebuild — verified it fails when that component is removed.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
waleedlatif1 merged commit 32a15fd into staging Aug 25, 2026
29 checks passed
@waleedlatif1
waleedlatif1 deleted the wt-copilot-tool-perms branch August 25, 2026 22:41
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