Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/rules/sim-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ apps/
└── realtime/ # Bun Socket.IO server (collaborative canvas)
packages/ # @sim/* — audit, auth, db, logger, realtime-protocol,
# security, tsconfig, utils, workflow-authz,
# security, tsconfig, utils, platform-authz,
# workflow-persistence, workflow-types
```

Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/sim-architecture.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ apps/
└── realtime/ # Bun Socket.IO server (collaborative canvas)

packages/ # @sim/* — audit, auth, db, logger, realtime-protocol,
# security, tsconfig, utils, workflow-authz,
# security, tsconfig, utils, platform-authz,
# workflow-persistence, workflow-types
```

Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/sim-testing.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ These modules are mocked globally — do NOT re-mock them in test files unless y
- `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store`
- `@/blocks/registry`
- `@trigger.dev/sdk`
- `@sim/workflow-authz` → `workflowAuthzMock`
- `@sim/platform-authz/workflow` → `workflowAuthzMock`

## Structure

Expand Down
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Thank you for your interest in contributing to Sim! Our goal is to provide devel
> - `apps/sim/` — the main Next.js application (App Router, ReactFlow, Zustand, Shadcn, Tailwind CSS).
> - `apps/realtime/` — a small Bun + Socket.IO server that powers the collaborative canvas. Shares DB and Better Auth secrets with `apps/sim` via `@sim/*` packages.
> - `apps/docs/` — Fumadocs-based documentation site.
> - `packages/` — shared workspace packages (`@sim/db`, `@sim/auth`, `@sim/audit`, `@sim/workflow-types`, `@sim/workflow-persistence`, `@sim/workflow-authz`, `@sim/realtime-protocol`, `@sim/security`, `@sim/logger`, `@sim/utils`, `@sim/testing`, `@sim/tsconfig`).
> - `packages/` — shared workspace packages (`@sim/db`, `@sim/auth`, `@sim/audit`, `@sim/workflow-types`, `@sim/workflow-persistence`, `@sim/platform-authz`, `@sim/realtime-protocol`, `@sim/security`, `@sim/logger`, `@sim/utils`, `@sim/testing`, `@sim/tsconfig`).
>
> Strict one-way dependency flow: `apps/* → packages/*`. Packages never import from apps. Please ensure your contributions follow this and our best practices for clarity, maintainability, and consistency.

Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ packages/
├── auth/ # @sim/auth — shared Better Auth verifier
├── db/ # @sim/db — drizzle schema + client
├── logger/ # @sim/logger
├── platform-authz/ # @sim/platform-authz — workspace + workflow authz (subpath exports)
├── realtime-protocol/ # @sim/realtime-protocol — socket op constants + zod schemas
├── security/ # @sim/security — safeCompare
├── tsconfig/ # shared tsconfig presets
├── utils/ # @sim/utils
├── workflow-authz/ # @sim/workflow-authz
├── workflow-persistence/ # @sim/workflow-persistence
└── workflow-types/ # @sim/workflow-types — pure BlockState/Loop/Parallel types
```
Expand Down Expand Up @@ -409,7 +409,7 @@ Use Vitest. Test files: `feature.ts` → `feature.test.ts`. See `.cursor/rules/s

### Global Mocks (vitest.setup.ts)

`@sim/db`, `@sim/db/schema`, `drizzle-orm`, `@sim/logger`, `@sim/workflow-authz`, `@/blocks/registry`, `@/lib/auth`, `@/lib/auth/hybrid`, `@/lib/core/utils/request`, `@trigger.dev/sdk`, and store mocks are provided globally. Do NOT re-mock them unless overriding behavior. (The `vi.mock('@/lib/auth', ...)` in the example below is an override of the global mock so `getSession` can be controlled per-test.)
`@sim/db`, `@sim/db/schema`, `drizzle-orm`, `@sim/logger`, `@sim/platform-authz/workflow`, `@/blocks/registry`, `@/lib/auth`, `@/lib/auth/hybrid`, `@/lib/core/utils/request`, `@trigger.dev/sdk`, and store mocks are provided globally. Do NOT re-mock them unless overriding behavior. (The `vi.mock('@/lib/auth', ...)` in the example below is an override of the global mock so `getSession` can be controlled per-test.)

### Standard Test Pattern

Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ packages/
├── auth/ # @sim/auth — shared Better Auth verifier
├── db/ # @sim/db — drizzle schema + client
├── logger/ # @sim/logger
├── platform-authz/ # @sim/platform-authz — workspace + workflow authz (subpath exports)
├── realtime-protocol/ # @sim/realtime-protocol — socket op constants + zod schemas
├── security/ # @sim/security — safeCompare
├── tsconfig/ # shared tsconfig presets
├── utils/ # @sim/utils
├── workflow-authz/ # @sim/workflow-authz
├── workflow-persistence/ # @sim/workflow-persistence
└── workflow-types/ # @sim/workflow-types — pure BlockState/Loop/Parallel types
```
Expand Down Expand Up @@ -409,7 +409,7 @@ Use Vitest. Test files: `feature.ts` → `feature.test.ts`. See `.cursor/rules/s

### Global Mocks (vitest.setup.ts)

`@sim/db`, `@sim/db/schema`, `drizzle-orm`, `@sim/logger`, `@sim/workflow-authz`, `@/blocks/registry`, `@/lib/auth`, `@/lib/auth/hybrid`, `@/lib/core/utils/request`, `@trigger.dev/sdk`, and store mocks are provided globally. Do NOT re-mock them unless overriding behavior. (The `vi.mock('@/lib/auth', ...)` in the example below is an override of the global mock so `getSession` can be controlled per-test.)
`@sim/db`, `@sim/db/schema`, `drizzle-orm`, `@sim/logger`, `@sim/platform-authz/workflow`, `@/blocks/registry`, `@/lib/auth`, `@/lib/auth/hybrid`, `@/lib/core/utils/request`, `@trigger.dev/sdk`, and store mocks are provided globally. Do NOT re-mock them unless overriding behavior. (The `vi.mock('@/lib/auth', ...)` in the example below is an override of the global mock so `getSession` can be controlled per-test.)

### Standard Test Pattern

Expand Down
35 changes: 28 additions & 7 deletions apps/docs/content/docs/en/platform/permissions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Every workspace has one **Owner** (the person who created it) plus any number of
- Can be removed from the workspace by the owner or other admins

<Callout type="info">
For shared (organization) workspaces, the organization's Owner and Admins are treated as Admins of every workspace in the organization, even without an explicit per-workspace invite.
For shared (organization) workspaces, the organization's Owner and Admins are full Admins of every workspace in the organization, even without an explicit per-workspace invite. They automatically see every organization workspace, have complete read, write, and management access, and their workspace role is fixed — it shows greyed out in the member list with a tooltip and cannot be changed.
</Callout>

---
Expand Down Expand Up @@ -151,10 +151,30 @@ Users can create two types of environment variables:
- Managed in **Settings**, then go to **Secrets**

### Workspace Environment Variables
- **Read permission**: Can see variable names and values
- **Write/Admin permission**: Can add, edit, and delete variables
- Available to all workspace members
- If a personal variable has the same name as a workspace variable, the personal one takes priority
- **Read**: see variable names (the values stay hidden unless you're an admin of that secret)
- **Write**: add new variables, and edit or delete ones you created
- **Admin**: add, edit, delete, and view the values of any workspace variable
- Workspace variables are a kind of workspace credential, so they follow the [Credential Access](#credential-access) rules below — workspace Admins are admins of all of them
- Available to all workspace members; if a personal variable has the same name, the personal one takes priority

---

## Credential Access

Workspace credentials — OAuth connections, service accounts, and workspace environment variables — have two roles of their own:

- **Credential Member**: can use the credential in workflows.
- **Credential Admin**: can use it and also edit, delete, and share it.

These roles follow your workspace role:

- **Workspace Admins are automatically Credential Admins** of every shared credential in the workspace (OAuth connections, service accounts, and workspace environment variables). Because organization Owners and Admins are workspace Admins everywhere, they are Credential Admins too. These automatic roles are fixed — they show greyed out with a tooltip in the credential's member list and cannot be changed.
- **Read and Write members are Credential Members** by default — they can use shared credentials but cannot edit, delete, or share them unless someone makes them a Credential Admin (you are always an admin of credentials you create).
- **Personal environment variables** are the exception: they stay private to their owner and are never shared with workspace admins.

<Callout type="info">
A Credential Admin can both use and manage a credential, so a workspace Admin can run workflows that use any shared OAuth connection in the workspace — including one another member added.
</Callout>

---

Expand All @@ -173,7 +193,7 @@ An organization has three roles: **Owner**, **Admin**, and **Member**.
- Invite and remove team members from the organization
- Create new shared workspaces under the organization
- Manage billing, seat count, and subscription settings
- Access all shared workspaces within the organization as a workspace Admin
- Access every shared workspace in the organization as a workspace Admin automatically (no per-workspace invite), including administering the credentials inside them
- Promote members to Admin or demote Admins to Member

<Callout type="info">
Expand All @@ -194,6 +214,7 @@ import { FAQ } from '@/components/ui/faq'
{ question: "Can I restrict which integrations or model providers a team member can use?", answer: "Yes, on Enterprise-entitled organizations. Any organization owner or admin can create permission groups with fine-grained controls, including restricting allowed integrations and allowed model providers to specific lists. You can also disable access to MCP tools, custom tools, skills, and various platform features like the knowledge base, API keys, or Copilot on a per-group basis. Permission groups are scoped to the organization and can govern either all workspaces or a specific subset — a user can belong to multiple groups but is governed by exactly one group in any given workspace." },
{ question: "What happens when a personal environment variable has the same name as a workspace variable?", answer: "The personal environment variable takes priority. When a workflow runs, if both a personal and workspace variable share the same name, the personal value is used. This allows individual users to override shared workspace configuration when needed." },
{ question: "Can an Admin remove the workspace owner?", answer: "No. The workspace owner cannot be removed from the workspace by anyone. Only the workspace owner can delete the workspace or transfer ownership to another user. Admins can do everything else, including inviting and removing other users and managing workspace settings." },
{ question: "What are permission groups and how do they work?", answer: "Permission groups are an Enterprise access control feature that lets organization owners and admins define granular restrictions beyond the standard Read/Write/Admin roles. Groups are scoped to the organization and can govern either all workspaces or a specific subset. A user can belong to multiple groups, but at most one governs them in any given workspace: a workspace-specific group takes precedence over an all-workspaces group, which takes precedence over the organization's default group. A permission group can hide UI sections (like trace spans, knowledge base, API keys, or deployment options), disable features (MCP tools, custom tools, skills, invitations), and restrict which integrations and model providers its members can access. Members are assigned manually, and an organization can designate one group as the default (always all-workspaces) that governs everyone not explicitly assigned — including external workspace members. Execution-time enforcement is based on the organization that owns the workflow's workspace, not the user's current UI context." },
{ question: "Who can manage a workspace's credentials and secrets?", answer: "Workspace Admins are automatically Credential Admins of the workspace's shared credentials — OAuth connections, service accounts, and workspace environment variables — so they can use, edit, delete, and share them, and run workflows that rely on them. Organization Owners and Admins get this too because they are workspace Admins everywhere. Read and Write members get use-only access to shared credentials unless they are explicitly made a Credential Admin. Personal environment variables are never shared; they stay private to their owner." },
{ question: "What are permission groups and how do they work?", answer: "Permission groups are an Enterprise access control feature that lets organization owners and admins define granular restrictions beyond the standard Read/Write/Admin roles. Groups are scoped to the organization and can govern either all workspaces or a specific subset. A user can belong to multiple groups, but at most one governs them in any given workspace: a workspace-specific group takes precedence over an all-workspaces group, which takes precedence over the organization's default group. A permission group can hide UI sections (like trace spans, knowledge base, API keys, or deployment options), disable features (MCP tools, custom tools, skills, invitations), and restrict which integrations and model providers its members can access. Members are assigned manually, and an organization can designate one group as the default (always all-workspaces) that governs everyone not explicitly assigned — including external workspace members. Restrictions are enforced based on the organization that owns the workflow's workspace, not on which workspace you're currently viewing." },
{ question: "How should I set up permissions for a new team member?", answer: "Start with the lowest permission level they need. Invite teammates to the organization as Members, then add them to the relevant workspace with Read permission if they only need visibility, Write if they need to create and run workflows, or Admin if they need to manage the workspace and its users. For clients, partners, or users who already belong to another Sim organization, use external workspace access so they can collaborate without joining your organization or consuming a seat." },
]} />
2 changes: 1 addition & 1 deletion apps/realtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"@sim/auth": "workspace:*",
"@sim/db": "workspace:*",
"@sim/logger": "workspace:*",
"@sim/platform-authz": "workspace:*",
"@sim/realtime-protocol": "workspace:*",
"@sim/security": "workspace:*",
"@sim/utils": "workspace:*",
"@sim/workflow-authz": "workspace:*",
"@sim/workflow-persistence": "workspace:*",
"@sim/workflow-types": "workspace:*",
"@socket.io/redis-adapter": "8.3.0",
Expand Down
2 changes: 1 addition & 1 deletion apps/realtime/src/database/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
workflowSubflows,
} from '@sim/db'
import { createLogger } from '@sim/logger'
import { getActiveWorkflowContext } from '@sim/platform-authz/workflow'
import {
BLOCK_OPERATIONS,
BLOCKS_OPERATIONS,
Expand All @@ -20,7 +21,6 @@ import {
WORKFLOW_OPERATIONS,
} from '@sim/realtime-protocol/constants'
import { randomFloat } from '@sim/utils/random'
import { getActiveWorkflowContext } from '@sim/workflow-authz'
import { loadWorkflowFromNormalizedTablesRaw } from '@sim/workflow-persistence/load'
import { mergeSubBlockValues } from '@sim/workflow-persistence/subblocks'
import { isWorkflowBlockProtected } from '@sim/workflow-types/workflow'
Expand Down
2 changes: 1 addition & 1 deletion apps/realtime/src/handlers/operations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createLogger } from '@sim/logger'
import { assertWorkflowMutable, WorkflowLockedError } from '@sim/platform-authz/workflow'
import {
BLOCK_OPERATIONS,
BLOCKS_OPERATIONS,
Expand All @@ -11,7 +12,6 @@ import {
import { WorkflowOperationSchema } from '@sim/realtime-protocol/schemas'
import { getErrorMessage } from '@sim/utils/errors'
import { generateId } from '@sim/utils/id'
import { assertWorkflowMutable, WorkflowLockedError } from '@sim/workflow-authz'
import { ZodError } from 'zod'
import { persistWorkflowOperation } from '@/database/operations'
import type { AuthenticatedSocket } from '@/middleware/auth'
Expand Down
2 changes: 1 addition & 1 deletion apps/realtime/src/handlers/subblocks.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { db } from '@sim/db'
import { workflow, workflowBlocks } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { assertWorkflowMutable, WorkflowLockedError } from '@sim/platform-authz/workflow'
import { SUBBLOCK_OPERATIONS } from '@sim/realtime-protocol/constants'
import { getErrorMessage } from '@sim/utils/errors'
import { assertWorkflowMutable, WorkflowLockedError } from '@sim/workflow-authz'
import { isWorkflowBlockProtected } from '@sim/workflow-types/workflow'
import { and, eq } from 'drizzle-orm'
import type { AuthenticatedSocket } from '@/middleware/auth'
Expand Down
2 changes: 1 addition & 1 deletion apps/realtime/src/handlers/variables.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { db } from '@sim/db'
import { workflow } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { assertWorkflowMutable, WorkflowLockedError } from '@sim/platform-authz/workflow'
import { VARIABLE_OPERATIONS } from '@sim/realtime-protocol/constants'
import { getErrorMessage } from '@sim/utils/errors'
import { assertWorkflowMutable, WorkflowLockedError } from '@sim/workflow-authz'
import { eq } from 'drizzle-orm'
import type { AuthenticatedSocket } from '@/middleware/auth'
import { checkWorkflowOperationPermission } from '@/middleware/permissions'
Expand Down
2 changes: 1 addition & 1 deletion apps/realtime/src/middleware/permissions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const { mockAuthorize } = vi.hoisted(() => ({
mockAuthorize: vi.fn(),
}))

vi.mock('@sim/workflow-authz', () => ({
vi.mock('@sim/platform-authz/workflow', () => ({
authorizeWorkflowByWorkspacePermission: mockAuthorize,
}))

Expand Down
2 changes: 1 addition & 1 deletion apps/realtime/src/middleware/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { db } from '@sim/db'
import { workflow } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { authorizeWorkflowByWorkspacePermission } from '@sim/platform-authz/workflow'
import {
BLOCK_OPERATIONS,
BLOCKS_OPERATIONS,
Expand All @@ -11,7 +12,6 @@ import {
VARIABLE_OPERATIONS,
WORKFLOW_OPERATIONS,
} from '@sim/realtime-protocol/constants'
import { authorizeWorkflowByWorkspacePermission } from '@sim/workflow-authz'
import { and, eq, isNull } from 'drizzle-orm'

const logger = createLogger('SocketPermissions')
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ apps/
└── realtime/ # Bun Socket.IO server (collaborative canvas)
packages/ # @sim/* — audit, auth, db, logger, realtime-protocol,
# security, tsconfig, utils, workflow-authz,
# security, tsconfig, utils, platform-authz,
# workflow-persistence, workflow-types
```

Expand Down
Loading
Loading