diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82f4b3299cc..62557c62854 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -611,6 +611,274 @@ jobs: "${IMAGE}:${SHA}-amd64" "${IMAGE}:${SHA}-arm64" fi + # Sign the published images and attach SLSA provenance and an SBOM to each. + # + # This runs after create-ghcr-manifests rather than inside the build because + # buildx's own provenance/sbom attestations stay off (see the note in + # .github/actions/docker-build): the extra manifests they add to an index + # break the `imagetools create` retagging that promote-images depends on. + # Attaching attestations here instead leaves the index itself untouched — they + # are stored as separate referrer manifests that point at it. + # + # Resolve the set of digests that actually got published, so the attestation + # job below covers every tag a customer can pull. + # + # A static list is not enough. `imagetools create` always writes an INDEX, so + # `:-amd64` is a single-entry index whose digest differs from the + # `:-amd64` manifest it wraps — attesting the manifest leaves the tag + # people actually pin unverifiable. Which tags exist also varies per run: + # version tags only on a release, and the latest tags only when the monotonic + # guard in create-ghcr-manifests passed. Resolving tag -> digest here and + # de-duplicating is what keeps the two in step without hardcoding that logic + # twice. + attest-subjects: + name: Resolve Attestation Subjects + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 10 + needs: [create-ghcr-manifests, detect-version] + if: >- + !cancelled() && + needs.create-ghcr-manifests.result == 'success' && + needs.detect-version.result == 'success' && + github.event_name == 'push' && github.ref == 'refs/heads/main' + permissions: + contents: read + packages: read + outputs: + subjects: ${{ steps.resolve.outputs.subjects }} + count: ${{ steps.resolve.outputs.count }} + steps: + - name: Login to GHCR + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Resolve published tags to digests + id: resolve + env: + IS_RELEASE: ${{ needs.detect-version.outputs.is_release }} + VERSION: ${{ needs.detect-version.outputs.version }} + SHA: ${{ github.sha }} + run: | + set -euo pipefail + + IMAGES="simstudio migrations realtime pii cron" + + # Prints the digest, or nothing when the tag is genuinely absent. + # + # An absent tag and a registry hiccup both make `inspect` fail, and + # treating them alike is how a published image silently ends up + # unsigned while this job still goes green. So: retry, and only report + # "absent" when the registry actually says the manifest is unknown. + # Anything else fails the step. + digest_of() { + local ref="$1" attempt raw err absent=0 + for attempt in 1 2 3; do + if raw="$(docker buildx imagetools inspect "$ref" --format '{{json .Manifest}}' 2>/tmp/inspect.err)"; then + printf '%s' "$raw" | jq -r '.digest // empty' + return 0 + fi + err="$(cat /tmp/inspect.err)" + # Absence is retried like any other failure: GHCR can report a + # just-published alias as unknown for a moment, and accepting that + # on the first attempt would skip a tag this run did publish. + case "$err" in + *"not found"*|*MANIFEST_UNKNOWN*|*"no such manifest"*|*"NAME_UNKNOWN"*) absent=1 ;; + *) absent=0 ;; + esac + if [ "$attempt" -lt 3 ]; then + sleep "$((attempt * 3))" + fi + done + # Only call it absent if the registry said so on the final attempt. + if [ "$absent" -eq 1 ]; then + return 0 + fi + echo "::error::Could not inspect ${ref} after 3 attempts: ${err}" >&2 + return 1 + } + + # Records a subject. `platform` tells the attestation job whether this + # digest is a single-architecture image, which is the only case where + # a Syft SBOM describes what the puller actually gets. + emit() { + jq -nc --arg image "$1" --arg digest "$2" --arg platform "$3" \ + '{image: $image, digest: $digest, platform: $platform}' >> /tmp/subjects.jsonl + } + + : > /tmp/subjects.jsonl + for name in $IMAGES; do + image="ghcr.io/simstudioai/${name}" + + # The sha tags are this run's own output. All three must resolve — + # a missing one means the publish did not complete, not that the tag + # is optional. + seen="" + sha_index="" + for tag in "${SHA}" "${SHA}-amd64" "${SHA}-arm64"; do + digest="$(digest_of "${image}:${tag}")" + if [ -z "$digest" ]; then + echo "::error::${image}:${tag} was not published by this run" + exit 1 + fi + case "$tag" in + *-amd64) platform=amd64 ;; + *-arm64) platform=arm64 ;; + *) platform=index; sha_index="$digest" ;; + esac + seen="$seen $digest" + emit "$image" "$digest" "$platform" + done + + # A moving alias is only taken when it resolves to the same index + # digest this run published — content identity, which is what a + # digest can prove. create-ghcr-manifests holds the latest tags back when + # its monotonic guard sees a newer commit, and they then still point + # at an older build — attesting those would put this run's signature + # and provenance on an image it did not produce. The per-arch + # aliases are published in the same guarded block as `latest`, so + # that one comparison gates all three. + alias_groups="latest" + if [ "${IS_RELEASE}" = "true" ]; then + alias_groups="${alias_groups} ${VERSION}" + fi + + for alias in $alias_groups; do + # A mismatch has two very different causes: the guard deliberately + # held the tag back, or GHCR is still serving the previous digest + # moments after this run wrote it. Re-read before concluding the + # former, or a read landing a second early silently drops three + # subjects from the matrix. + alias_index="" + for alias_attempt in 1 2 3; do + alias_index="$(digest_of "${image}:${alias}")" + [ "$alias_index" = "$sha_index" ] && break + [ "$alias_attempt" -lt 3 ] && sleep 5 || true + done + + if [ "$alias_index" != "$sha_index" ]; then + # `latest` is legitimately allowed to lag: the monotonic guard in + # create-ghcr-manifests holds it back when the branch has moved + # on, and it then belongs to an older run that attested it. + if [ "$alias" = "latest" ]; then + echo "Skipping latest* for ${image}: it does not point at this run's index." + continue + fi + # A version tag has no such carve-out. This run published it, so + # a release must not ship a version image nothing has attested. + echo "::error::${image}:${alias} does not resolve to this run's index (${sha_index:-none}); refusing to publish an unattested release image" + exit 1 + fi + for tag in "${alias}" "${alias}-amd64" "${alias}-arm64"; do + digest="$(digest_of "${image}:${tag}")" + if [ -z "$digest" ]; then + echo "::error::${image}:${tag} is missing though ${image}:${alias} is current" + exit 1 + fi + case " $seen " in *" $digest "*) continue ;; esac + seen="$seen $digest" + case "$tag" in + *-amd64) emit "$image" "$digest" amd64 ;; + *-arm64) emit "$image" "$digest" arm64 ;; + *) emit "$image" "$digest" index ;; + esac + done + done + done + + if [ ! -s /tmp/subjects.jsonl ]; then + echo "::error::Resolved no image digests to attest" + exit 1 + fi + + echo "Resolved $(wc -l < /tmp/subjects.jsonl) distinct subjects:" + cat /tmp/subjects.jsonl + echo "subjects=$(jq -sc . /tmp/subjects.jsonl)" >> "$GITHUB_OUTPUT" + echo "count=$(wc -l < /tmp/subjects.jsonl | tr -d ' ')" >> "$GITHUB_OUTPUT" + + # One leg per distinct published digest. Attesting each subject separately is + # also what makes the SBOMs truthful: the amd64 and arm64 images contain + # different packages, and one SBOM attached to the index cannot describe both. + attest-images: + name: Attest Images + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 15 + needs: [attest-subjects] + if: >- + !cancelled() && + needs.attest-subjects.result == 'success' + permissions: + contents: read + packages: write + # Sigstore signs against the runner's OIDC identity; no key material is stored. + id-token: write + attestations: write + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.attest-subjects.outputs.subjects) }} + + steps: + - name: Login to GHCR + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Skipped for index subjects: Syft resolves an index to one platform, so + # the SBOM it produces would describe amd64 while the index also serves + # arm64. The per-arch subjects below carry an accurate SBOM each, and the + # index still gets a signature and provenance. + # + # Scanned by the `-` tag rather than by `matrix.digest`. Half + # the per-arch subjects are single-entry INDEXES (`imagetools create` + # writes an index even from one manifest), and Syft resolves an index + # against the RUNNER's platform — so an arm64-only index fails outright on + # an amd64 runner with "no child with platform linux/arm64". The sha tag is + # the plain manifest that index wraps: identical content, no platform + # resolution, and one pull shared by both subjects instead of two. + - name: Generate SBOM + if: matrix.platform != 'index' + uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2 + with: + image: ${{ matrix.image }}:${{ github.sha }}-${{ matrix.platform }} + format: spdx-json + output-file: sbom.spdx.json + # The action's own release upload is for workflows triggered by a + # release; these attach to the image instead. + upload-artifact: false + upload-release-assets: false + + - name: Attest SBOM + if: matrix.platform != 'index' + uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0 + with: + subject-name: ${{ matrix.image }} + subject-digest: ${{ matrix.digest }} + sbom-path: sbom.spdx.json + # Stored alongside the image so a mirrored registry carries the + # attestation with it, rather than only being retrievable from GitHub. + push-to-registry: true + + - name: Attest build provenance + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-name: ${{ matrix.image }} + subject-digest: ${{ matrix.digest }} + push-to-registry: true + + - name: Install Cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + + # The attestations above prove how the image was built; this is the plain + # signature that admission controllers (Kyverno, the Sigstore policy + # controller) verify before admitting a pod. + - name: Sign image + run: cosign sign --yes "${{ matrix.image }}@${{ matrix.digest }}" + # Check if docs changed # Smallest runner on purpose: a depth-2 checkout plus a path filter, no # install and no build. @@ -652,11 +920,22 @@ jobs: name: Create GitHub Release runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 10 - needs: [create-ghcr-manifests, detect-version] - # Explicit results: see migrate's comment. + needs: [create-ghcr-manifests, attest-subjects, attest-images, detect-version] + # Explicit results: see migrate's comment. attest-images is a gate, not just + # an ordering edge — a release must not advertise images whose signature or + # attestation failed to publish. The count check is not redundant: a matrix + # built from an empty include list produces no legs and still reports + # success, so without it an empty subject set would open the gate. + # + # Note this gates the GitHub release, not the production deploy: CodePipeline + # fires from the ECR tags moved by promote-images, upstream of this job, and + # only the GHCR mirrors are attested. if: >- !cancelled() && needs.create-ghcr-manifests.result == 'success' && + needs.attest-subjects.result == 'success' && + needs.attest-subjects.outputs.count != '0' && + needs.attest-images.result == 'success' && needs.detect-version.result == 'success' && needs.detect-version.outputs.is_release == 'true' permissions: diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 05e2ee8a12b..fa3e7ede4c9 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -6,11 +6,19 @@ on: paths: - 'helm/sim/**' - '.github/workflows/helm.yml' + # The image inventory is generated from the chart and checked here, so a + # change to its generator has to run this workflow too. + - 'scripts/generate-image-manifest.ts' + - 'package.json' pull_request: branches: [main, staging, dev] paths: - 'helm/sim/**' - '.github/workflows/helm.yml' + # The image inventory is generated from the chart and checked here, so a + # change to its generator has to run this workflow too. + - 'scripts/generate-image-manifest.ts' + - 'package.json' concurrency: group: helm-${{ github.ref }} @@ -43,6 +51,13 @@ jobs: - name: Scheduler parity (docker/crontab vs helm cronjobs) run: bun run scripts/check-cron-parity.ts + # helm/sim/images.yaml is what an operator mirrors into a disconnected + # registry, so a chart change that adds an image has to update it. Lives + # here rather than in `check:audits` because it renders the chart, and the + # audits job has no Helm. The script imports only node builtins. + - name: Image inventory is current + run: bun run images:check + - name: Helm lint run: helm lint helm/sim --values helm/sim/ci/default-values.yaml diff --git a/apps/docs/content/docs/platform/enterprise/access-control.mdx b/apps/docs/content/docs/platform/enterprise/access-control.mdx index 3ff1b78f6f2..a318cc4c377 100644 --- a/apps/docs/content/docs/platform/enterprise/access-control.mdx +++ b/apps/docs/content/docs/platform/enterprise/access-control.mdx @@ -13,7 +13,7 @@ Access Control lets organization admins define permission groups that restrict w ## How it works -Access control is built around **permission groups**. Each group belongs to a specific organization and has a name, an optional description, a **workspace scope**, an optional **member** list, and a configuration that defines what its members can and cannot do. The organization's single **default group** is org-wide; every other group targets a **specific set of workspaces**. A non-default group with **no members** governs **all members** of its workspaces (including external members); adding members narrows it to only those people. Personal workspaces that do not belong to an organization have no permission groups. +Each group belongs to a specific organization and has a name, an optional description, a **workspace scope**, an optional **member** list, and a configuration that defines what its members can and cannot do. Personal or grandfathered workspaces that do not belong to an organization have no permission groups. Sim resolves the governing group for a user in a workspace deterministically: @@ -27,15 +27,15 @@ Assignment-time checks keep this unambiguous: a workspace has at most one all-me When a user runs a workflow or uses Chat, Sim reads the resolved group's configuration and applies it: - **In the executor:** If a workflow uses a disallowed block type or model provider, execution halts immediately with an error. This applies to both manual runs and scheduled or API-triggered deployments. -- **In Chat:** Disallowed blocks are filtered out of the block list so they cannot be added to a workflow. Disallowed tool types (MCP, custom tools, skills) are skipped if Sim attempts to use them. +- **In Chat:** Disallowed blocks are filtered out of the block list so they cannot be added to a workflow. Disallowed tool types (MCP, custom tools, skills) are dropped while Sim generates a workflow, with the omission recorded. If a run reaches one anyway, the block fails with an error naming the restriction rather than skipping it. --- ## Setup -### 1. Open Access Control settings +### 1. Open Permission groups settings -Go to **Settings → Enterprise → Access Control** from any workspace in your organization. Permission groups are defined once at the organization level and apply to every workspace under it. Only organization owners and admins can manage them. +Go to **Settings → Organization → Permission groups** from any workspace in your organization. Permission groups are defined once at the organization level and apply to every workspace under it. Only organization owners and admins can manage them. Access Control settings showing a list of permission groups: Contractors, Sales, Engineering, and Marketing, each with Details and Delete actions @@ -45,105 +45,148 @@ Click **+ Create** and enter a name (required) and optional description. A group ### 3. Configure permissions -Click **Details** on a group, then open **Configure Permissions**. Non-default groups have a **Members** tab plus three restriction tabs; the default group has only the restriction tabs. +Click **Details** on a group to open its configuration. It has four tabs — **General**, **Model Providers**, **Blocks**, and **Platform**. Name, description, and the permission settings are buffered until you save; the workspace scope, the default-group switch, and member changes are applied immediately, so **Discard** does not undo them. -#### Members +Throughout the editor, a **checked** box means allowed. Clearing a checkbox is what applies a restriction. -A workspace-scoped group with **no members** applies to everyone in its workspaces (including external members). Add members here — searching your organization by name or email — to restrict the group to only those people; removing every member returns it to governing everyone. The default group ignores members and has no Members tab. +#### General + +Holds the group's **Name** and **Description**, the **Default group** switch, the **Workspaces** the group governs, and its **Members**. + +A workspace-scoped group with **no members** applies to everyone in its workspaces (including external members). Add members here — searching your organization by name or email — to restrict the group to only those people; removing every member returns it to governing everyone. The default group ignores members and governs every workspace in the organization. #### Model Providers Controls which AI model providers members of this group can use. -Model Providers tab showing a grid of AI providers including Ollama, vLLM, OpenAI, Anthropic, Google, Azure OpenAI, and others with checkboxes to allow or restrict access The list shows all providers available in Sim. +Model Providers tab showing a grid of AI providers including Ollama, vLLM, OpenAI, Anthropic, Google, Azure OpenAI, and others with checkboxes to allow or restrict access + +The list shows all providers available in Sim. - **All checked (default):** All providers are allowed. - **Subset checked:** Only the selected providers are allowed. Any workflow block or agent using a provider not on the list will fail at execution time. +Expand a provider row to reach its **model denylist**. Clearing individual models blocks exactly those models while leaving the rest of the provider available — useful when a provider is sanctioned but a specific model is not. + #### Blocks Controls which workflow blocks members can place and execute. -Blocks tab showing Core Blocks (Agent, API, Condition, Function, Knowledge, etc.) and Tools (integrations like 1Password, Ahrefs, Airtable, and more) with checkboxes to allow or restrict each Blocks are split into two sections: **Core Blocks** (Agent, API, Condition, Function, etc.) and **Tools** (all integration blocks). +Blocks tab showing Core Blocks (Agent, API, Condition, Function, Knowledge, etc.) and Tools (integrations like 1Password, Ahrefs, Airtable, and more) with checkboxes to allow or restrict each + +Blocks are split into two sections: **Core Blocks** (Agent, API, Condition, Function, etc.) and **Tools** (all integration blocks). - **All checked (default):** All blocks are allowed. - **Subset checked:** Only the selected blocks are allowed. Workflows that already contain a disallowed block will fail when run — they are not automatically modified. +Expand an integration block to reach its **tool denylist**. Clearing individual tools blocks those operations while leaving the rest of the integration usable — for example, allowing a member to read from a service but not to delete in it. + The `start_trigger` block (the entry point of every workflow) is always allowed and cannot be restricted. #### Platform -Controls visibility of platform features and modules. +Controls the modules, actions, and credentials available to group members. Every row refuses at the API, not only in the UI — clearing a box revokes the access; it does not merely hide a tab. -Platform tab showing feature toggles grouped by category: Sidebar (Knowledge Base, Tables), Workflow Panel (Copilot), Settings Tabs, Tools, Deploy Tabs, Features, Logs, and Collaboration Each checkbox maps to a specific feature; checking it hides or disables that feature for group members. +Platform tab showing feature toggles grouped by category -**Sidebar** +**Modules** -| Feature | Effect when checked | -|---------|-------------------| -| Knowledge Base | Hides the Knowledge Base section from the sidebar | -| Tables | Hides the Tables section from the sidebar | +| Feature | What clearing it withholds | +|---------|---------------------------| +| Chat | Revokes Chat. Members cannot ask Sim to build or edit anything. | +| Sim Mailer | Revokes the Sim Mailer inbox. Members cannot read or send mail. | -**Workflow Panel** +**Knowledge Base** -| Feature | Effect when checked | -|---------|-------------------| -| Copilot | Hides the Copilot panel inside the workflow editor | +| Feature | What clearing it withholds | +|---------|---------------------------| +| Knowledge Base | Revokes the Knowledge Base module. Members cannot open, search, or query any knowledge base. | +| Knowledge Base Creation | Prevents creating knowledge bases, leaving existing ones queryable. | +| Knowledge Base Uploads | Prevents uploading local documents, leaving sanctioned connectors as the only source. | -**Settings Tabs** +The **Knowledge Base** row also carries a **connector allowlist** — *Connectors knowledge bases may sync from*. Leave it untouched to allow every connector, or select a subset to limit which external sources a knowledge base may sync. -| Feature | Effect when checked | -|---------|-------------------| -| Integrations | Hides the Integrations tab in Settings | -| Secrets | Hides the Secrets tab in Settings | -| API Keys | Hides the Sim Keys tab in Settings | -| Files | Hides the Files tab in Settings | +**Tables** -**Tools** +| Feature | What clearing it withholds | +|---------|---------------------------| +| Tables | Revokes the Tables module. Members cannot read or write any table. | +| Table Creation | Prevents creating tables, leaving existing ones usable. | +| Table Export | Prevents downloading a whole table as CSV or JSON. | + +**Files** -| Feature | Effect when checked | -|---------|-------------------| -| MCP Tools | Disables the use of MCP tools in workflows and agents | -| Custom Tools | Disables the use of custom tools in workflows and agents | -| Skills | Disables the use of Sim Skills in workflows and agents | +| Feature | What clearing it withholds | +|---------|---------------------------| +| Files | Revokes the Files module. Members cannot list, upload, or download workspace files. | +| Public Sharing | Revokes public file sharing. Members cannot create a share link. | +| Bulk Download | Prevents downloading folders as an archive. | -**Deploy Tabs** +The **Public Sharing** row also carries an **auth-mode allowlist** — *Auth modes public file-share links may use* (Anyone with link, Password, Email, SSO). Select a subset to force share links onto stronger authentication. -| Feature | Effect when checked | -|---------|-------------------| -| API | Hides the API deployment tab | -| MCP | Hides the MCP deployment tab | -| Chat | Hides the Chat deployment tab | -| Template | Hides the Template deployment tab | +**Deployment** -**Features** +| Feature | What clearing it withholds | +|---------|---------------------------| +| Public API | Revokes public API access. Calls to a deployed workflow are refused. | +| API Deployment | Prevents deploying a workflow as an API endpoint. | +| MCP Server | Prevents exposing a workflow as an MCP server. | +| Chat Deployment | Prevents publishing a workflow as a chat. | +| Webhook Triggers | Prevents making a workflow reachable from an inbound webhook. | -| Feature | Effect when checked | -|---------|-------------------| -| Sim Mailer | Hides the Sim Mailer (Inbox) feature | -| Public API | Disables public API access for deployed workflows | +The **Chat Deployment** row also carries an **auth-mode allowlist** — *Auth modes chat deployments may use* (Public, Password, Email, SSO). + +**Tools** + +| Feature | What clearing it withholds | +|---------|---------------------------| +| MCP Tools | Blocks agents from calling MCP tools. | +| Custom Tools | Blocks agents from calling user-defined custom tools. | +| Skills | Blocks agents from loading skills. | +| Tool Auto-Approval | Prevents auto-approving tool calls, so every call must be confirmed. | **Logs** -| Feature | Effect when checked | -|---------|-------------------| -| Trace Spans | Hides trace span details in execution logs | +| Feature | What clearing it withholds | +|---------|---------------------------| +| Trace Spans | Withholds per-block trace spans from logs and from the API. | +| Log Export | Prevents downloading execution logs as a CSV. | +| Execution Cost | Withholds execution cost. Logs and member exports omit cost and token spend; organization-level data drains, configurable by org admins only, are not projected. | **Collaboration** -| Feature | Effect when checked | -|---------|-------------------| -| Invitations | Disables the ability to invite new members to the workspace | +| Feature | What clearing it withholds | +|---------|---------------------------| +| Invitations | Prevents inviting anyone to a workspace or to the organization. | +| Workspace Creation | Prevents creating new workspaces. A new one is not covered by any workspace-scoped group until you add it, though the organization's default group still governs it. | +| Member Directory | Withholds the member directory. Members cannot see the names or email addresses of other members. Organization owners and admins keep access — the roster routes exempt those roles. | -### 4. Choose who it applies to +**Credentials & Access** + +| Feature | What clearing it withholds | +|---------|---------------------------| +| Integrations | Revokes integration connections. Members cannot view, add, or remove an OAuth connection. | +| Secrets | Revokes secrets. Members cannot read, add, or change a workspace environment variable. | +| API Keys | Revokes workspace API keys. Members cannot list, create, or revoke one. | +| Personal API Keys | Prevents members from using a personal API key against this workspace. | +| Personal Credentials | Prevents connecting personal credentials, leaving only workspace-shared ones. | +| CLI Access | Prevents approving a CLI login, which mints a key for the public API. | + +##### Rows read from the organization default group + +Two rows — **Workspace Creation** and **Member Directory** — are read only from the organization's **default group**, because the act they govern names no workspace. On any other group the editor renders them inert, tags them **Organization**, and skips them in **Select All**. Set them on the default group. + +Five more rows — **Integrations**, **API Keys**, **Invitations**, **Personal API Keys**, and **CLI Access** — apply on the group in front of you for anything scoped to one of its workspaces. The account-level path of the same action falls back to the default group: minting a personal key, an organization-wide invitation, an account-level CLI login. To close one of these completely, set it on the default group as well. -A workspace-scoped group applies to **all members of its workspaces by default** — including external members. To restrict it to specific people instead, open **Configure Permissions → Members** and add members by searching your organization by name or email. Removing every member returns the group to governing everyone in its workspaces. +### 4. Choose who it applies to A user is governed by one group per workspace, so adding a user is rejected when it would conflict with another of their groups on a shared workspace (skipped rather than added in bulk). The default group ignores members entirely — it always governs everyone not covered by a workspace group. -Manage which workspaces a group governs from the **Workspaces** list in the group's **Details** view (Add and Remove). A non-default group is created targeting at least one workspace, but you can later remove all of them — a group with no workspaces simply governs nothing until you add one back. +A workspace also has at most one all-members group. Adding a workspace to a group, or removing a group's last member, is rejected when doing so would violate that — memberships and scopes are never silently moved. + +Manage which workspaces a group governs from the **Workspaces** list on the same **General** tab. A non-default group is created targeting at least one workspace, but you can later remove all of them — a group with no workspaces simply governs nothing until you add one back. External workspace members (people who have access to a workspace but belong to a different organization) can't be added as named members, but a workspace-scoped group with no members — and the organization default group — still governs them. @@ -166,47 +209,19 @@ This applies regardless of how the workflow is triggered — manually, via API, When a user opens Chat, their permission group is read before any block or tool suggestions are made: - Blocks not in the allowed list are filtered out of the block picker entirely — they do not appear as options. -- If Sim generates a workflow step that would use a disallowed tool (MCP, custom, or skills), that step is skipped and the reason is noted. - ---- - -## User membership rules - -- A user can belong to **multiple** permission groups, but **at most one** group governs them in any given workspace. -- For a given workspace, a non-default group the user is an **explicit member** of takes precedence over a non-default **all-members** group (one with no members) targeting that workspace, which takes precedence over the organization's **default group**. -- A workspace has **at most one all-members group**, and a user is an explicit member of **at most one** group per workspace. Adding a user, adding a workspace, or removing a group's last member is rejected when it would violate this — memberships and scopes are never silently moved. -- A workspace-scoped group with **no members** governs everyone in its workspaces (including external members); add members to narrow it to specific people. -- Users not covered by any workspace group fall under the organization's **default group** if one is set; otherwise no restrictions are applied to them. -- Only one group per organization can be the **default group**; it always applies to all workspaces, ignores members, and also governs external workspace members. -- Personal or grandfathered workspaces that do not belong to an organization have no permission groups. +- If Sim generates a workflow step that would use a disallowed tool (MCP, custom, or skills), the tool is left out of the generated workflow. A run that reaches a disallowed tool fails with an error naming the restriction. --- @@ -241,4 +252,4 @@ You can also set a server-level block allowlist using the `ALLOWED_INTEGRATIONS` ALLOWED_INTEGRATIONS=slack,gmail,agent,function,condition ``` -Once enabled, permission groups are managed through **Settings → Enterprise → Access Control** the same way as Sim Cloud. +Once enabled, permission groups are managed through **Settings → Organization → Permission groups** the same way as Sim Cloud. diff --git a/apps/docs/content/docs/platform/enterprise/audit-logs.mdx b/apps/docs/content/docs/platform/enterprise/audit-logs.mdx index 9be2eb67ad1..36ecdacfa4c 100644 --- a/apps/docs/content/docs/platform/enterprise/audit-logs.mdx +++ b/apps/docs/content/docs/platform/enterprise/audit-logs.mdx @@ -14,7 +14,7 @@ Audit logs give your organization a tamper-evident record of every significant a ### In the UI -Go to **Settings → Enterprise → Audit Logs** in your workspace. Logs are displayed in a table with the following columns: +Go to **Settings → Organization → Audit logs** in your workspace. Logs are displayed in a table with the following columns: Audit Logs settings showing a table of events with columns for Timestamp, Event, Description, and Actor, along with search and filter controls @@ -149,4 +149,13 @@ AUDIT_LOGS_ENABLED=true NEXT_PUBLIC_AUDIT_LOGS_ENABLED=true ``` -Once enabled, audit logs are viewable in **Settings → Enterprise → Audit Logs** and accessible via the API. +Once enabled, audit logs are viewable in **Settings → Organization → Audit logs** and accessible via the API. + +`GET /api/v1/audit-logs` authenticates with an `x-api-key` whose owner is an admin or owner of an organization, so it is unreachable on a deployment where nobody belongs to one yet. On Sim Cloud it additionally requires an active Enterprise subscription; self-hosted, `AUDIT_LOGS_ENABLED` takes that role. The admin-key equivalent needs neither an organization nor a plan: + +```http +GET /api/v1/admin/audit-logs +x-admin-key: +``` + +It accepts the same filters plus `actorEmail`, drops `includeDeparted`, and paginates with `limit` (max 250) and `offset` instead of the organization endpoint's `limit` (max 100) and `cursor`. It returns entries across the whole deployment rather than one organization. `GET /api/v1/admin/audit-logs/` returns a single entry. Set `ADMIN_API_KEY` to use it — see the [self-hosted enterprise guide](/platform/enterprise/self-hosted). diff --git a/apps/docs/content/docs/platform/enterprise/custom-blocks.mdx b/apps/docs/content/docs/platform/enterprise/custom-blocks.mdx index a528416612d..438541dd9ab 100644 --- a/apps/docs/content/docs/platform/enterprise/custom-blocks.mdx +++ b/apps/docs/content/docs/platform/enterprise/custom-blocks.mdx @@ -14,7 +14,7 @@ A custom block always runs the **latest deployed version** of its source workflo ## Common uses -Custom blocks turn a workflow one team owns into infrastructure the whole organization can safely reuse. Credentials and complexity stay with the block's author; everyone else gets a clean block that's always up to date. A few patterns: +The block's author keeps the credentials and the workflow logic; by default consumers see only the inputs and outputs, unless the author enables **Trace runs in consumer logs**. Common patterns: - **Internal API gateway.** Wrap an authenticated internal or partner endpoint — "Create Ticket", "Charge Account", "Provision User" — behind a block that takes only the business inputs. Teammates call it without the base URL, API key, or auth headers, and when the endpoint changes you update one workflow instead of every consumer's. - **Blessed knowledge lookup.** Package a vetted retrieval pipeline — chunking, filters, reranking — as "Search Company Docs" with a single query input, so teams reuse the approved retrieval instead of each rebuilding it. @@ -35,7 +35,7 @@ Custom blocks turn a workflow one team owns into infrastructure the whole organi ### 1. Open Custom blocks settings -Go to **Settings → Enterprise → Custom blocks** and click **Create block**. +Go to **Settings → Organization → Custom blocks** and click **Create block**. Custom blocks settings page listing a published block with its icon, name, and description, with a Create block button in the header @@ -90,19 +90,19 @@ Click **Save changes**. The block is published immediately and becomes available ## Using a custom block -In the workflow editor, open the block toolbar. Published custom blocks appear under a **Custom blocks** section. Drag one onto the canvas like any other block, fill in its inputs (using the placeholders as a guide), and reference its outputs in downstream blocks. +In the workflow editor, open the block toolbar. Published custom blocks appear under a **Custom blocks** section. Drag one into your workflow like any other block, fill in its inputs (using the placeholders as a guide), and reference its outputs in downstream blocks. Workflow editor block toolbar with a Custom Blocks section listing two published blocks below Core Blocks Consumers don't need any access to the source workflow. The block runs on its own, using only the inputs provided, and returns only the outputs you exposed. Its internal steps, models, and intermediate values stay hidden unless the block's publisher turned on **Trace runs in consumer logs**, in which case they appear under the block in the run's trace. -A custom block connected to a Start block on the workflow canvas, with its query input filled in and the run output showing the returned fields +A custom block connected to a Start block in the workflow editor, with its query input filled in and the run output showing the returned fields --- ## Managing blocks -Open a block from **Settings → Enterprise → Custom blocks** to edit or delete it. +Open a block from **Settings → Organization → Custom blocks** to edit or delete it. - **Editing** changes only the block's presentation, interface, and trace policy — name, description, icon, input placeholders, exposed outputs, and whether runs are traced in consumer logs. The source workflow can't be re-pointed. - **Changing what the block does** is done by editing and **redeploying the source workflow**. The block picks up the new deployment automatically; there's nothing to republish. @@ -131,7 +131,7 @@ Open a block from **Settings → Enterprise → Custom blocks** to edit or delet }, { question: "Can consumers see the workflow behind a block?", - answer: "No. A custom block only exposes the inputs it needs and the outputs you chose to share. The source workflow, its steps, and its intermediate values are never visible, and consumers don't need any access to it." + answer: "A custom block only exposes the inputs it needs and the outputs you chose to share, and consumers don't need any access to the source workflow. Its steps and intermediate values stay hidden unless you enable Trace runs in consumer logs, which surfaces them in the consumer's run trace." }, { question: "Can I change which workflow a block points to?", diff --git a/apps/docs/content/docs/platform/enterprise/data-drains.mdx b/apps/docs/content/docs/platform/enterprise/data-drains.mdx index 78c9b6c2686..d8805a9fc40 100644 --- a/apps/docs/content/docs/platform/enterprise/data-drains.mdx +++ b/apps/docs/content/docs/platform/enterprise/data-drains.mdx @@ -4,6 +4,7 @@ description: Continuously export workflow logs, audit logs, and Chat data to you --- import { FAQ } from '@/components/ui/faq' +import { Image } from '@/components/ui/image' Data Drains let organization owners and admins on Enterprise plans continuously export Sim data to a destination they control — a customer-owned S3 bucket, Google Cloud Storage bucket, Azure Blob container, BigQuery table, Snowflake table, Datadog logs intake, or an HTTPS webhook. A drain runs on a schedule, picks up only new rows since its last successful run, and writes them to the destination. Viewing drain configuration and run history is restricted to owners and admins as well, since destinations expose internal bucket names, table identifiers, and webhook URLs. @@ -13,11 +14,11 @@ Drains are independent of [Data Retention](/platform/enterprise/data-retention) ## Setup -Go to **Settings → Enterprise → Data Drains** in your workspace, then click **New drain**. +Go to **Settings → Organization → Data drains** in your workspace, then click **New drain**. -![Data Drains settings page showing two configured drains — one exporting workflow logs to Amazon S3 daily, another exporting Copilot chats to an HTTPS webhook hourly](/static/enterprise/data-drains-list.png) +Data Drains settings page showing two configured drains — one exporting workflow logs to Amazon S3 daily, another exporting Chat conversations to an HTTPS webhook hourly -![New data drain dialog with fields for name, source, cadence, destination, and S3 credentials](/static/enterprise/data-drains-new.png) +New data drain dialog with fields for name, source, cadence, destination, and S3 credentials Each drain has four pieces: @@ -186,7 +187,7 @@ The **last 10 runs** for each drain are visible by expanding its row in the sett ## Pairing with Data Retention -Drains and [Data Retention](/platform/enterprise/data-retention) are independent modules. Sim does **not** gate retention on drain progress — if a drain is failing, retention will still purge data on its own schedule. This matches the model used by Datadog Archives and AWS CloudWatch + S3 Export: keep the two configurations orthogonal and let the customer pair them deliberately. +Drains and [Data Retention](/platform/enterprise/data-retention) are independent modules. Sim does **not** gate retention on drain progress — if a drain is failing, retention will still purge data on its own schedule. Keep the two configurations independent and pair them deliberately. To safely use both together, set the drain cadence shorter than the retention period for the same data category: @@ -207,10 +208,6 @@ After data lands in your bucket or webhook system, archive lifecycle (transition question: "Who can configure data drains?", answer: "Only organization owners and admins can view, create, edit, run, or delete drains. On Sim Cloud, the organization must be on an Enterprise plan." }, - { - question: "Will drained data be duplicated if a run fails?", - answer: "The drain cursor only advances on overall success, so a failure replays the same chunks on the next run. Every row has a stable `id` field and every webhook chunk has an `Idempotency-Key` header so receivers can dedupe." - }, { question: "Can I export multiple sources to the same destination?", answer: "Yes — create one drain per source, all pointing at the same bucket or endpoint. S3 destinations namespace by source automatically; webhook receivers can branch on the `X-Sim-Source` header." @@ -240,6 +237,29 @@ DATA_DRAINS_ENABLED=true NEXT_PUBLIC_DATA_DRAINS_ENABLED=true ``` -`NEXT_PUBLIC_DATA_DRAINS_ENABLED` shows the **Settings → Enterprise → Data Drains** page in the UI. `DATA_DRAINS_ENABLED` gates the server-side mutating endpoints and the cron dispatcher — when unset on a self-hosted deployment, drain create/update/delete/run requests return `404` and the dispatcher is a no-op. Both should be set to `true` together. +`NEXT_PUBLIC_DATA_DRAINS_ENABLED` shows the **Settings → Organization → Data drains** page in the UI. `DATA_DRAINS_ENABLED` gates the server-side mutating endpoints and the cron dispatcher — when unset on a self-hosted deployment, drain create/update/delete/run requests return `404` and the dispatcher is a no-op. Both should be set to `true` together. + +### Scheduling the dispatcher + +The dispatcher is an HTTP endpoint, not a self-scheduling job — something has to call it: + +``` +GET /api/cron/run-data-drains +``` + +It authenticates with a bearer token equal to `CRON_SECRET` and returns `401` when that variable is unset, so a self-hosted deployment must set it: + +```bash +openssl rand -hex 32 +``` + +Set the same value as `CRON_SECRET` on both the app and whatever invokes the endpoint. Generating it in your shell does not configure either one. + +Both shipped deployments schedule this endpoint hourly for you — Helm through `cronjobs.jobs.runDataDrains`, Docker Compose through its `cron` service. A deployment that runs neither schedules it itself: + +```bash +curl -H "Authorization: Bearer $CRON_SECRET" \ + https://sim.example.com/api/cron/run-data-drains +``` -Data Drains otherwise rely on the standard Trigger.dev background job infrastructure used elsewhere in Sim — no additional setup is required. The cron dispatcher runs hourly and fans out due drains as background jobs. +Each due drain is then fanned out as a `run-data-drain` background job, so the deployment also needs `TRIGGER_DEV_ENABLED` and a configured Trigger.dev project. Without it the dispatcher still claims the work and enqueues it to the database, but nothing drains that queue for this job type, so the runs stay pending and never execute. See [background jobs](/platform/self-hosting/background-jobs). diff --git a/apps/docs/content/docs/platform/enterprise/data-retention.mdx b/apps/docs/content/docs/platform/enterprise/data-retention.mdx index 819ab84ee01..46622fefa2a 100644 --- a/apps/docs/content/docs/platform/enterprise/data-retention.mdx +++ b/apps/docs/content/docs/platform/enterprise/data-retention.mdx @@ -18,7 +18,7 @@ Both are configured once at the **organization level** and apply to every worksp ## Setup -Go to **Settings → Enterprise → Data Retention** in your workspace. +Go to **Settings → Organization → Data retention** in your workspace. Data Retention settings showing the Retention policies list with the Organization default row and its summary of retention periods and PII stages @@ -97,12 +97,12 @@ The **Workflow input** and **Block outputs** stages alter what the workflow comp For each stage, choose the **entity types** to redact from the searchable grid. They are grouped as: -- **Common** — person name, email, phone, credit card, IP address, URL, IBAN, crypto wallet, medical license, VIN +- **Common** — person name, email, phone, credit card, IP address, location, date or time, URL, IBAN, crypto wallet, nationality/religious/political group, medical license, VIN - **United States** — SSN, passport, driver's license, bank account, ITIN - **United Kingdom** — NHS number, National Insurance number -- **Other regions** — Singapore, Australian, and Indian identifiers +- **Other regions** — Spanish (NIF, NIE), Italian (fiscal code, driver's licence, VAT code, passport, identity card), Polish (PESEL), Singaporean (NRIC/FIN, UEN), Australian (ABN, ACN, TFN, Medicare), Indian (PAN, Aadhaar, vehicle registration, voter ID, passport), and Finnish (personal identity code) identifiers -The **Block outputs** stage is restricted to regex- and checksum-based recognizers, so it can run in-flight over large payloads without a performance penalty. Types that need name-model detection — person name, location, date or time — are not offered for that stage. +The **Block outputs** stage is restricted to regex- and checksum-based recognizers, so it can run in-flight over large payloads without a performance penalty. Types that need name-model detection — person name, location, date or time, and nationality/religious/political group — are not offered for that stage. Detection is language-aware: pick the **language** whose recognizers should apply. English, Spanish, Italian, Polish, and Finnish are supported, and the grid filters to the identifiers available for the selected language. @@ -182,7 +182,30 @@ NEXT_PUBLIC_DATA_RETENTION_ENABLED=true DATA_RETENTION_ENABLED=true ``` -Once enabled, retention settings are configurable through **Settings → Enterprise → Data Retention** the same way as Sim Cloud. +Once enabled, retention settings are configurable through **Settings → Organization → Data retention** the same way as Sim Cloud. + +### Scheduling the deletion pass + +`DATA_RETENTION_ENABLED` permits deletion; it does not perform it. Deletion runs when a scheduled request reaches one of three endpoints, each authenticated with a bearer token equal to `CRON_SECRET`: + +| Category | Endpoint | +|----------|----------| +| Execution and job logs | `GET /api/logs/cleanup` | +| Soft-deleted resources | `GET /api/cron/cleanup-soft-deletes` | +| Chats and Chat runs | `GET /api/cron/cleanup-tasks` | + + +Neither shipped deployment schedules these three endpoints — not the Helm chart, not Docker Compose's `cron` service. An operator who sets `DATA_RETENTION_ENABLED=true` alone still deletes nothing. Add them to `cronjobs.jobs`, or call them daily from an external scheduler. + + +```bash +# Use the value already configured as CRON_SECRET on the app — a token +# generated here and not installed there returns 401. +curl -H "Authorization: Bearer $CRON_SECRET" \ + https://sim.example.com/api/logs/cleanup +``` + +Each call fans the work out as background jobs. Trigger.dev is not required: when it is not configured the dispatcher runs the cleanup chunks inline instead of enqueuing them, so deletion completes on a default self-host. ### PII redaction @@ -190,7 +213,10 @@ PII redaction runs against a standalone [Presidio](https://microsoft.github.io/p ```bash # The Presidio service exposing /analyze and /anonymize -PII_URL=http://localhost:5001 +# Helm — substitute your release name and namespace +PII_URL=http://-pii..svc.cluster.local:5001 +# Docker Compose — the PII service name on your network +# PII_URL=http://pii:5001 ``` -All PII stages are configurable under **Settings → Enterprise → Data Retention**. +All PII stages are configurable under **Settings → Organization → Data retention**. diff --git a/apps/docs/content/docs/platform/enterprise/forks.mdx b/apps/docs/content/docs/platform/enterprise/forks.mdx index 4301284fd9a..a38b28d5881 100644 --- a/apps/docs/content/docs/platform/enterprise/forks.mdx +++ b/apps/docs/content/docs/platform/enterprise/forks.mdx @@ -29,7 +29,7 @@ On Sim Cloud, your organization may also need the feature turned on for your acc ### 1. Open Forks -Go to **Settings → Enterprise → Workspace Forks** in the workspace you want to fork from (or manage). +Go to **Settings → Organization → Workspace forks** in the workspace you want to fork from (or manage). Workspace Forks settings page showing Parent and Forks sections with Docs, See activity, and Create fork actions @@ -62,7 +62,7 @@ Click **Fork**. The child workspace is created immediately. Deployed workflows l ### 3. Open the parent edge (from the child) -Open the **child** workspace → **Settings → Enterprise → Workspace Forks**. On the **Parent** row, open the menu and choose **Edit mappings**. +Open the **child** workspace → **Settings → Organization → Workspace forks**. On the **Parent** row, open the menu and choose **Edit mappings**. Child rows (when you are on the parent) only offer **Open workspace** and **Disconnect** — mapping and sync are owned by the child configuring how it relates to its parent. @@ -361,7 +361,7 @@ Schedules, webhooks, and triggers are not live in the child until you **deploy** ## Edge cases to keep in mind -- **Force overwrite** — Sync does not merge canvas changes. Anything only on the target that conflicts with the source’s deployed workflows can be lost. Use the confirm dialog’s archive list carefully. +- **Force overwrite** — Sync does not merge editor changes. Anything only on the target that conflicts with the source’s deployed workflows can be lost. Use the confirm dialog’s archive list carefully. - **Deselect on fork** — Cleared references are intentional. Prefer copying the resource, or plan to reconnect it in the child. - **Background copy** — Right after fork, large tables / knowledge bases / files may still be copying. Check **Activity** if something looks empty. - **OAuth MCP** — Expect a reconnect in the child (and after a copied server on sync). @@ -373,13 +373,8 @@ Schedules, webhooks, and triggers are not live in the child until you **deploy** --- @@ -392,4 +387,4 @@ Self-hosted deployments turn Forks on with an environment variable instead of th |----------|-------------| | `FORKING_ENABLED`, `NEXT_PUBLIC_FORKING_ENABLED` | Enables workspace forking when billing is not used as the entitlement gate | -Once enabled, use the same **Settings → Enterprise → Workspace Forks** UI as Sim Cloud. Only workspace admins can manage forks. +Once enabled, use the same **Settings → Organization → Workspace forks** UI as Sim Cloud. Only workspace admins can manage forks. diff --git a/apps/docs/content/docs/platform/enterprise/index.mdx b/apps/docs/content/docs/platform/enterprise/index.mdx index fa3f62af3b1..c8691167ca4 100644 --- a/apps/docs/content/docs/platform/enterprise/index.mdx +++ b/apps/docs/content/docs/platform/enterprise/index.mdx @@ -3,31 +3,29 @@ title: Enterprise description: Enterprise features for business organizations --- -import { FAQ } from '@/components/ui/faq' - -Sim Enterprise adds fine-grained access control, SSO, audit logging, compliance features, and workspace forking on top of Team plans. +Sim Enterprise adds permission groups, SSO, audit logs, usage tracking, data retention and drains, white-labeling, and workspace forks on top of Team plans. --- -## Access Control +## Permission groups -Define permission groups on a workspace to control what features and integrations its members can use. Permission groups are scoped to a single workspace — a user can belong to different groups (or no group) in different workspaces. +Define permission groups to control what features, models, blocks, and integrations your members can use. A permission group belongs to an **organization**. The organization's single **default group** governs everyone org-wide; every other group targets a specific set of workspaces and, by default, governs all members of those workspaces — or only named members once you add them. A user is governed by exactly one group in any given workspace. -External workspace members can be assigned to permission groups just like internal organization members, but they remain outside the organization roster and do not consume seats. +External workspace members can be governed by permission groups just like internal organization members, but they remain outside the organization roster and do not consume seats. -### Features +### What a group controls -- **Allowed Model Providers** - Restrict which AI providers users can access (OpenAI, Anthropic, Google, etc.) -- **Allowed Blocks** - Control which workflow blocks are available -- **Platform Settings** - Hide Knowledge Base, disable MCP tools, disable custom tools, or disable invitations +- **Model providers** - Restrict which AI providers members can use, and deny individual models within an allowed provider +- **Blocks** - Control which workflow blocks are available, and deny individual tools within an allowed integration +- **Platform** - Revoke modules (Chat, Knowledge Base, Tables, Files, Sim Mailer), deployment surfaces, tool types, log detail, collaboration actions, and credential access ### Setup -1. Navigate to **Settings** → **Access Control** in the workspace you want to manage +1. Navigate to **Settings → Organization → Permission groups** from any workspace in your organization 2. Create a permission group with your desired restrictions -3. Add workspace members to the permission group +3. Scope it to workspaces, and optionally add named members -Any workspace admin on an Enterprise-entitled workspace can manage permission groups. Users not assigned to any group have full access. Restrictions are enforced at both UI and execution time, based on the workflow's workspace. +Only organization owners and admins can manage permission groups. Users not governed by any group have full access. Restrictions are enforced at both UI and execution time, based on the organization that owns the workflow's workspace. See the [Access Control guide](/platform/enterprise/access-control) for full details. @@ -41,9 +39,9 @@ See the [SSO setup guide](/platform/enterprise/sso) for step-by-step instruction --- -## Whitelabeling +## White-labeling -Replace Sim's default branding — logos, product name, and favicons — with your own. See the [whitelabeling guide](/platform/enterprise/whitelabeling). +Replace Sim's default branding — logos, wordmark, product name, and theme colors — with your own. Instance-wide branding environment variables additionally cover the favicon and custom CSS. See the [white-labeling guide](/platform/enterprise/whitelabeling). --- @@ -67,7 +65,7 @@ Configure how long execution logs, soft-deleted resources, and Chat data are kep ## Data Drains -Continuously export workflow logs, audit logs, and Chat data to a customer-owned S3 bucket or HTTPS webhook on a schedule. See the [data drains guide](/platform/enterprise/data-drains). +Continuously export workflow logs, audit logs, and Chat data to a destination you control — object storage, a data warehouse, Datadog, or an HTTPS webhook — on a schedule. See the [data drains guide](/platform/enterprise/data-drains). --- @@ -77,14 +75,6 @@ Clone a workspace into a linked child, then push or pull **deployed** workflow c --- - - ---- - ## Self-hosted setup Self-hosted deployments unlock enterprise features through environment configuration instead of billing. One switch turns on the whole set: diff --git a/apps/docs/content/docs/platform/enterprise/self-hosted.mdx b/apps/docs/content/docs/platform/enterprise/self-hosted.mdx index b059b7df99d..3eafb8886f2 100644 --- a/apps/docs/content/docs/platform/enterprise/self-hosted.mdx +++ b/apps/docs/content/docs/platform/enterprise/self-hosted.mdx @@ -12,7 +12,7 @@ On Sim Cloud, enterprise features are unlocked by an Enterprise subscription. Se There are two parts to getting this right, and skipping the second is the most common reason features appear to do nothing: 1. **Enable the features** with `ENTERPRISE_ENABLED`. -2. **Give them an organization to apply to.** Whitelabeling, PII redaction, permission groups, custom blocks, data drains, and audit scoping all read their settings from the organization that owns a workspace. A deployment where everyone works in personal workspaces has no organization for those settings to come from. +2. **Give them an organization to apply to.** White-labeling, PII redaction, permission groups, custom blocks, data drains, and audit scoping all read their settings from the organization that owns a workspace. A deployment where everyone works in personal workspaces has no organization for those settings to come from. ## Enable the feature set @@ -23,10 +23,9 @@ ENTERPRISE_ENABLED=true NEXT_PUBLIC_ENTERPRISE_ENABLED=true ``` -That turns on organizations, permission groups, SSO, whitelabeling, audit logs, -custom blocks, session policies, data retention, data drains, workspace forks, the Sandbox -entitlement, and the inbox. Sandboxes remain unavailable until their remote -provider and dedicated Function base are configured. +That turns on organizations, permission groups, SSO, white-labeling, audit logs, +usage tracking, custom blocks, session policies, data retention, data drains, workspace +forks, the Sandbox entitlement, and the inbox. ### Turning one feature off @@ -49,130 +48,52 @@ The individual flags also work on their own if you would rather opt in one at a | SAML and OIDC sign-in | `SSO_ENABLED` | `NEXT_PUBLIC_SSO_ENABLED` | | Custom branding | `WHITELABELING_ENABLED` | `NEXT_PUBLIC_WHITELABELING_ENABLED` | | Audit logs | `AUDIT_LOGS_ENABLED` | `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` | +| Usage tracking | `USAGE_MONITORING_ENABLED` | `NEXT_PUBLIC_USAGE_MONITORING_ENABLED` | | Custom blocks | `CUSTOM_BLOCKS_ENABLED` | `NEXT_PUBLIC_CUSTOM_BLOCKS_ENABLED` | | Session policies | `SESSION_POLICIES_ENABLED` | `NEXT_PUBLIC_SESSION_POLICIES_ENABLED` | | Data retention deletion | `DATA_RETENTION_ENABLED` | `NEXT_PUBLIC_DATA_RETENTION_ENABLED` | | Data drains | `DATA_DRAINS_ENABLED` | `NEXT_PUBLIC_DATA_DRAINS_ENABLED` | -| Workspace forks | `FORKING_ENABLED` | — | +| Workspace forks | `FORKING_ENABLED` | `NEXT_PUBLIC_FORKING_ENABLED` | | Sim Mailer inbox | `INBOX_ENABLED` | `NEXT_PUBLIC_INBOX_ENABLED` | | Sandboxes | `SANDBOXES_ENABLED` | `NEXT_PUBLIC_SANDBOXES_ENABLED` | -Sandboxes also need a remote execution provider and a dedicated Function base -image. Build and configure that base before enabling the UI; custom workspace -sandboxes layer their packages on top of it. +Sandboxes also need a remote execution provider and a dedicated Function base image before they can run anything. `SANDBOXES_ENABLED` grants the server-side entitlement; `NEXT_PUBLIC_SANDBOXES_ENABLED` projects provider readiness to the browser and exposes Shell plus custom Sandbox management. Set the public flag only after the selected provider has credentials and a valid immutable Function base configured. -For E2B: +JavaScript without `import` or `require` does not use the remote provider and continues to run in the local isolated VM when all Sandbox flags are off. Python, Shell, JavaScript with external imports, and selected custom Sandboxes fail with an explicit configuration error until the remote Function base is ready. -```bash -E2B_API_KEY=... \ - bun run apps/sim/scripts/build-function-e2b-template.ts \ - --name sim-function - -SANDBOX_PROVIDER=e2b -E2B_ENABLED=true -E2B_API_KEY=... -E2B_FUNCTION_TEMPLATE_ID=: -E2B_FUNCTION_TEMPLATE_GENERATION= -SANDBOXES_ENABLED=true -NEXT_PUBLIC_SANDBOXES_ENABLED=true -``` - -The builder uses E2B's maintained `code-interpreter-v1` base, assigns a fresh -release generation, and prints both runtime values. `--generation` remains -available for release automation, and `--base-template` accepts an immutable -base override when a deployment deliberately owns one. - -For Daytona, use the immutable snapshot ID printed by the builder. The API key needs -`write:snapshots` to build and `write:sandboxes` to execute: +See [Sandboxes](/platform/self-hosting/sandboxes) for the provider credentials, the Function base-image build, and the promotion procedure. -```bash -DAYTONA_API_KEY=... \ - bun run apps/sim/scripts/build-function-daytona-snapshot.ts \ - --name sim-function-2026-08-03 \ - --parity-manifest /tmp/function-sandbox-manifest.json - -SANDBOX_PROVIDER=daytona -DAYTONA_API_KEY=... -DAYTONA_FUNCTION_SNAPSHOT_ID= -SANDBOXES_ENABLED=true -NEXT_PUBLIC_SANDBOXES_ENABLED=true -``` - -`SANDBOXES_ENABLED` grants the server-side self-hosted entitlement. -`NEXT_PUBLIC_SANDBOXES_ENABLED` projects provider readiness to the browser and -exposes Shell plus custom Sandbox management. Set the public flag only after the -selected provider has credentials and a valid immutable Function base configured. -The Function language value itself is never conditioned on these flags, so a -saved Python block cannot be silently serialized or executed as JavaScript. + + Data retention is the one feature that deletes data. Its flag controls the cleanup pass, not the settings screen — retention windows are always configurable. Nothing is ever deleted until you enable it, and even then only against windows you configured explicitly. Sim never applies the hosted plan defaults to a self-hosted deployment. + -JavaScript without `import` or `require` does not use this remote provider and -continues to run in the local isolated VM when all Sandbox flags are off. Python, -Shell, JavaScript with external imports, and selected custom Sandboxes fail with -an explicit configuration error until the remote Function base is ready. +## Schedule the background jobs -Mothership's `function_execute` and `run_code` tools use Mothership's separate -shell image, including for JavaScript without imports. If the deployment uses -Mothership code tools, also configure the image produced by the Mothership -release process for the selected provider: +Two enterprise features are started by a cron-driven HTTP endpoint rather than by the app on its own schedule. What happens next differs: retention's cleanup runs inline in the app process, while each due data drain is handed to a background job. All four endpoints authenticate with a bearer token equal to `CRON_SECRET`, and all four return `401` when it is unset: ```bash -# E2B -MOTHERSHIP_E2B_TEMPLATE_ID= - -# Daytona -DAYTONA_SHELL_SNAPSHOT_ID= +openssl rand -hex 32 ``` -These values are selected only for workflow Copilot and workspace Mothership -code-tool calls. They never replace or act as a fallback for -`E2B_FUNCTION_TEMPLATE_ID` or -`DAYTONA_FUNCTION_SNAPSHOT_ID`; Function blocks and custom workspace sandboxes -continue to use the dedicated Function base. +Persist that value as `CRON_SECRET` on the app **and** on whatever calls these endpoints. Generating it in a shell configures neither, and a mismatch returns `401`, so the work silently never runs. -Use E2B as the release baseline before building or promoting Daytona: +| Feature | Endpoint | Suggested schedule | Scheduled for you | +|---------|----------|--------------------|-------------------| +| Data drains | `GET /api/cron/run-data-drains` | Hourly | Yes — Helm and Docker Compose both call it | +| Retention — logs | `GET /api/logs/cleanup` | Daily | **No** — schedule it yourself | +| Retention — soft deletes | `GET /api/cron/cleanup-soft-deletes` | Daily | **No** — schedule it yourself | +| Retention — Chat tasks | `GET /api/cron/cleanup-tasks` | Daily | **No** — schedule it yourself | -```bash -# 1. Verify the exact E2B Function build and capture its accepted package/runtime surface. -E2B_ENABLED=true \ -E2B_API_KEY=... \ -E2B_FUNCTION_TEMPLATE_ID=: \ -E2B_FUNCTION_TEMPLATE_GENERATION= \ -SANDBOX_PARITY_MANIFEST_OUT=/tmp/function-sandbox-manifest.json \ - bun run apps/sim/scripts/verify-sandbox-parity.ts - -# 2. Pin Daytona's reconstructed packages to that accepted E2B manifest. -DAYTONA_API_KEY=... \ - bun run apps/sim/scripts/build-function-daytona-snapshot.ts \ - --name sim-function-2026-08-03 \ - --parity-manifest /tmp/function-sandbox-manifest.json - -# 3. Verify the immutable Daytona snapshot against the same baseline before promotion. -SANDBOX_PROVIDER=daytona \ -DAYTONA_API_KEY=... \ -DAYTONA_FUNCTION_SNAPSHOT_ID= \ -SANDBOX_PARITY_MANIFEST_BASELINE=/tmp/function-sandbox-manifest.json \ - bun run apps/sim/scripts/verify-sandbox-parity.ts -``` - - - `E2B_FUNCTION_TEMPLATE_ID` and `DAYTONA_FUNCTION_SNAPSHOT_ID` fail closed when - unset or mutable. The E2B value must be an exact `