diff --git a/.agents/skills/add-integration/SKILL.md b/.agents/skills/add-integration/SKILL.md index 58b1f32f800..5b82f1070de 100644 --- a/.agents/skills/add-integration/SKILL.md +++ b/.agents/skills/add-integration/SKILL.md @@ -1,6 +1,6 @@ --- name: add-integration -description: Add a complete Sim integration from API docs, covering tools, block, icon, optional triggers, registrations, and integration conventions. Use when introducing a new service under `apps/sim/tools`, `apps/sim/blocks`, and `apps/sim/triggers`. +description: Add a complete Sim integration from API docs, covering tools, block, icon, optional triggers, registrations, resolved-secret/model-input safety, and integration conventions. Use when introducing a new service under `apps/sim/tools`, `apps/sim/blocks`, and `apps/sim/triggers`. argument-hint: [api-docs-url] --- @@ -122,6 +122,64 @@ export const {service}{Action}Tool: ToolConfig = { - When using `type: 'json'` and you know the object shape, define `properties` with the inner fields so downstream consumers know the structure. Only use bare `type: 'json'` when the shape is truly dynamic - If you do not know the response JSON shape from docs or verified examples, you MUST tell the user and stop. Never guess outputs or response mappings. +### Resolved Secrets at Model and Persistence Boundaries + +Classify every request field before implementing the tool: + +This is opt-in, not a blanket integration migration. Add a model-input declaration only when the +service's official documentation or an unambiguous local execution path proves that the exact +field is consumed by an AI model. If that cannot be established, preserve existing tool behavior +and leave the field unannotated. + +- **Ordinary provider/API input:** leave it unchanged. Do not add blanket result sanitization. +- **Text or structured content consumed by an AI model:** declare `request.modelInput` with + `mode: 'project'` and select only the exact model-visible fields. The shared executor replaces + activated Sim secrets with canonical `{{NAME}}` labels before request formatting. For nested or + JSON-string fields, use a small shared selector plus `applyProjected`; verify that selecting the + rebuilt params reproduces the projected selection. +- **Opaque model input sent directly to an external provider** such as a model-read URL or image + payload: declare `request.opaqueModelInput` with `mode: 'reject-resolved-secrets'` and select only + the exact effective value. The shared `executeTool` preflight rejects incomplete or secret-bearing + committed provenance before URL/body formatting or network I/O, preserves safe request bytes, + and sends no provenance metadata to the provider. +- **Opaque model input owned by an authenticated internal route** such as uploaded audio, image, + video, file bytes, or signed URLs: add `privateProvenance` to a projected request, or use + `mode: 'private-provenance'` when there is no textual projection. The route must call + `validateOpaqueModelInputProvenance` before downloading or sending content to the model and must + apply the workspace-file provenance guard before reading a persisted workspace file. +- **Sim-owned durable storage or internal execution handoff** that can later enter a workflow/model + (table cells, Agent memory, knowledge documents/chunks, workspace-file contents, or child-workflow + input): transport encrypted field-scoped provenance with `request.secretProvenance`. The + authenticated receiver validates the exact selection and scope, strips the private envelope, and + persists, imports, or propagates it at the owning boundary. Preserve shared legacy behavior for + headerless internal calls and rows/files whose provenance marker is `NULL`; never invent a + tool-local migration rule. + +Hard rules: + +- Never substitute secret plaintext into source or serialize plaintext provenance. +- Never hand-roll private provenance headers/envelopes; the shared `executeTool` boundary owns + transport and strips private metadata from functional results. +- Never attach private provenance to an external URL or to `directExecution`. Use the centralized + `opaqueModelInput` rejection mode for external/direct opaque model inputs, or an authenticated + internal route when encrypted provenance must cross the boundary. +- Never sanitize arbitrary third-party tool results. Projection applies only to secrets activated + by Sim's resolved-secret provenance for that execution/tool call. +- Do not add provenance merely because a value is persisted, returned by a tool, or appears in a + filename. Require a concrete Sim `{{...}}` resolution path and a later model/log boundary. If an + unsupported field can resolve a secret but does not justify durable tracking (for example a + `file_write` path), reject it at that exact ingress. +- At diagnostic boundaries, project only values carrying execution-scoped provenance. Ordinary + provider responses, filenames, URLs, and errors remain unchanged when Sim did not resolve a + secret into them. + +Add focused tests covering named projection, ordinary identical text without provenance, nested +shape preservation, malformed/incomplete private metadata failing closed, centralized external +opaque rejection before formatting/I/O without byte changes or metadata transport, headerless +legacy requests, and absence of private metadata in the public tool result. For durable sinks, also +cover legacy `NULL` markers, exact-empty new writes, tracked secret writes, stale/missing sidecars, +and scope isolation. + ## Step 3: Create Block ### File Location @@ -535,6 +593,11 @@ If creating V2 versions (API-aligned outputs): - [ ] Created `index.ts` barrel export - [ ] Registered all tools in `tools/registry.ts` - [ ] Ran `bun run tool-metadata:generate` and committed the regenerated artifacts +- [ ] Classified every model-visible, opaque, Sim-durable, and internal-execution request field +- [ ] Added shared model-input projection, centralized opaque rejection, or private provenance only + where required +- [ ] Confirmed ordinary third-party tool results are not generically sanitized +- [ ] Added provenance compatibility and fail-closed boundary tests where applicable ### Block - [ ] Created `blocks/blocks/{service}.ts` diff --git a/.agents/skills/add-managed-cli/SKILL.md b/.agents/skills/add-managed-cli/SKILL.md new file mode 100644 index 00000000000..906d99e5257 --- /dev/null +++ b/.agents/skills/add-managed-cli/SKILL.md @@ -0,0 +1,149 @@ +--- +name: add-managed-cli +description: Add or upgrade a curated, immutable managed CLI for Sim Function sandboxes, including client-safe catalog metadata, a pinned server-only installation recipe, checksum and executable verification, provider compatibility, PATH propagation, content-addressed image identity, and tests. Use when adding a CLI to the Sandbox managed-CLI selector or changing an existing managed CLI version or recipe. +--- + +# Add a Managed CLI + +Add CLIs through the curated registry. Never turn this surface into arbitrary commands or package names: system packages already cover validated Debian/APT coordinates, while managed CLIs require immutable artifacts and reproducible recipes. + +## Read First + +Read these live sources before editing; do not copy their current entries into this skill: + +1. `apps/sim/lib/execution/remote-sandbox/cli-tools.ts` — persisted IDs and client-safe metadata. +2. `apps/sim/lib/execution/remote-sandbox/cli-tools.server.ts` — server-only recipes and recipe helpers. +3. `apps/sim/lib/execution/remote-sandbox/cli-tools.test.ts` — catalog and supply-chain invariants. +4. `apps/sim/lib/execution/remote-sandbox/cli-tools-boundary.test.ts` — client/server import boundary. +5. `apps/sim/lib/execution/remote-sandbox/sandbox-spec.ts` — content-addressed hash inputs. + +Read `resolve.ts` and `e2b.ts` only when changing provisioning mechanics. A normal catalog addition should not require UI, API, database, resolver, or provider edits; those paths derive from the registries. + +Do not modify the dedicated Function base image or the separate Mothership Shell template for a normal managed CLI addition. Managed recipes layer on the Function base image. Do not change `MAX_SANDBOX_CLI_TOOLS` from ten unless the user separately requests a product-limit change. + +## 1. Verify the Upstream Release + +Use primary upstream release documentation and official artifacts. Establish all of the following before writing code: + +- Exact version and stable Linux x86-64 artifact URL. Never use `latest`, mutable redirects, or an unversioned installer. +- SHA-256 for the exact artifact. Prefer a publisher-signed checksum; otherwise download the official artifact and compute it independently. +- Archive layout and the exact executable paths to install. +- Noninteractive, credential-free verification commands for every advertised executable, normally version commands. +- Required PATH entries under `/opt/sim-cli`. +- E2B and Daytona compatibility. Default to both only when the same Linux recipe works on both. + +When the user does not name a version, select the current stable upstream release from primary sources and state the exact version chosen. Do not silently choose a prerelease or infer a version from an unverified secondary source. + +Reject curl-to-shell installers, `npm install`, `pip install`, distro package repositories, arbitrary user commands, and artifacts from unofficial mirrors. Never place credentials, tokens, login commands, or account configuration in an image recipe or build log; authentication is runtime-only. + +## 2. Choose an Immutable ID + +Use `@-r`. + +- New upstream version: append a new ID ending in `-r1`. +- Recipe-only change for the same upstream version: append `-r2`, `-r3`, and so on. +- Never mutate or delete an existing ID or recipe. Persisted sandboxes must continue resolving to the bytes and behavior they selected. +- On upgrade, retain the old ID and recipe and set its metadata to `selectable: false`. Only the newest version keeps the public label selectable. + +Before shipping the first upgrade for a tool family, verify that editing a sandbox cannot leave both the retired and replacement IDs selected. If the generic selector and API validation do not already replace or reject colliding versions, address that once at the generic registry boundary with focused UI and contract tests; never special-case the individual CLI or silently install two versions that expose the same executable. + +Recipe identity includes the ID, revision, and SHA-256 in the sandbox image hash. Keeping old entries is what makes that identity reproducible rather than merely cache-busting. + +## 3. Add Client-Safe Metadata + +In `cli-tools.ts`: + +1. Append the ID to `SANDBOX_CLI_TOOL_IDS` in the same order used by the metadata and recipe registries. +2. Add a `SANDBOX_CLI_TOOLS` entry whose key and `id` exactly match. +3. Provide a unique selectable `label`, concise `description`, existing `category`, and useful executable/vendor aliases in `searchTerms`. +4. Add a category only when no existing category is accurate, then ensure it has at least one selectable entry. + +Keep this file safe for client bundles. It must not contain artifact URLs, checksums, install commands, verification commands, PATH recipes, provider SDKs, or imports from `cli-tools.server.ts`. + +The API enum and searchable grouped selector derive from this registry. Do not add parallel option arrays or route-local wire types. + +## 4. Add the Server-Only Recipe + +In `cli-tools.server.ts`, use the narrowest existing helper: + +- `defineBinaryRecipe` for one downloaded binary. +- `defineTarGzipRecipe` or `defineZipRecipe` for archives containing binaries. +- `defineVerifiedRecipe` for a vendor archive or installer layout that needs explicit commands. +- A direct typed entry only when the helpers cannot faithfully model the release. + +Provide every field the recipe contract requires: + +- Exact `version`, `artifactUrl`, `artifactName`, and lowercase 64-character `sha256`. +- Every installed `executable` and a corresponding `verificationCommands` entry. +- Deterministic extraction/install commands into `/opt/sim-cli`; quote fixed paths and clean temporary artifacts. +- `pathEntries` when the executable is not installed into the helper's default `bin` directory. +- `supportedProviders` only when it differs from the E2B-and-Daytona default. +- `revision` when it differs from `1`; it must agree with the ID suffix. + +Verification must prove the command is discoverable through `sandboxCliEnvironment`, not authenticate or contact a user account. Recipe commands run as root during both prebuilt image creation and runtime provisioning. + +If the artifact host is new, add only the exact official hostname to the `officialHosts` allowlist in `cli-tools.test.ts`. Treat that as a supply-chain review, not a way to silence the test. + +## 5. Preserve Generic Behavior + +Confirm the existing generic paths remain sufficient: + +- `sandboxCliToolRecipes` canonicalizes and resolves the recipe. +- `sandboxCliEnvironment` propagates PATH to Python subprocesses, JavaScript subprocesses, and Shell. +- E2B bakes the recipe into the custom image; runtime-strategy providers install it within the Function timeout. +- CLI-only sandboxes remain buildable even with no language packages. +- `hashSandboxSpec` includes recipe ID, revision, and checksum while preserving the legacy hash for an empty CLI list. +- The settings selector derives groups and search aliases from client-safe metadata. + +Do not special-case a CLI in those layers unless the registry contract cannot express a genuine provider requirement. Extend the registry contract generically when multiple CLIs need the same new behavior. + +## 6. Test the Addition + +Extend tests when the new entry introduces behavior not already covered: + +- For every upgrade, add a regression proving the old ID and recipe remain resolvable but non-selectable, while the replacement ID is selectable. +- Add important executable aliases to the table-driven search assertion. +- Add a focused assertion for a multi-executable recipe, custom PATH, or restricted provider. +- Add an opt-in credentialed smoke test only when installation plus a real minimal command cannot be validated without authentication. Read credentials from test-only environment variables, skip by default, create them only at runtime, and always tear down the sandbox. + +Never commit downloaded artifacts or credentials. + +## Required Validation + +From `apps/sim`: + +```bash +bunx vitest run \ + lib/execution/remote-sandbox/cli-tools.test.ts \ + lib/execution/remote-sandbox/cli-tools-boundary.test.ts \ + lib/execution/remote-sandbox/sandbox-spec.test.ts \ + lib/execution/remote-sandbox/resolve.test.ts \ + lib/api/contracts/sandboxes.test.ts \ + 'app/workspace/[workspaceId]/settings/components/sandboxes/utils.test.ts' \ + 'app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.test.tsx' +``` + +From the repository root: + +```bash +bun run type-check +bun run check:api-validation +bunx biome check \ + apps/sim/lib/execution/remote-sandbox/cli-tools.ts \ + apps/sim/lib/execution/remote-sandbox/cli-tools.server.ts \ + apps/sim/lib/execution/remote-sandbox/cli-tools.test.ts +git diff --check +``` + +For a new recipe, also exercise its install and every verification command in an actual E2B or Daytona sandbox when credentials and network access are available. Report clearly when only registry/unit validation ran. + +## Completion Checklist + +- [ ] Official immutable Linux x86-64 artifact and SHA-256 verified. +- [ ] Versioned ID appended; old IDs and recipes retained. +- [ ] Client metadata is searchable, categorized, unique, and recipe-free. +- [ ] Server recipe is pinned, integrity-checked, noninteractive, and credential-free. +- [ ] Every advertised executable has an offline verification command and PATH entry. +- [ ] Provider compatibility is explicit and accurate. +- [ ] Catalog, boundary, hash, resolver, type, API-validation, format, and diff checks pass. +- [ ] Real provider installation was tested, or the missing live verification is disclosed. diff --git a/.agents/skills/add-managed-cli/agents/openai.yaml b/.agents/skills/add-managed-cli/agents/openai.yaml new file mode 100644 index 00000000000..7b10d2ef10b --- /dev/null +++ b/.agents/skills/add-managed-cli/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Add Managed CLI" + short_description: "Add a verified CLI to sandbox images" + default_prompt: "Use $add-managed-cli to add a pinned, verified CLI to Sim sandbox images." diff --git a/.agents/skills/add-tools/SKILL.md b/.agents/skills/add-tools/SKILL.md index 1ed42f2e364..2e3afe56063 100644 --- a/.agents/skills/add-tools/SKILL.md +++ b/.agents/skills/add-tools/SKILL.md @@ -145,6 +145,22 @@ export const {serviceName}{Action}Tool: ToolConfig< - Always explicitly set `required: true` or `required: false` - Optional params should have `required: false` +## Resolved Secrets and Provenance Boundaries + +- Leave ordinary external API inputs and third-party results unchanged. Add provenance handling only + when an exact field is proven to cross a Sim model, durable-storage, or internal-execution boundary. +- Project AI-consumed text/structured fields with the smallest exact `request.modelInput` selector. +- Reject resolved secrets in opaque model input sent directly to an external provider with + `request.opaqueModelInput`; never attach private metadata to an external URL or `directExecution`. +- For authenticated internal routes, use `privateProvenance` for opaque model input or + `request.secretProvenance` for durable writes and execution handoffs. Authenticate first, validate + the exact selection and scope, strip the private envelope, then import or propagate provenance at + the receiving boundary. Preserve documented headerless legacy behavior. +- Never substitute secret plaintext into source, serialize plaintext provenance, hand-roll private + headers, or blanket-sanitize tool results. +- Add focused tests for named projection, identical unproven public text, malformed/incomplete + metadata, metadata stripping, scope isolation, and legacy compatibility where applicable. + ## Critical Rules for Outputs ### Output Types @@ -456,6 +472,9 @@ All tool IDs MUST use `snake_case`: `{service}_{action}` (e.g., `x_create_tweet` - [ ] Tools registered in `tools/registry.ts` - [ ] `bun run tool-metadata:generate` run and the regenerated artifacts committed - [ ] Block wired: `tools.access`, dropdown options, subBlocks, `tools.config`, outputs, inputs +- [ ] Model, durable-storage, and internal-execution boundaries use the shared provenance mechanisms + only where a concrete Sim `{{...}}` resolution path requires them +- [ ] Ordinary third-party inputs/results remain unchanged and private metadata never leaves Sim ## Final Validation (Required) diff --git a/.agents/skills/ship/SKILL.md b/.agents/skills/ship/SKILL.md index d1bfd8b4a0e..8f144a04aa6 100644 --- a/.agents/skills/ship/SKILL.md +++ b/.agents/skills/ship/SKILL.md @@ -69,7 +69,8 @@ When the user runs `/ship`: for s in check:boundaries check:api-validation:strict check:desktop-bridge check:desktop-ipc \ check:utils check:zustand-v5 \ check:react-query check:client-boundary check:bare-icons check:icon-paths \ - check:realtime-prune check:tool-registry-boundary tool-metadata:check \ + check:realtime-prune check:tool-registry-boundary check:tool-request-boundary \ + tool-metadata:check \ integration-catalog:check skills:check agent-stream-docs:check; do ( bun run "$s" >"/tmp/ship-audit-${s//:/-}.log" 2>&1; echo "$? $s" >>/tmp/ship-audit-results ) & done diff --git a/.agents/skills/validate-integration/SKILL.md b/.agents/skills/validate-integration/SKILL.md index fb503813eba..3d76c78b674 100644 --- a/.agents/skills/validate-integration/SKILL.md +++ b/.agents/skills/validate-integration/SKILL.md @@ -1,6 +1,6 @@ --- name: validate-integration -description: Validate an existing Sim integration (tools, block, registry) against the service's API docs +description: Validate an existing Sim integration (tools, block, registry, and resolved-secret/model-input boundaries) against the service's API docs and Sim execution conventions argument-hint: [api-docs-url] --- @@ -129,6 +129,53 @@ For **every** tool file, check: - [ ] Registry keys use snake_case and match tool IDs exactly - [ ] Entries are in alphabetical order within the file +### Resolved-Secret Provenance and Model Input + +For every request field, determine whether it is ordinary API input, model-visible text/structured +content, opaque model input, or a value persisted into Sim-owned durable storage. + +Treat model-input provenance as opt-in. Require official documentation or an unambiguous local +execution path proving that the exact field reaches an AI model. If the evidence is ambiguous, +leave the integration unchanged; do not infer a model boundary merely from natural-language, +search, extraction, or "AI-powered" marketing terminology. + +- [ ] AI-consumed text/structured fields use `request.modelInput` with `mode: 'project'` and a + minimal exact selector; nested/JSON-string adapters preserve shape through `applyProjected` +- [ ] Opaque AI-consumed values sent directly to an external provider or `directExecution` use + `request.opaqueModelInput` with `mode: 'reject-resolved-secrets'` and an exact effective-value + selector; the central executor rejects incomplete/secret-bearing committed provenance before + formatting or I/O, leaves safe bytes unchanged, and sends no provenance metadata externally +- [ ] Opaque AI-consumed files/bytes/URLs owned by an authenticated internal route use + `privateProvenance` (or `mode: 'private-provenance'`), and the route validates + `validateOpaqueModelInputProvenance` before any download or model call +- [ ] Persisted workspace-file contents are checked with the shared provenance guard only when + their bytes or decoded content cross into a model/tool-result boundary; ordinary file APIs + remain unchanged. Unsupported secret-bearing file paths are rejected at `file_write` +- [ ] Sim-owned durable writes and internal execution handoffs that can enter workflows/models use + field-scoped `request.secretProvenance`; authenticated receivers validate the exact selection + and scope, strip private metadata, and persist, import, or propagate it at the owning boundary +- [ ] Private provenance is never attached to external URLs or `directExecution`; those paths use + centralized `opaqueModelInput` rejection when their opaque values are model-bound +- [ ] No tool performs raw secret plaintext/source substitution or serializes plaintext provenance +- [ ] No `transformResponse` or tool-local helper blanket-sanitizes ordinary third-party results; + only execution-scoped, activated Sim provenance is projected at shared model/log boundaries +- [ ] Private headers/envelopes are produced and stripped by the shared tool executor, never + hand-rolled or returned as functional output +- [ ] Every added provenance hook has a concrete Sim `{{...}}` resolution path and a later + persistence/model/log crossing; there is no generic handling for arbitrary filenames, + metadata, provider results, or API payloads +- [ ] Diagnostic projection is applied only to values carrying execution-scoped provenance; + ordinary provider responses, filenames, URLs, and errors are unchanged +- [ ] Tests cover named `{{NAME}}` projection, unproven identical public text, nested shape + preservation, malformed/incomplete metadata, centralized opaque rejection before formatting + or I/O with safe-byte preservation, headerless legacy requests, metadata stripping, and + durable legacy/stale/scope cases when applicable + +Treat a missing or bypassed model, durable, or internal-execution provenance boundary as +**critical**. Do not fix it with a tool-specific string replacer or by sanitizing every provider +result; repair the shared request, authenticated internal-route, persistence, or re-entry boundary +that owns the data. + ## Step 4: Validate Block ### Block ↔ Tool Alignment (CRITICAL) @@ -301,6 +348,13 @@ Group findings by severity: - Service-account metadata disagrees with the canonical OAuth service configuration - `tools.config.tool` returning wrong tool ID for an operation - Type coercions in `tools.config.tool` instead of `tools.config.params` +- AI-consumed request fields bypass the shared projection, centralized opaque rejection, or + private-provenance boundary +- Opaque model input is downloaded or sent before provenance and workspace-file checks +- A Sim-owned durable sink or internal execution handoff drops encrypted provenance or breaks + legacy headerless/`NULL` data +- A tool substitutes secret plaintext into source, leaks private metadata, or generically sanitizes + unrelated third-party results **Warning** (follows conventions incorrectly or has usability issues): - Optional field not set to `mode: 'advanced'` @@ -375,6 +429,10 @@ After fixing, confirm: - [ ] Validated memory load safety using `.agents/skills/memory-load-check/SKILL.md` when tools list/search/download/import/export/batch data - [ ] Validated error handling (error checks, meaningful messages) - [ ] Validated registry entries (tools and block, alphabetical, correct imports) +- [ ] Validated model-visible/opaque inputs and Sim-durable/internal-execution provenance at their + owning boundaries +- [ ] Confirmed legacy persisted data keeps working and tracked invalid provenance fails closed +- [ ] Confirmed ordinary third-party results remain unchanged absent activated Sim provenance - [ ] Validated `{Service}BlockMeta` exported with at least 7 templates - [ ] Reported all issues grouped by severity - [ ] Fixed all critical and warning issues diff --git a/.claude/commands/add-integration.md b/.claude/commands/add-integration.md index 8df06ac1771..2b8e6a4fc13 100644 --- a/.claude/commands/add-integration.md +++ b/.claude/commands/add-integration.md @@ -1,5 +1,5 @@ --- -description: Add a complete Sim integration from API docs, covering tools, block, icon, optional triggers, registrations, and integration conventions. Use when introducing a new service under `apps/sim/tools`, `apps/sim/blocks`, and `apps/sim/triggers`. +description: Add a complete Sim integration from API docs, covering tools, block, icon, optional triggers, registrations, resolved-secret/model-input safety, and integration conventions. Use when introducing a new service under `apps/sim/tools`, `apps/sim/blocks`, and `apps/sim/triggers`. argument-hint: [api-docs-url] --- @@ -121,6 +121,64 @@ export const {service}{Action}Tool: ToolConfig = { - When using `type: 'json'` and you know the object shape, define `properties` with the inner fields so downstream consumers know the structure. Only use bare `type: 'json'` when the shape is truly dynamic - If you do not know the response JSON shape from docs or verified examples, you MUST tell the user and stop. Never guess outputs or response mappings. +### Resolved Secrets at Model and Persistence Boundaries + +Classify every request field before implementing the tool: + +This is opt-in, not a blanket integration migration. Add a model-input declaration only when the +service's official documentation or an unambiguous local execution path proves that the exact +field is consumed by an AI model. If that cannot be established, preserve existing tool behavior +and leave the field unannotated. + +- **Ordinary provider/API input:** leave it unchanged. Do not add blanket result sanitization. +- **Text or structured content consumed by an AI model:** declare `request.modelInput` with + `mode: 'project'` and select only the exact model-visible fields. The shared executor replaces + activated Sim secrets with canonical `{{NAME}}` labels before request formatting. For nested or + JSON-string fields, use a small shared selector plus `applyProjected`; verify that selecting the + rebuilt params reproduces the projected selection. +- **Opaque model input sent directly to an external provider** such as a model-read URL or image + payload: declare `request.opaqueModelInput` with `mode: 'reject-resolved-secrets'` and select only + the exact effective value. The shared `executeTool` preflight rejects incomplete or secret-bearing + committed provenance before URL/body formatting or network I/O, preserves safe request bytes, + and sends no provenance metadata to the provider. +- **Opaque model input owned by an authenticated internal route** such as uploaded audio, image, + video, file bytes, or signed URLs: add `privateProvenance` to a projected request, or use + `mode: 'private-provenance'` when there is no textual projection. The route must call + `validateOpaqueModelInputProvenance` before downloading or sending content to the model and must + apply the workspace-file provenance guard before reading a persisted workspace file. +- **Sim-owned durable storage or internal execution handoff** that can later enter a workflow/model + (table cells, Agent memory, knowledge documents/chunks, workspace-file contents, or child-workflow + input): transport encrypted field-scoped provenance with `request.secretProvenance`. The + authenticated receiver validates the exact selection and scope, strips the private envelope, and + persists, imports, or propagates it at the owning boundary. Preserve shared legacy behavior for + headerless internal calls and rows/files whose provenance marker is `NULL`; never invent a + tool-local migration rule. + +Hard rules: + +- Never substitute secret plaintext into source or serialize plaintext provenance. +- Never hand-roll private provenance headers/envelopes; the shared `executeTool` boundary owns + transport and strips private metadata from functional results. +- Never attach private provenance to an external URL or to `directExecution`. Use the centralized + `opaqueModelInput` rejection mode for external/direct opaque model inputs, or an authenticated + internal route when encrypted provenance must cross the boundary. +- Never sanitize arbitrary third-party tool results. Projection applies only to secrets activated + by Sim's resolved-secret provenance for that execution/tool call. +- Do not add provenance merely because a value is persisted, returned by a tool, or appears in a + filename. Require a concrete Sim `{{...}}` resolution path and a later model/log boundary. If an + unsupported field can resolve a secret but does not justify durable tracking (for example a + `file_write` path), reject it at that exact ingress. +- At diagnostic boundaries, project only values carrying execution-scoped provenance. Ordinary + provider responses, filenames, URLs, and errors remain unchanged when Sim did not resolve a + secret into them. + +Add focused tests covering named projection, ordinary identical text without provenance, nested +shape preservation, malformed/incomplete private metadata failing closed, centralized external +opaque rejection before formatting/I/O without byte changes or metadata transport, headerless +legacy requests, and absence of private metadata in the public tool result. For durable sinks, also +cover legacy `NULL` markers, exact-empty new writes, tracked secret writes, stale/missing sidecars, +and scope isolation. + ## Step 3: Create Block ### File Location @@ -534,6 +592,11 @@ If creating V2 versions (API-aligned outputs): - [ ] Created `index.ts` barrel export - [ ] Registered all tools in `tools/registry.ts` - [ ] Ran `bun run tool-metadata:generate` and committed the regenerated artifacts +- [ ] Classified every model-visible, opaque, Sim-durable, and internal-execution request field +- [ ] Added shared model-input projection, centralized opaque rejection, or private provenance only + where required +- [ ] Confirmed ordinary third-party tool results are not generically sanitized +- [ ] Added provenance compatibility and fail-closed boundary tests where applicable ### Block - [ ] Created `blocks/blocks/{service}.ts` diff --git a/.claude/commands/add-managed-cli.md b/.claude/commands/add-managed-cli.md new file mode 100644 index 00000000000..4b5bfcd0840 --- /dev/null +++ b/.claude/commands/add-managed-cli.md @@ -0,0 +1,148 @@ +--- +description: Add or upgrade a curated, immutable managed CLI for Sim Function sandboxes, including client-safe catalog metadata, a pinned server-only installation recipe, checksum and executable verification, provider compatibility, PATH propagation, content-addressed image identity, and tests. Use when adding a CLI to the Sandbox managed-CLI selector or changing an existing managed CLI version or recipe. +--- + +# Add a Managed CLI + +Add CLIs through the curated registry. Never turn this surface into arbitrary commands or package names: system packages already cover validated Debian/APT coordinates, while managed CLIs require immutable artifacts and reproducible recipes. + +## Read First + +Read these live sources before editing; do not copy their current entries into this skill: + +1. `apps/sim/lib/execution/remote-sandbox/cli-tools.ts` — persisted IDs and client-safe metadata. +2. `apps/sim/lib/execution/remote-sandbox/cli-tools.server.ts` — server-only recipes and recipe helpers. +3. `apps/sim/lib/execution/remote-sandbox/cli-tools.test.ts` — catalog and supply-chain invariants. +4. `apps/sim/lib/execution/remote-sandbox/cli-tools-boundary.test.ts` — client/server import boundary. +5. `apps/sim/lib/execution/remote-sandbox/sandbox-spec.ts` — content-addressed hash inputs. + +Read `resolve.ts` and `e2b.ts` only when changing provisioning mechanics. A normal catalog addition should not require UI, API, database, resolver, or provider edits; those paths derive from the registries. + +Do not modify the dedicated Function base image or the separate Mothership Shell template for a normal managed CLI addition. Managed recipes layer on the Function base image. Do not change `MAX_SANDBOX_CLI_TOOLS` from ten unless the user separately requests a product-limit change. + +## 1. Verify the Upstream Release + +Use primary upstream release documentation and official artifacts. Establish all of the following before writing code: + +- Exact version and stable Linux x86-64 artifact URL. Never use `latest`, mutable redirects, or an unversioned installer. +- SHA-256 for the exact artifact. Prefer a publisher-signed checksum; otherwise download the official artifact and compute it independently. +- Archive layout and the exact executable paths to install. +- Noninteractive, credential-free verification commands for every advertised executable, normally version commands. +- Required PATH entries under `/opt/sim-cli`. +- E2B and Daytona compatibility. Default to both only when the same Linux recipe works on both. + +When the user does not name a version, select the current stable upstream release from primary sources and state the exact version chosen. Do not silently choose a prerelease or infer a version from an unverified secondary source. + +Reject curl-to-shell installers, `npm install`, `pip install`, distro package repositories, arbitrary user commands, and artifacts from unofficial mirrors. Never place credentials, tokens, login commands, or account configuration in an image recipe or build log; authentication is runtime-only. + +## 2. Choose an Immutable ID + +Use `@-r`. + +- New upstream version: append a new ID ending in `-r1`. +- Recipe-only change for the same upstream version: append `-r2`, `-r3`, and so on. +- Never mutate or delete an existing ID or recipe. Persisted sandboxes must continue resolving to the bytes and behavior they selected. +- On upgrade, retain the old ID and recipe and set its metadata to `selectable: false`. Only the newest version keeps the public label selectable. + +Before shipping the first upgrade for a tool family, verify that editing a sandbox cannot leave both the retired and replacement IDs selected. If the generic selector and API validation do not already replace or reject colliding versions, address that once at the generic registry boundary with focused UI and contract tests; never special-case the individual CLI or silently install two versions that expose the same executable. + +Recipe identity includes the ID, revision, and SHA-256 in the sandbox image hash. Keeping old entries is what makes that identity reproducible rather than merely cache-busting. + +## 3. Add Client-Safe Metadata + +In `cli-tools.ts`: + +1. Append the ID to `SANDBOX_CLI_TOOL_IDS` in the same order used by the metadata and recipe registries. +2. Add a `SANDBOX_CLI_TOOLS` entry whose key and `id` exactly match. +3. Provide a unique selectable `label`, concise `description`, existing `category`, and useful executable/vendor aliases in `searchTerms`. +4. Add a category only when no existing category is accurate, then ensure it has at least one selectable entry. + +Keep this file safe for client bundles. It must not contain artifact URLs, checksums, install commands, verification commands, PATH recipes, provider SDKs, or imports from `cli-tools.server.ts`. + +The API enum and searchable grouped selector derive from this registry. Do not add parallel option arrays or route-local wire types. + +## 4. Add the Server-Only Recipe + +In `cli-tools.server.ts`, use the narrowest existing helper: + +- `defineBinaryRecipe` for one downloaded binary. +- `defineTarGzipRecipe` or `defineZipRecipe` for archives containing binaries. +- `defineVerifiedRecipe` for a vendor archive or installer layout that needs explicit commands. +- A direct typed entry only when the helpers cannot faithfully model the release. + +Provide every field the recipe contract requires: + +- Exact `version`, `artifactUrl`, `artifactName`, and lowercase 64-character `sha256`. +- Every installed `executable` and a corresponding `verificationCommands` entry. +- Deterministic extraction/install commands into `/opt/sim-cli`; quote fixed paths and clean temporary artifacts. +- `pathEntries` when the executable is not installed into the helper's default `bin` directory. +- `supportedProviders` only when it differs from the E2B-and-Daytona default. +- `revision` when it differs from `1`; it must agree with the ID suffix. + +Verification must prove the command is discoverable through `sandboxCliEnvironment`, not authenticate or contact a user account. Recipe commands run as root during both prebuilt image creation and runtime provisioning. + +If the artifact host is new, add only the exact official hostname to the `officialHosts` allowlist in `cli-tools.test.ts`. Treat that as a supply-chain review, not a way to silence the test. + +## 5. Preserve Generic Behavior + +Confirm the existing generic paths remain sufficient: + +- `sandboxCliToolRecipes` canonicalizes and resolves the recipe. +- `sandboxCliEnvironment` propagates PATH to Python subprocesses, JavaScript subprocesses, and Shell. +- E2B bakes the recipe into the custom image; runtime-strategy providers install it within the Function timeout. +- CLI-only sandboxes remain buildable even with no language packages. +- `hashSandboxSpec` includes recipe ID, revision, and checksum while preserving the legacy hash for an empty CLI list. +- The settings selector derives groups and search aliases from client-safe metadata. + +Do not special-case a CLI in those layers unless the registry contract cannot express a genuine provider requirement. Extend the registry contract generically when multiple CLIs need the same new behavior. + +## 6. Test the Addition + +Extend tests when the new entry introduces behavior not already covered: + +- For every upgrade, add a regression proving the old ID and recipe remain resolvable but non-selectable, while the replacement ID is selectable. +- Add important executable aliases to the table-driven search assertion. +- Add a focused assertion for a multi-executable recipe, custom PATH, or restricted provider. +- Add an opt-in credentialed smoke test only when installation plus a real minimal command cannot be validated without authentication. Read credentials from test-only environment variables, skip by default, create them only at runtime, and always tear down the sandbox. + +Never commit downloaded artifacts or credentials. + +## Required Validation + +From `apps/sim`: + +```bash +bunx vitest run \ + lib/execution/remote-sandbox/cli-tools.test.ts \ + lib/execution/remote-sandbox/cli-tools-boundary.test.ts \ + lib/execution/remote-sandbox/sandbox-spec.test.ts \ + lib/execution/remote-sandbox/resolve.test.ts \ + lib/api/contracts/sandboxes.test.ts \ + 'app/workspace/[workspaceId]/settings/components/sandboxes/utils.test.ts' \ + 'app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.test.tsx' +``` + +From the repository root: + +```bash +bun run type-check +bun run check:api-validation +bunx biome check \ + apps/sim/lib/execution/remote-sandbox/cli-tools.ts \ + apps/sim/lib/execution/remote-sandbox/cli-tools.server.ts \ + apps/sim/lib/execution/remote-sandbox/cli-tools.test.ts +git diff --check +``` + +For a new recipe, also exercise its install and every verification command in an actual E2B or Daytona sandbox when credentials and network access are available. Report clearly when only registry/unit validation ran. + +## Completion Checklist + +- [ ] Official immutable Linux x86-64 artifact and SHA-256 verified. +- [ ] Versioned ID appended; old IDs and recipes retained. +- [ ] Client metadata is searchable, categorized, unique, and recipe-free. +- [ ] Server recipe is pinned, integrity-checked, noninteractive, and credential-free. +- [ ] Every advertised executable has an offline verification command and PATH entry. +- [ ] Provider compatibility is explicit and accurate. +- [ ] Catalog, boundary, hash, resolver, type, API-validation, format, and diff checks pass. +- [ ] Real provider installation was tested, or the missing live verification is disclosed. diff --git a/.claude/commands/add-tools.md b/.claude/commands/add-tools.md index 60ab5448d96..e5c0da8997a 100644 --- a/.claude/commands/add-tools.md +++ b/.claude/commands/add-tools.md @@ -144,6 +144,22 @@ export const {serviceName}{Action}Tool: ToolConfig< - Always explicitly set `required: true` or `required: false` - Optional params should have `required: false` +## Resolved Secrets and Provenance Boundaries + +- Leave ordinary external API inputs and third-party results unchanged. Add provenance handling only + when an exact field is proven to cross a Sim model, durable-storage, or internal-execution boundary. +- Project AI-consumed text/structured fields with the smallest exact `request.modelInput` selector. +- Reject resolved secrets in opaque model input sent directly to an external provider with + `request.opaqueModelInput`; never attach private metadata to an external URL or `directExecution`. +- For authenticated internal routes, use `privateProvenance` for opaque model input or + `request.secretProvenance` for durable writes and execution handoffs. Authenticate first, validate + the exact selection and scope, strip the private envelope, then import or propagate provenance at + the receiving boundary. Preserve documented headerless legacy behavior. +- Never substitute secret plaintext into source, serialize plaintext provenance, hand-roll private + headers, or blanket-sanitize tool results. +- Add focused tests for named projection, identical unproven public text, malformed/incomplete + metadata, metadata stripping, scope isolation, and legacy compatibility where applicable. + ## Critical Rules for Outputs ### Output Types @@ -455,6 +471,9 @@ All tool IDs MUST use `snake_case`: `{service}_{action}` (e.g., `x_create_tweet` - [ ] Tools registered in `tools/registry.ts` - [ ] `bun run tool-metadata:generate` run and the regenerated artifacts committed - [ ] Block wired: `tools.access`, dropdown options, subBlocks, `tools.config`, outputs, inputs +- [ ] Model, durable-storage, and internal-execution boundaries use the shared provenance mechanisms + only where a concrete Sim `{{...}}` resolution path requires them +- [ ] Ordinary third-party inputs/results remain unchanged and private metadata never leaves Sim ## Final Validation (Required) diff --git a/.claude/commands/ship.md b/.claude/commands/ship.md index 326abe9fcb3..6b673b18f8e 100644 --- a/.claude/commands/ship.md +++ b/.claude/commands/ship.md @@ -68,7 +68,8 @@ When the user runs `/ship`: for s in check:boundaries check:api-validation:strict check:desktop-bridge check:desktop-ipc \ check:utils check:zustand-v5 \ check:react-query check:client-boundary check:bare-icons check:icon-paths \ - check:realtime-prune check:tool-registry-boundary tool-metadata:check \ + check:realtime-prune check:tool-registry-boundary check:tool-request-boundary \ + tool-metadata:check \ integration-catalog:check skills:check agent-stream-docs:check; do ( bun run "$s" >"/tmp/ship-audit-${s//:/-}.log" 2>&1; echo "$? $s" >>/tmp/ship-audit-results ) & done diff --git a/.claude/commands/validate-integration.md b/.claude/commands/validate-integration.md index 540da61cd4b..2c343a8fb65 100644 --- a/.claude/commands/validate-integration.md +++ b/.claude/commands/validate-integration.md @@ -1,5 +1,5 @@ --- -description: Validate an existing Sim integration (tools, block, registry) against the service's API docs +description: Validate an existing Sim integration (tools, block, registry, and resolved-secret/model-input boundaries) against the service's API docs and Sim execution conventions argument-hint: [api-docs-url] --- @@ -128,6 +128,53 @@ For **every** tool file, check: - [ ] Registry keys use snake_case and match tool IDs exactly - [ ] Entries are in alphabetical order within the file +### Resolved-Secret Provenance and Model Input + +For every request field, determine whether it is ordinary API input, model-visible text/structured +content, opaque model input, or a value persisted into Sim-owned durable storage. + +Treat model-input provenance as opt-in. Require official documentation or an unambiguous local +execution path proving that the exact field reaches an AI model. If the evidence is ambiguous, +leave the integration unchanged; do not infer a model boundary merely from natural-language, +search, extraction, or "AI-powered" marketing terminology. + +- [ ] AI-consumed text/structured fields use `request.modelInput` with `mode: 'project'` and a + minimal exact selector; nested/JSON-string adapters preserve shape through `applyProjected` +- [ ] Opaque AI-consumed values sent directly to an external provider or `directExecution` use + `request.opaqueModelInput` with `mode: 'reject-resolved-secrets'` and an exact effective-value + selector; the central executor rejects incomplete/secret-bearing committed provenance before + formatting or I/O, leaves safe bytes unchanged, and sends no provenance metadata externally +- [ ] Opaque AI-consumed files/bytes/URLs owned by an authenticated internal route use + `privateProvenance` (or `mode: 'private-provenance'`), and the route validates + `validateOpaqueModelInputProvenance` before any download or model call +- [ ] Persisted workspace-file contents are checked with the shared provenance guard only when + their bytes or decoded content cross into a model/tool-result boundary; ordinary file APIs + remain unchanged. Unsupported secret-bearing file paths are rejected at `file_write` +- [ ] Sim-owned durable writes and internal execution handoffs that can enter workflows/models use + field-scoped `request.secretProvenance`; authenticated receivers validate the exact selection + and scope, strip private metadata, and persist, import, or propagate it at the owning boundary +- [ ] Private provenance is never attached to external URLs or `directExecution`; those paths use + centralized `opaqueModelInput` rejection when their opaque values are model-bound +- [ ] No tool performs raw secret plaintext/source substitution or serializes plaintext provenance +- [ ] No `transformResponse` or tool-local helper blanket-sanitizes ordinary third-party results; + only execution-scoped, activated Sim provenance is projected at shared model/log boundaries +- [ ] Private headers/envelopes are produced and stripped by the shared tool executor, never + hand-rolled or returned as functional output +- [ ] Every added provenance hook has a concrete Sim `{{...}}` resolution path and a later + persistence/model/log crossing; there is no generic handling for arbitrary filenames, + metadata, provider results, or API payloads +- [ ] Diagnostic projection is applied only to values carrying execution-scoped provenance; + ordinary provider responses, filenames, URLs, and errors are unchanged +- [ ] Tests cover named `{{NAME}}` projection, unproven identical public text, nested shape + preservation, malformed/incomplete metadata, centralized opaque rejection before formatting + or I/O with safe-byte preservation, headerless legacy requests, metadata stripping, and + durable legacy/stale/scope cases when applicable + +Treat a missing or bypassed model, durable, or internal-execution provenance boundary as +**critical**. Do not fix it with a tool-specific string replacer or by sanitizing every provider +result; repair the shared request, authenticated internal-route, persistence, or re-entry boundary +that owns the data. + ## Step 4: Validate Block ### Block ↔ Tool Alignment (CRITICAL) @@ -300,6 +347,13 @@ Group findings by severity: - Service-account metadata disagrees with the canonical OAuth service configuration - `tools.config.tool` returning wrong tool ID for an operation - Type coercions in `tools.config.tool` instead of `tools.config.params` +- AI-consumed request fields bypass the shared projection, centralized opaque rejection, or + private-provenance boundary +- Opaque model input is downloaded or sent before provenance and workspace-file checks +- A Sim-owned durable sink or internal execution handoff drops encrypted provenance or breaks + legacy headerless/`NULL` data +- A tool substitutes secret plaintext into source, leaks private metadata, or generically sanitizes + unrelated third-party results **Warning** (follows conventions incorrectly or has usability issues): - Optional field not set to `mode: 'advanced'` @@ -374,6 +428,10 @@ After fixing, confirm: - [ ] Validated memory load safety using `.agents/skills/memory-load-check/SKILL.md` when tools list/search/download/import/export/batch data - [ ] Validated error handling (error checks, meaningful messages) - [ ] Validated registry entries (tools and block, alphabetical, correct imports) +- [ ] Validated model-visible/opaque inputs and Sim-durable/internal-execution provenance at their + owning boundaries +- [ ] Confirmed legacy persisted data keeps working and tracked invalid provenance fails closed +- [ ] Confirmed ordinary third-party results remain unchanged absent activated Sim provenance - [ ] Validated `{Service}BlockMeta` exported with at least 7 templates - [ ] Reported all issues grouped by severity - [ ] Fixed all critical and warning issues diff --git a/.cursor/commands/add-integration.md b/.cursor/commands/add-integration.md index 47c3ae3f0d0..9c5498257b6 100644 --- a/.cursor/commands/add-integration.md +++ b/.cursor/commands/add-integration.md @@ -116,6 +116,64 @@ export const {service}{Action}Tool: ToolConfig = { - When using `type: 'json'` and you know the object shape, define `properties` with the inner fields so downstream consumers know the structure. Only use bare `type: 'json'` when the shape is truly dynamic - If you do not know the response JSON shape from docs or verified examples, you MUST tell the user and stop. Never guess outputs or response mappings. +### Resolved Secrets at Model and Persistence Boundaries + +Classify every request field before implementing the tool: + +This is opt-in, not a blanket integration migration. Add a model-input declaration only when the +service's official documentation or an unambiguous local execution path proves that the exact +field is consumed by an AI model. If that cannot be established, preserve existing tool behavior +and leave the field unannotated. + +- **Ordinary provider/API input:** leave it unchanged. Do not add blanket result sanitization. +- **Text or structured content consumed by an AI model:** declare `request.modelInput` with + `mode: 'project'` and select only the exact model-visible fields. The shared executor replaces + activated Sim secrets with canonical `{{NAME}}` labels before request formatting. For nested or + JSON-string fields, use a small shared selector plus `applyProjected`; verify that selecting the + rebuilt params reproduces the projected selection. +- **Opaque model input sent directly to an external provider** such as a model-read URL or image + payload: declare `request.opaqueModelInput` with `mode: 'reject-resolved-secrets'` and select only + the exact effective value. The shared `executeTool` preflight rejects incomplete or secret-bearing + committed provenance before URL/body formatting or network I/O, preserves safe request bytes, + and sends no provenance metadata to the provider. +- **Opaque model input owned by an authenticated internal route** such as uploaded audio, image, + video, file bytes, or signed URLs: add `privateProvenance` to a projected request, or use + `mode: 'private-provenance'` when there is no textual projection. The route must call + `validateOpaqueModelInputProvenance` before downloading or sending content to the model and must + apply the workspace-file provenance guard before reading a persisted workspace file. +- **Sim-owned durable storage or internal execution handoff** that can later enter a workflow/model + (table cells, Agent memory, knowledge documents/chunks, workspace-file contents, or child-workflow + input): transport encrypted field-scoped provenance with `request.secretProvenance`. The + authenticated receiver validates the exact selection and scope, strips the private envelope, and + persists, imports, or propagates it at the owning boundary. Preserve shared legacy behavior for + headerless internal calls and rows/files whose provenance marker is `NULL`; never invent a + tool-local migration rule. + +Hard rules: + +- Never substitute secret plaintext into source or serialize plaintext provenance. +- Never hand-roll private provenance headers/envelopes; the shared `executeTool` boundary owns + transport and strips private metadata from functional results. +- Never attach private provenance to an external URL or to `directExecution`. Use the centralized + `opaqueModelInput` rejection mode for external/direct opaque model inputs, or an authenticated + internal route when encrypted provenance must cross the boundary. +- Never sanitize arbitrary third-party tool results. Projection applies only to secrets activated + by Sim's resolved-secret provenance for that execution/tool call. +- Do not add provenance merely because a value is persisted, returned by a tool, or appears in a + filename. Require a concrete Sim `{{...}}` resolution path and a later model/log boundary. If an + unsupported field can resolve a secret but does not justify durable tracking (for example a + `file_write` path), reject it at that exact ingress. +- At diagnostic boundaries, project only values carrying execution-scoped provenance. Ordinary + provider responses, filenames, URLs, and errors remain unchanged when Sim did not resolve a + secret into them. + +Add focused tests covering named projection, ordinary identical text without provenance, nested +shape preservation, malformed/incomplete private metadata failing closed, centralized external +opaque rejection before formatting/I/O without byte changes or metadata transport, headerless +legacy requests, and absence of private metadata in the public tool result. For durable sinks, also +cover legacy `NULL` markers, exact-empty new writes, tracked secret writes, stale/missing sidecars, +and scope isolation. + ## Step 3: Create Block ### File Location @@ -529,6 +587,11 @@ If creating V2 versions (API-aligned outputs): - [ ] Created `index.ts` barrel export - [ ] Registered all tools in `tools/registry.ts` - [ ] Ran `bun run tool-metadata:generate` and committed the regenerated artifacts +- [ ] Classified every model-visible, opaque, Sim-durable, and internal-execution request field +- [ ] Added shared model-input projection, centralized opaque rejection, or private provenance only + where required +- [ ] Confirmed ordinary third-party tool results are not generically sanitized +- [ ] Added provenance compatibility and fail-closed boundary tests where applicable ### Block - [ ] Created `blocks/blocks/{service}.ts` diff --git a/.cursor/commands/add-managed-cli.md b/.cursor/commands/add-managed-cli.md new file mode 100644 index 00000000000..99b05bca409 --- /dev/null +++ b/.cursor/commands/add-managed-cli.md @@ -0,0 +1,144 @@ +# Add a Managed CLI + +Add CLIs through the curated registry. Never turn this surface into arbitrary commands or package names: system packages already cover validated Debian/APT coordinates, while managed CLIs require immutable artifacts and reproducible recipes. + +## Read First + +Read these live sources before editing; do not copy their current entries into this skill: + +1. `apps/sim/lib/execution/remote-sandbox/cli-tools.ts` — persisted IDs and client-safe metadata. +2. `apps/sim/lib/execution/remote-sandbox/cli-tools.server.ts` — server-only recipes and recipe helpers. +3. `apps/sim/lib/execution/remote-sandbox/cli-tools.test.ts` — catalog and supply-chain invariants. +4. `apps/sim/lib/execution/remote-sandbox/cli-tools-boundary.test.ts` — client/server import boundary. +5. `apps/sim/lib/execution/remote-sandbox/sandbox-spec.ts` — content-addressed hash inputs. + +Read `resolve.ts` and `e2b.ts` only when changing provisioning mechanics. A normal catalog addition should not require UI, API, database, resolver, or provider edits; those paths derive from the registries. + +Do not modify the dedicated Function base image or the separate Mothership Shell template for a normal managed CLI addition. Managed recipes layer on the Function base image. Do not change `MAX_SANDBOX_CLI_TOOLS` from ten unless the user separately requests a product-limit change. + +## 1. Verify the Upstream Release + +Use primary upstream release documentation and official artifacts. Establish all of the following before writing code: + +- Exact version and stable Linux x86-64 artifact URL. Never use `latest`, mutable redirects, or an unversioned installer. +- SHA-256 for the exact artifact. Prefer a publisher-signed checksum; otherwise download the official artifact and compute it independently. +- Archive layout and the exact executable paths to install. +- Noninteractive, credential-free verification commands for every advertised executable, normally version commands. +- Required PATH entries under `/opt/sim-cli`. +- E2B and Daytona compatibility. Default to both only when the same Linux recipe works on both. + +When the user does not name a version, select the current stable upstream release from primary sources and state the exact version chosen. Do not silently choose a prerelease or infer a version from an unverified secondary source. + +Reject curl-to-shell installers, `npm install`, `pip install`, distro package repositories, arbitrary user commands, and artifacts from unofficial mirrors. Never place credentials, tokens, login commands, or account configuration in an image recipe or build log; authentication is runtime-only. + +## 2. Choose an Immutable ID + +Use `@-r`. + +- New upstream version: append a new ID ending in `-r1`. +- Recipe-only change for the same upstream version: append `-r2`, `-r3`, and so on. +- Never mutate or delete an existing ID or recipe. Persisted sandboxes must continue resolving to the bytes and behavior they selected. +- On upgrade, retain the old ID and recipe and set its metadata to `selectable: false`. Only the newest version keeps the public label selectable. + +Before shipping the first upgrade for a tool family, verify that editing a sandbox cannot leave both the retired and replacement IDs selected. If the generic selector and API validation do not already replace or reject colliding versions, address that once at the generic registry boundary with focused UI and contract tests; never special-case the individual CLI or silently install two versions that expose the same executable. + +Recipe identity includes the ID, revision, and SHA-256 in the sandbox image hash. Keeping old entries is what makes that identity reproducible rather than merely cache-busting. + +## 3. Add Client-Safe Metadata + +In `cli-tools.ts`: + +1. Append the ID to `SANDBOX_CLI_TOOL_IDS` in the same order used by the metadata and recipe registries. +2. Add a `SANDBOX_CLI_TOOLS` entry whose key and `id` exactly match. +3. Provide a unique selectable `label`, concise `description`, existing `category`, and useful executable/vendor aliases in `searchTerms`. +4. Add a category only when no existing category is accurate, then ensure it has at least one selectable entry. + +Keep this file safe for client bundles. It must not contain artifact URLs, checksums, install commands, verification commands, PATH recipes, provider SDKs, or imports from `cli-tools.server.ts`. + +The API enum and searchable grouped selector derive from this registry. Do not add parallel option arrays or route-local wire types. + +## 4. Add the Server-Only Recipe + +In `cli-tools.server.ts`, use the narrowest existing helper: + +- `defineBinaryRecipe` for one downloaded binary. +- `defineTarGzipRecipe` or `defineZipRecipe` for archives containing binaries. +- `defineVerifiedRecipe` for a vendor archive or installer layout that needs explicit commands. +- A direct typed entry only when the helpers cannot faithfully model the release. + +Provide every field the recipe contract requires: + +- Exact `version`, `artifactUrl`, `artifactName`, and lowercase 64-character `sha256`. +- Every installed `executable` and a corresponding `verificationCommands` entry. +- Deterministic extraction/install commands into `/opt/sim-cli`; quote fixed paths and clean temporary artifacts. +- `pathEntries` when the executable is not installed into the helper's default `bin` directory. +- `supportedProviders` only when it differs from the E2B-and-Daytona default. +- `revision` when it differs from `1`; it must agree with the ID suffix. + +Verification must prove the command is discoverable through `sandboxCliEnvironment`, not authenticate or contact a user account. Recipe commands run as root during both prebuilt image creation and runtime provisioning. + +If the artifact host is new, add only the exact official hostname to the `officialHosts` allowlist in `cli-tools.test.ts`. Treat that as a supply-chain review, not a way to silence the test. + +## 5. Preserve Generic Behavior + +Confirm the existing generic paths remain sufficient: + +- `sandboxCliToolRecipes` canonicalizes and resolves the recipe. +- `sandboxCliEnvironment` propagates PATH to Python subprocesses, JavaScript subprocesses, and Shell. +- E2B bakes the recipe into the custom image; runtime-strategy providers install it within the Function timeout. +- CLI-only sandboxes remain buildable even with no language packages. +- `hashSandboxSpec` includes recipe ID, revision, and checksum while preserving the legacy hash for an empty CLI list. +- The settings selector derives groups and search aliases from client-safe metadata. + +Do not special-case a CLI in those layers unless the registry contract cannot express a genuine provider requirement. Extend the registry contract generically when multiple CLIs need the same new behavior. + +## 6. Test the Addition + +Extend tests when the new entry introduces behavior not already covered: + +- For every upgrade, add a regression proving the old ID and recipe remain resolvable but non-selectable, while the replacement ID is selectable. +- Add important executable aliases to the table-driven search assertion. +- Add a focused assertion for a multi-executable recipe, custom PATH, or restricted provider. +- Add an opt-in credentialed smoke test only when installation plus a real minimal command cannot be validated without authentication. Read credentials from test-only environment variables, skip by default, create them only at runtime, and always tear down the sandbox. + +Never commit downloaded artifacts or credentials. + +## Required Validation + +From `apps/sim`: + +```bash +bunx vitest run \ + lib/execution/remote-sandbox/cli-tools.test.ts \ + lib/execution/remote-sandbox/cli-tools-boundary.test.ts \ + lib/execution/remote-sandbox/sandbox-spec.test.ts \ + lib/execution/remote-sandbox/resolve.test.ts \ + lib/api/contracts/sandboxes.test.ts \ + 'app/workspace/[workspaceId]/settings/components/sandboxes/utils.test.ts' \ + 'app/workspace/[workspaceId]/settings/components/sandboxes/components/sandbox-editor.test.tsx' +``` + +From the repository root: + +```bash +bun run type-check +bun run check:api-validation +bunx biome check \ + apps/sim/lib/execution/remote-sandbox/cli-tools.ts \ + apps/sim/lib/execution/remote-sandbox/cli-tools.server.ts \ + apps/sim/lib/execution/remote-sandbox/cli-tools.test.ts +git diff --check +``` + +For a new recipe, also exercise its install and every verification command in an actual E2B or Daytona sandbox when credentials and network access are available. Report clearly when only registry/unit validation ran. + +## Completion Checklist + +- [ ] Official immutable Linux x86-64 artifact and SHA-256 verified. +- [ ] Versioned ID appended; old IDs and recipes retained. +- [ ] Client metadata is searchable, categorized, unique, and recipe-free. +- [ ] Server recipe is pinned, integrity-checked, noninteractive, and credential-free. +- [ ] Every advertised executable has an offline verification command and PATH entry. +- [ ] Provider compatibility is explicit and accurate. +- [ ] Catalog, boundary, hash, resolver, type, API-validation, format, and diff checks pass. +- [ ] Real provider installation was tested, or the missing live verification is disclosed. diff --git a/.cursor/commands/add-tools.md b/.cursor/commands/add-tools.md index 8bb6f8398b5..45399b10698 100644 --- a/.cursor/commands/add-tools.md +++ b/.cursor/commands/add-tools.md @@ -139,6 +139,22 @@ export const {serviceName}{Action}Tool: ToolConfig< - Always explicitly set `required: true` or `required: false` - Optional params should have `required: false` +## Resolved Secrets and Provenance Boundaries + +- Leave ordinary external API inputs and third-party results unchanged. Add provenance handling only + when an exact field is proven to cross a Sim model, durable-storage, or internal-execution boundary. +- Project AI-consumed text/structured fields with the smallest exact `request.modelInput` selector. +- Reject resolved secrets in opaque model input sent directly to an external provider with + `request.opaqueModelInput`; never attach private metadata to an external URL or `directExecution`. +- For authenticated internal routes, use `privateProvenance` for opaque model input or + `request.secretProvenance` for durable writes and execution handoffs. Authenticate first, validate + the exact selection and scope, strip the private envelope, then import or propagate provenance at + the receiving boundary. Preserve documented headerless legacy behavior. +- Never substitute secret plaintext into source, serialize plaintext provenance, hand-roll private + headers, or blanket-sanitize tool results. +- Add focused tests for named projection, identical unproven public text, malformed/incomplete + metadata, metadata stripping, scope isolation, and legacy compatibility where applicable. + ## Critical Rules for Outputs ### Output Types @@ -450,6 +466,9 @@ All tool IDs MUST use `snake_case`: `{service}_{action}` (e.g., `x_create_tweet` - [ ] Tools registered in `tools/registry.ts` - [ ] `bun run tool-metadata:generate` run and the regenerated artifacts committed - [ ] Block wired: `tools.access`, dropdown options, subBlocks, `tools.config`, outputs, inputs +- [ ] Model, durable-storage, and internal-execution boundaries use the shared provenance mechanisms + only where a concrete Sim `{{...}}` resolution path requires them +- [ ] Ordinary third-party inputs/results remain unchanged and private metadata never leaves Sim ## Final Validation (Required) diff --git a/.cursor/commands/ship.md b/.cursor/commands/ship.md index 58ceae2ccae..77ec67d04a8 100644 --- a/.cursor/commands/ship.md +++ b/.cursor/commands/ship.md @@ -63,7 +63,8 @@ When the user runs `/ship`: for s in check:boundaries check:api-validation:strict check:desktop-bridge check:desktop-ipc \ check:utils check:zustand-v5 \ check:react-query check:client-boundary check:bare-icons check:icon-paths \ - check:realtime-prune check:tool-registry-boundary tool-metadata:check \ + check:realtime-prune check:tool-registry-boundary check:tool-request-boundary \ + tool-metadata:check \ integration-catalog:check skills:check agent-stream-docs:check; do ( bun run "$s" >"/tmp/ship-audit-${s//:/-}.log" 2>&1; echo "$? $s" >>/tmp/ship-audit-results ) & done diff --git a/.cursor/commands/validate-integration.md b/.cursor/commands/validate-integration.md index 223b76c44e1..4ec7b32e9ec 100644 --- a/.cursor/commands/validate-integration.md +++ b/.cursor/commands/validate-integration.md @@ -123,6 +123,53 @@ For **every** tool file, check: - [ ] Registry keys use snake_case and match tool IDs exactly - [ ] Entries are in alphabetical order within the file +### Resolved-Secret Provenance and Model Input + +For every request field, determine whether it is ordinary API input, model-visible text/structured +content, opaque model input, or a value persisted into Sim-owned durable storage. + +Treat model-input provenance as opt-in. Require official documentation or an unambiguous local +execution path proving that the exact field reaches an AI model. If the evidence is ambiguous, +leave the integration unchanged; do not infer a model boundary merely from natural-language, +search, extraction, or "AI-powered" marketing terminology. + +- [ ] AI-consumed text/structured fields use `request.modelInput` with `mode: 'project'` and a + minimal exact selector; nested/JSON-string adapters preserve shape through `applyProjected` +- [ ] Opaque AI-consumed values sent directly to an external provider or `directExecution` use + `request.opaqueModelInput` with `mode: 'reject-resolved-secrets'` and an exact effective-value + selector; the central executor rejects incomplete/secret-bearing committed provenance before + formatting or I/O, leaves safe bytes unchanged, and sends no provenance metadata externally +- [ ] Opaque AI-consumed files/bytes/URLs owned by an authenticated internal route use + `privateProvenance` (or `mode: 'private-provenance'`), and the route validates + `validateOpaqueModelInputProvenance` before any download or model call +- [ ] Persisted workspace-file contents are checked with the shared provenance guard only when + their bytes or decoded content cross into a model/tool-result boundary; ordinary file APIs + remain unchanged. Unsupported secret-bearing file paths are rejected at `file_write` +- [ ] Sim-owned durable writes and internal execution handoffs that can enter workflows/models use + field-scoped `request.secretProvenance`; authenticated receivers validate the exact selection + and scope, strip private metadata, and persist, import, or propagate it at the owning boundary +- [ ] Private provenance is never attached to external URLs or `directExecution`; those paths use + centralized `opaqueModelInput` rejection when their opaque values are model-bound +- [ ] No tool performs raw secret plaintext/source substitution or serializes plaintext provenance +- [ ] No `transformResponse` or tool-local helper blanket-sanitizes ordinary third-party results; + only execution-scoped, activated Sim provenance is projected at shared model/log boundaries +- [ ] Private headers/envelopes are produced and stripped by the shared tool executor, never + hand-rolled or returned as functional output +- [ ] Every added provenance hook has a concrete Sim `{{...}}` resolution path and a later + persistence/model/log crossing; there is no generic handling for arbitrary filenames, + metadata, provider results, or API payloads +- [ ] Diagnostic projection is applied only to values carrying execution-scoped provenance; + ordinary provider responses, filenames, URLs, and errors are unchanged +- [ ] Tests cover named `{{NAME}}` projection, unproven identical public text, nested shape + preservation, malformed/incomplete metadata, centralized opaque rejection before formatting + or I/O with safe-byte preservation, headerless legacy requests, metadata stripping, and + durable legacy/stale/scope cases when applicable + +Treat a missing or bypassed model, durable, or internal-execution provenance boundary as +**critical**. Do not fix it with a tool-specific string replacer or by sanitizing every provider +result; repair the shared request, authenticated internal-route, persistence, or re-entry boundary +that owns the data. + ## Step 4: Validate Block ### Block ↔ Tool Alignment (CRITICAL) @@ -295,6 +342,13 @@ Group findings by severity: - Service-account metadata disagrees with the canonical OAuth service configuration - `tools.config.tool` returning wrong tool ID for an operation - Type coercions in `tools.config.tool` instead of `tools.config.params` +- AI-consumed request fields bypass the shared projection, centralized opaque rejection, or + private-provenance boundary +- Opaque model input is downloaded or sent before provenance and workspace-file checks +- A Sim-owned durable sink or internal execution handoff drops encrypted provenance or breaks + legacy headerless/`NULL` data +- A tool substitutes secret plaintext into source, leaks private metadata, or generically sanitizes + unrelated third-party results **Warning** (follows conventions incorrectly or has usability issues): - Optional field not set to `mode: 'advanced'` @@ -369,6 +423,10 @@ After fixing, confirm: - [ ] Validated memory load safety using `.agents/skills/memory-load-check/SKILL.md` when tools list/search/download/import/export/batch data - [ ] Validated error handling (error checks, meaningful messages) - [ ] Validated registry entries (tools and block, alphabetical, correct imports) +- [ ] Validated model-visible/opaque inputs and Sim-durable/internal-execution provenance at their + owning boundaries +- [ ] Confirmed legacy persisted data keeps working and tracked invalid provenance fails closed +- [ ] Confirmed ordinary third-party results remain unchanged absent activated Sim provenance - [ ] Validated `{Service}BlockMeta` exported with at least 7 templates - [ ] Reported all issues grouped by severity - [ ] Fixed all critical and warning issues diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index ac98f41f62a..4ae17ec7ce0 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -156,6 +156,9 @@ jobs: - name: Tool registry client-boundary audit run: bun run check:tool-registry-boundary + - name: Tool request transport boundary audit + run: bun run check:tool-request-boundary + - name: Verify generated tool metadata is in sync run: bun run tool-metadata:check diff --git a/.gitignore b/.gitignore index 501da963969..6e9bdf2c99c 100644 --- a/.gitignore +++ b/.gitignore @@ -50,9 +50,7 @@ dump.rdb .env.production # editor swap files -*.swp -*.swo -*.swn +*.sw? # vercel .vercel diff --git a/apps/docs/content/docs/en/agents/custom-tools.mdx b/apps/docs/content/docs/en/agents/custom-tools.mdx index 0a2eb82d517..c63df36da59 100644 --- a/apps/docs/content/docs/en/agents/custom-tools.mdx +++ b/apps/docs/content/docs/en/agents/custom-tools.mdx @@ -84,6 +84,8 @@ return { }; ``` +For a secret used as a complete JavaScript expression, prefer the unquoted form, such as `const apiKey = {{OPENWEATHER_API_KEY}};`. Quoted and embedded forms remain supported, including the placeholder embedded in the URL above, `"Bearer {{KEY}}"`, template literals, and JavaScript regex literals. The value is bound separately when the tool executes rather than pasted into its source, so its exact string contents are preserved. + You can also use the AI wand to generate code from a description. Environment variables are referenced with `{{KEY}}` syntax. @@ -114,7 +116,7 @@ Once created, custom tools appear alongside built-in tools when configuring an A - **Async/await** — Your code runs in an async context, so you can use `await` directly - **fetch()** — Make HTTP requests to external APIs - **Node.js built-ins** — Access to `crypto`, `Buffer`, and other standard modules -- **Environment variables** — Use `{{KEY}}` syntax to inject secrets +- **Environment variables** — Use `{{KEY}}` syntax to bind secrets at execution time without placing plaintext in source code ### Limitations @@ -155,7 +157,7 @@ From **Settings → Custom Tools** you can: \ No newline at end of file +]} /> diff --git a/apps/docs/content/docs/en/api-reference/typescript.mdx b/apps/docs/content/docs/en/api-reference/typescript.mdx index 791849f94a5..e51c26605bb 100644 --- a/apps/docs/content/docs/en/api-reference/typescript.mdx +++ b/apps/docs/content/docs/en/api-reference/typescript.mdx @@ -91,6 +91,7 @@ const result = await client.executeWorkflow('workflow-id', { message: 'Hello, wo - `stream` (boolean): Enable streaming responses (default: false) - `selectedOutputs` (string[]): Block outputs to stream in `blockName.attribute` format (e.g., `["agent1.content"]`) - `async` (boolean): Execute asynchronously (default: false) + - `executionTimeoutSeconds` (number): Optional server-side async execution cap from 1 to 604800 seconds. Requires `async: true` and cannot extend the account policy. **Returns:** `Promise` diff --git a/apps/docs/content/docs/en/platform/costs.mdx b/apps/docs/content/docs/en/platform/costs.mdx index 848e067c543..6f020cbd933 100644 --- a/apps/docs/content/docs/en/platform/costs.mdx +++ b/apps/docs/content/docs/en/platform/costs.mdx @@ -361,10 +361,11 @@ Storage follows the paid tier: Pro and Pro for Teams share the Pro limit, while | Plan | Sync | Async | |------|------|-------| | **Free** | 5 minutes | 90 minutes | -| **Pro / Max / Team / Enterprise** | 50 minutes | 90 minutes | +| **Pro / Max / Team** | 50 minutes | 90 minutes | +| **Enterprise** | 50 minutes | 90 minutes by default; configurable up to 7 days | **Sync runs** complete immediately and return results directly. These are triggered via the API with `async: false` (default) or through the UI. -**Async runs** (triggered via API with `async: true`, webhooks, or schedules) run in the background. +**Async runs** (triggered via API with `async: true`, webhooks, schedules, polling triggers, or table workflow columns) run in the background. A direct async API request can shorten the account policy for that request, but cannot extend it. If a workflow exceeds its time limit, it will be terminated and marked as failed with a timeout error. Design long-running workflows to use async runs or break them into smaller workflows. diff --git a/apps/docs/content/docs/en/platform/credentials.mdx b/apps/docs/content/docs/en/platform/credentials.mdx index 3502b5f6243..7b03b1a72d9 100644 --- a/apps/docs/content/docs/en/platform/credentials.mdx +++ b/apps/docs/content/docs/en/platform/credentials.mdx @@ -69,10 +69,10 @@ Select the secret you want to use. The reference appears highlighted in blue and When a saved secret is successfully substituted through a `{{KEY}}` reference, Sim masks exact, case-sensitive occurrences of its resolved value in log-facing content. This includes the editor's live block-log display, Logs Overview input and output, stored execution traces, log-read API responses, and the Logs block's **Get Run Details** output. Function and Agent span inputs, outputs, and errors, Agent thinking, and Agent tool-call arguments, results, and errors are protected. The replacement is normally shown as `{{KEY}}`. -This is an observability projection only. Secret resolution and workflow behavior are unchanged: blocks, tools, models, and downstream steps receive the real runtime value. Stored functional execution data, workflow execution responses, streams, callbacks, block state, and snapshots are not rewritten. Log-facing views and read APIs receive a separate protected copy, so the Logs Overview **Workflow Input** and **Workflow Output** are masked without changing the underlying workflow result. +Secret resolution and functional workflow behavior are unchanged: blocks, tools, and downstream steps receive the real runtime value. Stored functional execution data, workflow execution responses, streams, callbacks, block state, and snapshots are not rewritten. Log-facing views and read APIs receive a separate protected copy, so the Logs Overview **Workflow Input** and **Workflow Output** are masked without changing the underlying workflow result. Model requests receive another protected projection: exact secret values known to the run are replaced with `{{KEY}}` before model-visible messages, prompts, tool arguments, or tool continuations leave Sim. -Masking is activated only when Sim successfully resolves a value from **Settings → Secrets** through `{{KEY}}`. A hardcoded literal, direct `environmentVariables['KEY']` read, or shell `$KEY` read does not activate it by itself. Once activated, every exact occurrence of that value in the run's log-facing content is masked. Encoded, hashed, or otherwise transformed versions are not matched. Do not deliberately return or print secrets. +Execution-log masking is activated only when Sim successfully resolves a value from **Settings → Secrets** through `{{KEY}}`. A hardcoded literal, direct `environmentVariables['KEY']` read, or shell `$KEY` read does not activate log masking by itself. Model-bound projection also checks the run's authorized secret catalog, including direct reads, but both protections match only exact values. Encoded, hashed, fragmented, or otherwise transformed versions are not matched. Do not deliberately return or print secrets. ### Copilot code execution @@ -136,7 +136,7 @@ When a workflow runs, secrets resolve in this order: : +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: + +```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. + +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. + +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: + +```bash +# E2B +MOTHERSHIP_E2B_TEMPLATE_ID= + +# Daytona +DAYTONA_SHELL_SNAPSHOT_ID= +``` + +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. + +Use E2B as the release baseline before building or promoting Daytona: + +```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 `