Commit Graph
4792 Commits
Author SHA1 Message Date
Waleed 402472e4cc chore(readme): refresh demo GIFs from docs, lead with Mothership (#4878) 2026-06-04 10:06:31 -07:00
Waleed c620fdccf0 fix(security): chat attachment XSS, MCP OAuth SSRF guards, Teams clientState verification (#4877)
* fix(chat): prevent XSS in attachment preview via filename/data URL escaping

Replace document.write with an escaped blob URL preview: HTML-entity
encode the user-controlled filename and data URL, open with
noopener,noreferrer, and revoke the blob URL after navigation.

* fix(mcp): guard OAuth discovery and token revocation against SSRF

Route discoverOAuthServerInfo and the RFC 7009 revocation POST through an
SSRF-guarded fetch that validates every request URL via validateMcpServerSsrf
(blocking private/reserved/loopback targets, honoring ALLOWED_MCP_DOMAINS and
self-hosted localhost rules) and pins the connection to the resolved IP to
prevent DNS-rebinding TOCTOU. Previously these fetches used unvalidated global
fetch against URLs taken verbatim from attacker-controllable
authorization-server metadata.

* fix(webhooks): verify Graph clientState on Teams chat-subscription notifications

The microsoftteams_chat_subscription trigger set clientState=webhook.id when
creating the Graph subscription but never validated it on inbound change
notifications, so any request to the webhook path with a crafted notification
body was treated as authentic (CWE-345). verifyAuth now requires every
notification in the value array to carry a clientState matching the stored
webhook id (constant-time compare) and rejects payloads without notifications.
Validation handshakes (validationToken) are handled before auth and remain
unaffected; outgoing-webhook HMAC auth is unchanged.

* fix(webhooks): fail closed when Teams chat-subscription webhook id is unavailable

Hardens the clientState check so a missing webhook id (theoretically
unreachable, since the row is looked up by primary key) can never collapse
the expected value to an empty string that a forged clientState could match.

* docs(mcp): note AbortSignal does not bound SSRF-guard DNS lookup

* improvement(chat): hoist HTML escape map to module-level constant
2026-06-04 09:48:48 -07:00
Theodore Li 4076d7612b chore(api-key): remove legacy scan+decrypt auth fallback (#4876) 2026-06-03 21:52:23 -04:00
Theodore LiandClaude Opus 4.8 3518b999af feat(tables): background import for large CSVs with live progress (#4861)
* feat(tables): background import for large CSVs with live progress

* fix(tables): address review — import heartbeat, overlap guard, column/empty validation

* fix(tables): guard sync import overlap, scope fileKey to workspace, delete-on-replace after download

* fix(tables): stream large CSV imports from storage instead of buffering the whole file

* test(tables): fix async-import route tests for workspace-scoped fileKey + name uniquification

* fix(tables): append imports start after existing rows; reconcile missed import failures in the tray

* fix(tables): delete the uploaded CSV from storage after the import finishes

* fix(tables): validate replace before deleting rows; ignore stale replayed import events by importId

* fix(tables): bind import worker to its importId (no stale-worker clobber/overlap) and destroy storage stream on failure

* feat(tables): byte-based import progress, cancel support, and a start toast that opens the import view

* fix(tables): don't emit ready after cancel; honor cancel during the upload phase

* improvement(tables): use a stop (square) icon for canceling an active import

* fix(tables): make markTableImporting an atomic claim to close the concurrent-import TOCTOU race

* improvement(tables): preview CSV import from a slice, drop client row-count warning

The import dialog parsed the entire file in the browser to show an exact row
count and a row-limit warning. That holds the whole file in memory, blocks the
main thread, and hits V8's ~512MB string ceiling — so the dialog capped the
effective import size well below what the streaming importer handles.

Parse only the first 512KB (headers + sample for the mapping); drop the exact
count and the "would exceed the row limit by N" gate. The DB row-count trigger
already enforces max_rows server-side, so an over-limit import fails fast during
the run with a clear message instead of being blocked by an expensive parse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): gate import ownership every batch and stop canceled imports reappearing

- Worker checked run ownership only at the progress cadence (~every 5k rows), so
  a canceled/superseded import could insert several more batches (incl. the final
  partial batch) before stopping. Move the updateImportProgress ownership gate to
  the top of every flush — a run that lost the table stops within one batch.
- A list/dialog import canceled mid-upload left the server row `importing` until
  the in-flight server cancel landed; hydration re-seeded it from useTablesList,
  so the dismissed import flickered back. Flag the real table id canceled on the
  mid-upload cancel path, skip re-seeding flagged tables in hydration, and clear
  the flag once the server import is terminal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(tables): drive import tray by polling derived from server, not SSE

Import progress no longer holds an SSE connection per importing table. The tray
now derives its importing rows live from the table list (React Query), polled
only while an import is in flight; the table detail page keeps its own
cell-state SSE for grid refresh.

- store holds only client-only state now: optimistic uploads, which terminal
  completions to surface this session, canceled ids, menu open — no copied
  importStatus/rowsProcessed.
- useWorkspaceImports is the single source: polls via a data-predicate
  refetchInterval, derives rows, and fires completion toasts on the
  importing -> terminal transition.
- kickoff handlers use startUpload/setUploadPercent/endUpload; the invalidated
  list refetch surfaces the server row and polling takes over.
- removes use-hydrate-import-tray + use-import-progress-tracker (folded in).
- trims over-verbose comments across the import paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): ignore superseded-run import events in the detail SSE cache

applyImport applied every replayed import payload to the detail cache. The SSE
buffer can replay a prior import's terminal event for the same table, stomping a
newer in-flight import's UI. Lock to the active run's importId (and ignore a
replayed terminal before the id is known), matching the guard the header tracker
used to have.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): close sync-import TOCTOU by claiming the atomic import gate

The sync import route checked importStatus from a checkAccess snapshot, then
parsed/validated/wrote seconds later without taking the atomic claim. A
concurrent async kickoff (markTableImporting) could slip into that window and
both writers would run together — for replace mode, two delete+insert passes
leave the table indeterminate.

Claim the same atomic gate (markTableImporting) right before the write and
release it in the finally (before the response returns, so a client refetch
never sees the transient status). A row-level FOR UPDATE was avoided on purpose:
it would invert lock order against the position advisory lock / row-count
trigger and risk a deadlock — markTableImporting is the established gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(multipart): keep abort wired after resolve so a mid-upload disconnect tears down the stream

readMultipart resolves on the file-part header and hands the caller an un-drained
stream, but settle() ran cleanup() and detached the abort listener on that path
too. A client disconnect mid-upload then destroyed nothing — busboy never saw EOF,
the file stream stalled, and the route's `for await` held a request slot until
maxDuration (300s). Re-arm an abort handler scoped to the file stream on resolve,
detached when the stream closes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 21:26:06 -04:00
Theodore LiandClaude Opus 4.8 aed44024d4 fix(tables): surface real error causes on cell-execution failures (diagnostics) (#4868)
* fix(tables): retry transient DB/Redis failures in cell execution and surface error causes

Workflow-group-cell runs intermittently failed on trivial DB reads/writes
under heavy fan-out, stranding cells in `running`. Investigation showed the
PlanetScale and ElastiCache backends were healthy at the time — the failures
are transient connection-level faults that the cell (maxAttempts: 1) had no
tolerance for, and the real cause was never logged (Drizzle wraps it as
"Failed query: ..." and the driver cause lives in error.cause).

Resilience:
- Add retryTransient (lib/table/retry-transient.ts): retries only transient
  infra errors (reuses isRetryableInfrastructureError; adds an ioredis
  command-timeout match) with jittered backoff, then rethrows. Fail-fast for
  everything else.
- Wrap the cell's getTableById/getRowById reads, the terminal write
  (cell-write updateRow — idempotent via the executionId guard), and the
  Redis cascade-lock acquire.

Diagnostics:
- Add describeError (lib/core/errors/retryable-infrastructure.ts): walks the
  .cause chain and always returns the underlying driver cause (code/errno/
  syscall + causeChain), including for unclassified errors like AbortError.
- Log `cause` + a `retryable` flag (and aborted/timedOut in the cell's main
  catch) across the cell + finalization error paths, mirroring the existing
  schedule-execution pattern. Logging-only; no behavior change. This lets the
  next recurrence reveal the real cause and whether the retry applies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): address review feedback on cell retry resilience

- retryTransient: re-check the abort signal after the backoff sleep so a
  cancellation during sleep stops the next attempt (don't run/return work for
  an already-cancelled task).
- isRetryableRedisError: walk the .cause chain (mirroring the infra
  classifier) so wrapped Redis timeouts are recognized; drop "Connection is in
  subscriber mode" — that's a connection-state programming error, not a
  transient drop, and would just fail identically every retry.
- cascade-lock: stop wrapping acquireLock in retryTransient. acquireLock is a
  non-idempotent SET NX, so retrying after a timed-out-but-applied first SET
  returns false (key already ours) and yields a false `contended` that skips
  the cascade. A transient Redis blip here just fails the run before pickup
  (no stranded cell); the dispatcher re-drives it.
- Tests: cause-chain Redis match, subscriber-mode exclusion, abort-during-sleep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): drop out-of-scope abort/timeout fields from cell catch

The main catch logged `aborted`/`timedOut` from `abortSignal`/`timeoutController`,
but those are declared inside the outer try block (the inner try around
executeWorkflow is try/finally, so this catch belongs to the outer try) and are
not in scope in the catch — `next build`'s type-check failed with "Cannot find
name 'abortSignal'". Local incremental `tsc --noEmit` had skipped the file and
falsely passed; the Cursor/Greptile reviewers flagged this correctly.

Removed the two fields. Abort/timeout is still surfaced via `cause:
describeError(err)` (an aborted run shows `name: 'AbortError'` / the timeout
message), so no diagnostic signal is lost.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(tables): drop in-process retry, keep cause diagnostics only

In-process retry is the wrong layer for this path: the cell task is
maxAttempts:1 by design, retrying on a possibly-degraded worker may not help,
and it masks the very transient-failure signal we're trying to capture before
we understand the root cause. Removed retryTransient entirely (file + all
wrapping in cell-write, the cascade reads, and the lock acquire) and kept only
the diagnostic logging.

- Deleted lib/table/retry-transient.ts (+ test); cell-write and the cascade
  reads call getTableById/getRowById/updateRow directly again, fail-fast.
- Kept describeError + `cause`/`retryable` fields across the cell + finalization
  catch blocks; the cell-path `retryable` flag now sources from
  isRetryableInfrastructureError (the canonical classifier) for consistency.

Diagnostics-first: surface the real driver cause on the next recurrence, then
decide the actual fix (e.g. task-level maxAttempts, or addressing the worker-
side cause) from evidence rather than a speculative in-process retry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(schedules): log error cause on scheduled-execution failure paths

The scheduled-job failure paths logged the raw error (.message/stack only) —
its `.cause` (the real driver error behind a Drizzle "Failed query: ..."
wrapper) was never recorded, and the classified-only
`describeRetryableInfrastructureError` returns undefined for unrecognized
errors. A real failed run (same incident window as the cell failures) failed in
`applyScheduleUpdate` with exactly this unrecorded cause.

Added `cause: describeError(error)` (always-on, walks the cause chain) to the
applyScheduleUpdate catch, the early-failure catch, and the unhandled-error
catch — passed as a second arg so the existing message+stack still emit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(errors): move describeError to @sim/utils/errors

`describeError` is a general-purpose error/cause-chain helper — it didn't
belong in `lib/core/errors/retryable-infrastructure.ts` (that module is
specifically about classifying retryable infra errors, and the name read wrong
for a generic diagnostic). Moved it to `@sim/utils/errors` alongside `toError`/
`getErrorMessage`/`getPostgresErrorCode`, with its own cycle-safe cause walk.

- Added describeError + DescribedError + tests to packages/utils/src/errors.ts.
- Reverted the describeError addition from retryable-infrastructure.ts (it keeps
  only isRetryableInfrastructureError / describeRetryableInfrastructureError,
  which are accurately named and still used by the schedule retry path).
- Re-pointed all consumers (cell, logging-session, pause-persistence, schedule)
  to import describeError from @sim/utils/errors. The `retryable` classification
  flag still sources from isRetryableInfrastructureError where used.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:57:06 -04:00
Waleed 5efb47e8c5 fix(storage): percent-encode object key in multipart fallback URL (#4872)
* fix(storage): percent-encode object key in multipart fallback URL

buildObjectFallbackUrl built the object URL from a raw key. Keys with spaces
or reserved characters (and the pre-existing AWS branch) would produce a
structurally invalid location. Encode the key per path segment (preserving
'/' separators) across all branches (AWS, custom path-style, virtual-hosted).

* refactor(storage): clearer per-segment key encoding in fallback URL

* test(storage): cover multipart fallback URL (AWS, R2 virtual-hosted, MinIO path-style, key encoding)
2026-06-03 14:46:56 -07:00
WaleedandClaude Opus 4.8 80d966d336 fix(gitlab): pin pagination cursor to configured host + consolidate isSameOrigin (#4873)
* fix(gitlab): pin pagination cursor to configured host before following it

The repository-tree keyset cursor stores GitLab's verbatim rel="next"
URL and re-fetches it with an Authorization: Bearer header. Assert the
cursor's origin matches the configured apiBase before following it, so a
tampered or corrupted fileNextUrl cannot exfiltrate the access token to
an attacker-controlled host. Fails closed on mismatch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(validation): generalize isSameOrigin and reuse across connectors/tools

Add an optional base argument to the shared isSameOrigin (defaulting to
the app base URL) so callers can pin a URL to any trusted origin. The
GitLab connector's cursor host-check and the tools self-origin check now
consume the shared helper instead of their own URL-parsing.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 14:42:54 -07:00
Waleed 85942a5301 fix(dev): use globalThis for singleton state to prevent HMR memory leaks (#4869)
* fix(dev): use globalThis for singleton state to prevent HMR memory leaks

* fix(dev): apply globalThis guard to rate-limiter storage factory to prevent listener accumulation

* fix(types): resolve McpConnectionManager globalThis undefined type error
2026-06-03 13:58:14 -07:00
Theodore Li 4b84f06108 fix(mothership): run client-routed workflow tools server-side in headless execution (#4870)
* fix(mothership): run client-routed workflow tools server-side in headless execution

Headless Mothership (Mothership block, no browser) could not run workflows.
The run_workflow/run_workflow_until_block/run_block/run_from_block tools are
registered with route 'client', so the executor gate (isSimExecuted) skipped
their registered server handlers and fell through to executeAppTool, throwing
'Tool not found'. Interactive runs delegate these to the browser before reaching
the executor, so only the headless path broke.

Allow a client-routed tool to use its registered server handler when one exists,
which only affects the four run tools (the only client-routed tools, all of which
have server handlers).

* test(mothership): clear handler registry between executor tests

Add clearHandlers() helper and reset the module-level handler registry in
beforeEach so handlers registered in one test do not leak into the next.
2026-06-03 16:58:04 -04:00
WaleedandClaude Opus 4.8 cd66774960 feat(gitlab): sync repository files (code/docs) (#4864)
* feat(gitlab): sync repository files (code/docs) alongside wiki and issues

* fix(gitlab): follow full keyset next-link for repo tree + skip disabled wiki gracefully in all/both

* fix(gitlab): error on bad user branch (tree 404), warn on resolveRef fallback, normalize pathPrefix to directory boundary

* fix(gitlab): preserve slashes in branch ref for file source URLs (GitFlow branches)

* fix(gitlab): never abort sync on repo-tree 404 (empty repo); validate user branch exists at setup instead

* fix(gitlab): validate ref via commits endpoint so tags and commit SHAs are accepted, not just branches

* fix(gitlab): skip repo phase on tree 403 (missing read_repository) so wiki/issues still sync under all

* fix(byok): add Fal icon and repair corrupted Ollama icon path

The Ollama BYOK icon rendered blank because its SVG path had spaces
stripped between arc-command flags (e.g. `a5.05 5.05 0 12.05-.636`),
producing invalid tokens. Replaced with the canonical Ollama path.

Also added a dedicated FalIcon (was falling back to the generic
ImageIcon) and wired it into the BYOK provider list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(icons): repair corrupted Fireworks icon arc command

The leftmost spark of the Fireworks icon never rendered because its
third subpath used a corrupted arc command (`a34.59 34.59 0 17.15 37.65`)
with collapsed flags, yielding an invalid sweep-flag of 7 that aborts
the path parse. Replaced with the canonical lobehub Fireworks source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 12:23:03 -07:00
WaleedandClaude Opus 4.8 b329c36b1a fix(auth): link SSO sign-in to existing same-email accounts (#4866)
* fix(auth): link SSO sign-in to existing same-email accounts

SSO sign-ins failed with "account not linked" (then a cascading "Invalid
callbackURL") when an account with the same email already existed. Better
Auth's `@better-auth/sso` plugin hardcodes the provisioned user's
`emailVerified: options?.trustEmailVerified ? <claim> : false`, so with the
option unset every SSO login arrived unverified and tripped the account
linking gate `(!isTrustedProvider && !userInfo.emailVerified)` whenever the
provider was not in `accountLinking.trustedProviders`.

- Set `trustEmailVerified: true` on the SSO plugin so the IdP's verified-email
  claim is honored (Okta, Entra ID, Google Workspace, Auth0 all assert it).
- Trust the operator's configured provider for linking: merge
  `SSO_PROVIDER_ID` (when present in the app env) plus a new
  `SSO_TRUSTED_PROVIDER_IDS` list into `trustedProviders`. Empty/unset =>
  no-op, so existing deployments are unchanged.
- Invite callback URL: return a clean `/invite/<id>` (token already persists
  in sessionStorage) so an appended `?error=` cannot produce a malformed URL.
- Document `SSO_TRUSTED_PROVIDER_IDS` in SSO docs, Helm values, and schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(auth): address review — guard trusted SSO providers, revert invite callback

- Only compute additionalTrustedSsoProviders when SSO_ENABLED, so
  trustedProviders is exactly unchanged for non-SSO deployments.
- Revert the invite getCallbackUrl change: keep the token in the callback URL
  (with sessionStorage/searchParams fallback) so the token survives when
  sessionStorage is unavailable. The account-linking fix removes the
  "account not linked" error that caused the malformed callback URL, so the
  callback cleanup is unnecessary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(auth): guard trusted SSO providers with isSsoEnabled (isTruthy)

env.SSO_ENABLED can be the string "false" (t3-env returns strings for
booleans), which is truthy in JS. Use the canonical isSsoEnabled flag
(isTruthy(env.SSO_ENABLED)) so SSO_ENABLED="false"/"0" correctly yields an
empty trusted-provider list, matching how SSO is gated elsewhere.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 12:00:04 -07:00
Waleed b98fbfebef docs(slack): remove archival reference from Download files per Slack Marketplace guidelines (#4867) 2026-06-03 11:46:13 -07:00
Waleed 648a5a117d feat(storage): support S3-compatible endpoints (R2, MinIO, B2) for file storage (#4865)
* feat(storage): support S3-compatible endpoints (R2, MinIO, B2) for file storage

Add S3_ENDPOINT and S3_FORCE_PATH_STYLE env vars, wired into the shared upload
S3 client so Cloudflare R2, MinIO, Backblaze B2, and other S3-compatible stores
work for self-hosted file storage. The endpoint is trusted operator config (no
SSRF/HTTPS gate). Makes the multipart Location fallback endpoint-aware, extends
the S3 client unit tests, and documents the new vars in Helm values, .env.example,
and the English self-hosting docs (incl. browser-reachability + CORS guidance).

* docs(storage): add RustFS as an S3-compatible provider example

* fix(storage): address review feedback and fix env mock for CI

- Add envBoolean to the shared env test mock (createEnvMock) so config.ts's
  forcePathStyle coercion resolves — fixes failing knowledge/utils.test.ts
- Declare S3_FORCE_PATH_STYLE as z.string() (every other env var's pattern);
  it's coerced via envBoolean at the consumption site, avoiding a boolean
  type that never matches the string process.env value
- Log path-style from S3_CONFIG.forcePathStyle (envBoolean) instead of a
  separate isTruthy call, so the startup log can't disagree with the client
- Make buildObjectFallbackUrl honor forcePathStyle: virtual-hosted-style URL
  (bucket as subdomain) for R2, path-style only when forcePathStyle is set

* docs(storage): add backlinks to S3-compatible providers (R2, MinIO, Ceph, B2, RustFS) and backends
2026-06-03 11:37:29 -07:00
Vikhyath Mondreti 1933fc4f11 fix(env): schema treatment of empty string (#4862) 2026-06-03 10:21:47 -07:00
Theodore LiandClaude Opus 4.8 928bd911f3 fix(background): recategorize user/recovery failures as errors, not trigger faults (#4860)
* fix(webhook): don't fault trigger run on user/workflow execution errors

Webhook-triggered executions re-threw every error, so trigger.dev marked
the run failed and fired #eng-errors alerts. The vast majority of these are
user-caused workflow failures (missing required fields, invalid field
references, bad URLs, provider 4xx, expired models, low credit) that are
already recorded in the execution logs.

Distinguish fault vs error in executeWebhookJobInternal: when the failure
was finalized by core (the workflow ran and its failure is logged), complete
the run with { success: false } instead of throwing. Errors that were not
finalized came from the webhook pipeline itself and still re-throw to fault
the run. Await waitForPostExecution first so the finalized flag is reliable.

The error is still recorded on the run's OTel span via recordException (no
ERROR status, so the run isn't faulted) and remains in the execution logs,
so these stay investigable in Tempo/Loki without false alerts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(schedule): don't fault trigger run on error-recovery failures

The schedule task already treats workflow-execution failures as recorded
errors rather than trigger faults, but the outermost catch's own recovery
code (the infra-retry and releaseClaim calls) was unguarded. A secondary DB
blip while releasing the claim re-threw and escaped run(), faulting the
trigger.dev run and firing an alert — a double-fault during cleanup.

Wrap the recovery path in a try/catch: log and record the exception on the
span without re-throwing. The claim expires on its TTL and the next tick
re-claims the schedule, so swallowing the cleanup failure is safe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(webhook): assert waitForPostExecution runs on the non-finalized path

Guards the race fix on the infra-error path so a future refactor can't
silently drop the await. Addresses Greptile review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 03:08:42 -04:00
Waleed ba2e4cca74 fix(fathom): skip getDocument when header cache is missing instead of emitting a degraded, un-refreshable record (#4859) 2026-06-02 20:11:15 -07:00
Waleed b58cd1f6b1 fix(slack): request reactions:read in OAuth URL, drop im:history (#4856)
* fix(slack): request reactions:read in OAuth URL, drop im:history

* chore(slack): update read-messages missing-scope message to drop im:history
2026-06-02 19:31:50 -07:00
Waleed 1cb5a1653b fix(auth): show "account already exists" on duplicate email signup (#4855)
* fix(auth): show "account already exists" on duplicate email signup

* fix(auth): use exact path match for duplicate-email signup check
2026-06-02 18:54:53 -07:00
Waleed 12db7da938 feat(connectors): add 11 knowledge base connectors (#4849)
* feat(connectors): add 11 knowledge base connectors (Gong, Grain, Fathom, Granola, incident.io, Rootly, Ashby, Greenhouse, DocuSign, Monday, GitLab)

* fix(granola): restore correct calendar_event field names (event_title/organiser/calendar_event_id/scheduled_start_time/scheduled_end_time/invitees)

* fix(connectors): correct Grain id field/include flags and incident.io timestamp value shape (verified vs raw API specs)

* feat(connectors): add scoping filters for Gong (host users), Fathom (meeting type/domain), Granola (folder/created-after)

* feat(connectors): add verified scoping filters (Grain, Rootly, Ashby, Greenhouse, DocuSign, GitLab) + clarify Monday board scope

* fix(connectors): docusign sandbox source URL, greenhouse scorecard cap, gong overlap; add incident.io status/mode filters

* chore(connectors): remove non-TSDoc inline comments

* fix(fathom): apply 14-day overlap to incremental created_after so late transcripts are recaptured

* fix(fathom): cache sourceUrl in header so getDocument preserves it; return concrete contentHash (drop omit/cast)

* fix(connectors): grain check transcript before formatting; gong cache date window across cursor pages

* fix(connectors): fathom skip meetings with no transcript/summary; docusign cache from_date across cursor pages

* fix(fathom): return metadata from getDocument (carried via header cache) for tag consistency

* fix(connectors): grain cache filter window across pages, greenhouse guard NaN page, rootly skip empty incidents

* feat(connectors): final audit — add verified scoping filters (grain team/type, granola+greenhouse createdBefore, gitlab milestone, rootly service/team/env), fix incident.io paused category + rootly severity-slug copy
2026-06-02 16:57:30 -07:00
Vikhyath Mondreti f56a0e4676 fix(schedules): count usage lim error schedule as failed run (#4853)
* fix(schedules): count usage lim error schedule as failed run

* remove backoff logic
2026-06-02 14:06:02 -07:00
Waleed e2c2d9add3 feat(apify): add run task, get dataset items, and get run tools (#4851)
* feat(apify): add run task, get dataset items, and get run tools

* fix(apify): guard undefined dataset id and forward explicit offset=0
2026-06-02 12:54:51 -07:00
Theodore Li 34ee7f918c fix(tables): count dispatcher pre-stamps in "X running" during active dispatch (#4850)
The live SSE path counts pending pre-stamps (isExecInFlight) but
countRunningCells excluded them, so each per-window refetch reset the badge
from ~20 to 0 (visible flicker now that the control stays shown via
hasActiveDispatch). Include unclaimed pre-stamps in byRowId when a dispatch is
active; keep excluding them only in the no-dispatch fallback (orphan case).
2026-06-02 15:37:22 -04:00
Vikhyath Mondreti 5d9752d563 fix(mothership): connect integrations from chat without state_mismatch (#4848)
* fix(oauth):  skipStateCookieCheck flag change

* browser initated solution

* fix draft timing issue
2026-06-02 12:22:38 -07:00
Waleed 3f3efc98c3 chore(auth): remove deprecated OAuth MCP provider plugin and backing tables (#4847) 2026-06-02 10:46:20 -07:00
Vikhyath Mondreti cf2477954f fix(hubspot): remove unused scopes (#4846) 2026-06-01 21:36:29 -07:00
Waleed a7984bff9e feat(landing): add AI-generated content disclaimer to integration landing page (#4845)
Adds an optional aiDisclaimer field to the integration landing content (types + data), rendered as an independent 'AI-generated content' section and baked into integrations.json via docs-gen. Populates Slack to satisfy Slack's AI-components guideline (disclaimer on the landing page).
2026-06-01 20:18:22 -07:00
Theodore Li 590e502a38 fix(mothership): scope mothership block tool permissions to the executing user (#4843) 2026-06-01 22:08:04 -04:00
Vikhyath Mondreti c786adac2c chore(access): helper cleanup (#4842) 2026-06-01 18:12:21 -07:00
Theodore LiandClaude Opus 4.8 3ccb3a392a fix(tables): reliable stop-all, accurate "X running", and rate/usage gating for cell runs (#4838)
* fix(tables): reliable stop-all, accurate "X running", and rate/usage gating for cell runs

Stop-all:
- Make the cancellation guard status-based (not executionId-scoped) so a
  `cancelled` tombstone stamped while a cell is still a dispatcher pre-stamp
  (null executionId) keeps the cell dead — fixes function-execute cells that
  resurrected after Stop all. Consolidated into shared isExecCancelled /
  isExecCancelledAfter predicates in deps.ts, reused by the in-memory guard,
  the SQL guard, the dispatcher tombstone filter, the worker, and resume.
- Add an explicit pre-execution cancellation read so a cell that dequeues
  after Stop all (e.g. from the trigger.dev queue) never runs.
- Resume worker aborts a cancelled paused/awaiting cell before resuming;
  cancelWorkflowGroupRuns marks paused executions cancelling.

"X running":
- Emit a dispatch SSE at dispatch start so auto-fired/capped runs surface
  immediately; show the control whenever a dispatch is active.

Checkbox dependency:
- Treat boolean `false` as an unmet dependency so unchecking never reruns
  dependents — only checking does. deriveExecClearsForDataPatch no longer
  re-arms a downstream group whose deps are unmet after the patch.

Rate / usage gating:
- Route table cell execution through preprocessExecution (billing actor =
  workspace billed account, usage limit, per-plan timeout), keeping draft.
- Rate limit: pace & retry per cell (async counter) so rows aren't skipped.
- Usage limit: halt the dispatch without marking cells and emit a
  usageLimitReached event; the client shows an Upgrade prompt that routes to
  subscription settings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tables): dedupe usage-limit event + release rate-limited cells on cancel

Addresses PR review:
- Usage limit: only the cell that transitions the dispatch active→complete
  (via completeDispatchIfActive) emits usageLimitReached, so concurrent cells
  don't fire up to 20 identical "upgrade" toasts.
- Rate-limit retry: re-check the cancelled tombstone after each sleep so a
  Stop All mid-wait releases the concurrency slot promptly (signal never fires
  on the trigger.dev backend).

* fix(tables): jitter rate-limit retry backoff to avoid thundering herd

Passing the bucket's shared resetAt as retryAfterMs made backoffWithJitter
return a fixed clamped value (no jitter, attempt ignored), so all concurrent
cells retried in lockstep. Pass null to get jittered exponential backoff.

* fix(tables): unstick cells + resync counter on usage-limit halt

Addresses PR review:
- Clear each blocked cell's pre-stamp on a 402 so it reverts to un-run instead
  of being stuck "Queued" (no error/cancelled badge); covers auto-fire cells
  with no owning dispatch.
- Client re-syncs run-state counts and refetches rows on usageLimitReached so
  the stale "X running" / Stop-all control clears and queued cells drop.
- Make usageLimitReached.dispatchId optional; client only touches the dispatch
  overlay when present.

* fix(tables): don't emit stale dispatching event after a mid-window halt

If a cell halts the dispatch mid-window (usage limit), re-read the dispatch
status after the batch and bail instead of emitting a per-window 'dispatching'
event that would arrive after the client dropped the dispatch and re-add it
(flickering 'X running' back).

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 20:43:32 -04:00
Vikhyath Mondreti ed19b0bcde fix(misc): keep block-tool params selected across store replace, perms parity for delete (#4840)
* fix(tool-input): keep block-tool params selected across store replace

* fix tests and extract kb ownership helper
2026-06-01 17:40:26 -07:00
Theodore Li fc7e35edc0 fix(tables): enforce plan limits in mothership user_table tool (#4832)
* fix(tables): enforce plan limits in mothership user_table tool

* improvement(tables): truncate over-limit CSV imports to the plan cap instead of rejecting

* fix(tables): log rollback failures and surface a clear reason on failed CSV import
2026-06-01 20:11:59 -04:00
Theodore Li 8aa74a2d8d feat(tables): add PostHog events for table-workflow run/stop gestures (#4839)
* feat(tables): add PostHog events for table-workflow run/stop gestures

* fix(tables): pass run source from action-bar play/refresh dispatchers
2026-06-01 19:51:47 -04:00
Waleed a7b0bd311d fix(deps): upgrade vitest to ^4.1.0 to patch critical Vitest UI advisory (GHSA-5xrq-8626-4rwp) (#4837)
* fix(deps): upgrade vitest to ^4.1.0 to patch critical Vitest UI advisory (GHSA-5xrq-8626-4rwp)

- Bump vitest and @vitest/coverage-v8 to ^4.1.0 across all workspaces (only patched release for the critical 'Vitest UI server arbitrary file read/execute' advisory; no 3.x backport exists)
- Widen @sim/testing peer range to ^3.0.0 || ^4.0.0
- Migrate constructor mocks to class expressions: vitest 4 uses Reflect.construct for mocks invoked with new, and arrow/function implementations are not constructable (function expressions also get reverted to arrows by biome's useArrowFunction)
- Remove deprecated test.poolOptions from apps/sim/vitest.config.ts (options are now top-level in vitest 4)

* fix(deps): exclude vulnerable vitest 4.0.x from @sim/testing peer range

Tighten the v4 arm of the peer range to >=4.1.0 <5.0.0 so the peer
requirement cannot be satisfied by the unpatched 4.0.x builds that
GHSA-5xrq-8626-4rwp affects.

* fix(testing): make vitest 4 constructor mocks type-check cleanly

- logging-session & mcp-oauth mocks: a class passed to mockImplementation has
  a construct signature that isn't assignable to its (...args) => any parameter,
  failing tsc. Use named function declarations instead (constructable via
  Reflect.construct, assignable to mockImplementation, and not rewritten to
  arrows by biome's useArrowFunction).
- database.mock.ts: vitest 4's generic vi.fn typings no longer break the
  self-referential cycle on the transaction callback's tx param; loosen tx and
  annotate the callback's return type to resolve the implicit-any errors.

* test(isolated-vm): de-flake queue-capacity scheduler tests

The 'queue is full' and 'per-owner queued limit' tests relied on
'await sleep(1)' to assume the first request had reached the queue before
submitting the overflow request. The first request only enqueues after an
async spawn-failure chain (acquireWorker -> spawn exit -> resolve null ->
enqueue), which isn't guaranteed within 1ms under CI load — the overflow
request then found an empty queue and hit the 200ms queue-wait timeout
instead of the capacity rejection.

Replace the wall-clock barrier with a deterministic, event-driven one: hold
the single global concurrency slot (IVM_MAX_CONCURRENT=1) with an active
worker and await an explicit 'dispatched' signal (fired when the worker
receives its execute message, after the scheduler counts it active). The
follow-up requests then deterministically hit the synchronous enqueue path.
Also drops the queue-wait timeout from 200ms to 50ms, so the tests run faster.
2026-06-01 16:11:35 -07:00
Vikhyath Mondreti 9bed841173 improvement(kbs): ownership bindings (#4833)
* improvement(kbs): ownership association

* address comments

* more comments
2026-06-01 15:46:41 -07:00
Theodore Li 2e0f506699 fix(table): preserve workflow groups on CSV column-add and dispatch after tx commit (#4503)
Two bugs in the CSV-import path:

- addTableColumnsWithTx rebuilt the schema with only `columns`, dropping
  `workflowGroups` (and any other top-level schema fields). Importing CSV
  into a table that has workflow groups erased the group config. Spread
  `table.schema` first so siblings survive.

- batchInsertRowsWithTx fired fireTableTrigger and scheduleRunsForRows from
  inside the caller's transaction. Both read through the global db
  connection, so they could run before the inserts committed and see no
  rows. Extracted the dispatch into dispatchAfterBatchInsert; non-tx
  wrapper fires it after `db.transaction(...)` resolves, and the CSV
  import route does the same after its tx.
2026-06-01 18:22:17 -04:00
Waleed e5a46d7959 feat(linq): add Linq iMessage/SMS/RCS integration (34 tools, block, attachment upload) (#4831) 2026-06-01 13:44:33 -07:00
WaleedandClaude Opus 4.8 403a02c3af feat(providers): add Together AI, Baseten, and Ollama Cloud model providers (#4830)
* feat(providers): add Together AI, Baseten, and Ollama Cloud model providers

* fix(providers): guard Ollama streaming fast-path with hasActiveTools

Match Together/Baseten/Fireworks: when tools are supplied but all are
filtered out (usageControl 'none'), take the single streaming call instead
of an extra non-streaming round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(providers): filter non-chat model types from Together model list

* refactor(providers): dedupe Ollama Cloud upstream schema

ollamaCloudUpstreamResponseSchema was byte-for-byte identical to
ollamaUpstreamResponseSchema (both /api/tags endpoints return the same
{ models: [{ name }] } shape). Drop the duplicate and reuse the shared schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 12:17:29 -07:00
WaleedandClaude Opus 4.8 3a2ebd12b9 improvement(copilot): stop persisting tool-call result outputs in transcripts (#4829)
Opening a Mothership task could take many seconds because a single persisted
assistant message in copilot_messages.content can reach hundreds of MB, almost
entirely inside contentBlocks[].toolCall.result.output (e.g. a get_workflow_logs
or run_workflow result). The DB query is ~2ms; the cost is detoasting that
payload, shipping it to the browser, and parsing it.

These outputs are dead weight on the Sim side: they are never rendered (the
thread shows only tool name/title/status) and never replayed to the model (the
upstream copilot service owns conversation memory). So drop result.output before
it is persisted, keeping result.success/error plus the tool metadata.

- add stripToolResultOutput() in persisted-message.ts
- apply it in messages-store toRow (covers every write path) and in
  loadCopilotChatMessages (existing rows render fast on read)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:20:07 -07:00
Waleed 919fa5274b feat(tables): expand filter operators (not-contains, starts/ends-with, not-in, empty) (#4827)
Add does-not-contain ($ncontains), starts-with ($startsWith), ends-with
($endsWith), not-in-array ($nin, previously executed server-side but unexposed
in the UI), and is-empty/is-not-empty ($empty) filter operators end-to-end —
SQL builder, condition types, query-builder converters/constants, the filter
UI, the Table tools/block descriptions, and docs.

Also fix correctness bugs in the filter builder surfaced by the wider operator
set:
- Same-column AND rules (e.g. age > 18 AND age < 65, or name startsWith 'A'
  AND name endsWith 'Z') silently overwrote each other because the AND group
  was keyed by column name. They now merge into one operator object, which
  also makes Filter -> rules -> Filter round-trip losslessly for multi-operator
  columns.
- $nin values were not split into an array like $in, and textual-match values
  like "123" were numeric-coerced (breaking the ILIKE path).
- A non-boolean $empty operand from the raw API silently inverted the check; it
  now coerces 'true'/'false' strings and otherwise returns a 400.
2026-05-31 22:49:15 -07:00
Waleed b399ee0fe2 improvement(copilot): make copilot_messages the sole transcript store, remove JSONB dual-write (#4826)
Stop writing/reading the legacy copilot_chats.messages JSONB column now that
reads are cut over to copilot_messages. Make appendCopilotChatMessages the
primary write (throws on failure instead of swallowing), repoint peripheral
readers (workspace VFS, chat cleanup, data drains, fork, superuser import) to
copilot_messages, and persist the assistant turn inside finalizeAssistantTurn's
transaction so it commits atomically with the stream-marker clear. The column
itself is dropped in a follow-up migration after this bakes.
2026-05-31 11:22:02 -07:00
Waleed e8f648547c fix(sso): re-check domain conflict before write and reject IP-address domains (#4825) 2026-05-30 21:01:47 -07:00
Waleed 5fa84160c1 fix(selectors): fetch all pages for paginated dropdown list routes (#4823)
* fix(selectors): fetch all pages for paginated dropdown list routes

Dropdown selectors fetched only the first page of paginated provider
APIs, silently hiding results past page one. Add bounded server-side
draining to the list routes across Microsoft Graph, Google, Notion,
Atlassian, Linear, AWS CloudWatch, and offset/token REST APIs, plus a
shared client-side drain cap in the selector hook. Response shapes,
stored values, and tool execution are unchanged; CloudWatch list tools
still honor a caller-supplied limit. Also fixes the Word file picker
that was searching for .xlsx files.

* fix(selectors): harden JSM and Monday pagination draining

- JSM service-desk/request-type drains advance `start` by the actual row
  count returned (not the fixed page size) and stop on an empty page, so a
  short non-final page can't skip items.
- Monday boards drain now checks `response.ok` per page, surfacing a
  mid-drain HTTP failure instead of treating it as an empty final page and
  returning a partial 200.

* docs(selectors): clarify JSM drain advances start by actual row count

The offset-advancement fix (advance `start` by the rows returned, not the
fixed page size) landed in 7b19788a8; update the TSDoc to match so it no
longer reads as advancing by `limit`.

* fix(selectors): drain fetchPage in direct fetchList callers

Making `fetchList` optional left three direct callers (outside the
useSelectorOptions hook) calling it unguarded, which broke the build's
type check. Route them through a shared `loadAllSelectorOptions` helper
that uses `fetchList` when present and otherwise drains `fetchPage`.
This also prevents a regression: `confluence.spaces` / `knowledge.documents`
now paginate via `fetchPage` only, and these callers (search/replace,
value resolution) would otherwise have silently returned no options.

* chore(selectors): rename MAX_PAGE_PAGES to MAX_NOTION_PAGES for readability
2026-05-30 20:55:16 -07:00
Waleed f6685cf1b1 feat(google-sheets): add row filtering to read with numeric operators (#4822)
* feat(google-sheets): add row filtering to read with numeric operators

Adds client-side row filtering to the Google Sheets read (v2) operation.
Filter the returned rows by a header column using text operators
(contains, not_contains, exact, not_equals, starts_with, ends_with) and
numeric/ordering operators (gt, gte, lt, lte). Filtering lives in a pure,
unit-tested helper (filterSheetRows) and runs over the fetched read range;
an optional `filter` output reports whether the column was found and how
many rows matched.

Also hardens the surrounding tools:
- trim spreadsheetId in write/update/append URL builders (matches read)
- URL-encode the v1 read default range
- expose valueInputOption for the update operation in the block

Backwards compatible: with no filter requested, read output is byte-
identical and the `filter` field is omitted. The filterMatchType union is
widened additively (4 -> 10 values).

* fix(google-sheets): correct filter metadata for missing column and header-only sheets

- matchedRows is now 0 (not totalRows) when the filter column is not found,
  so it no longer contradicts applied=false / columnFound=false
- columnFound now reflects an actual header lookup for empty/header-only
  sheets instead of being hardcoded true
- add tests covering header-only and empty sheets with present/absent columns
2026-05-30 20:09:53 -07:00
Waleed 6c476cf592 fix(files): don't reject external URLs containing '..' in file parse validation (#4821)
* fix(files): don't reject external URLs containing '..' in file parse validation

The file block's file_fetch operation rejected any external URL whose path
contained '..' (e.g. Slack files-pri slugs with a literal '...') with
'Access denied: path traversal detected'. Traversal checks only apply to
local paths — external http(s) URLs are fetched with SSRF protection
downstream and are never resolved against the filesystem, so they now
short-circuit as valid. Internal /api/files/serve/ URLs keep full traversal
protection.

* test(files): fix external-URL assertion to handle undefined error

* test(files): assert success explicitly in external-URL traversal test

* fix(files): keep traversal protection for https URLs matching internal serve paths
2026-05-30 19:35:45 -07:00
Theodore Li 97f7fe95dc improvement(enrichments): limit company-info to fields both providers return (#4817)
Hunter's company dataset returns null industry/foundedYear for many large companies (verified against the live API for Microsoft, Amazon, Google), so under the first-non-empty-wins cascade those columns appeared inconsistently across rows. Limit company-info outputs to employee count and description — the fields Hunter and PDL both reliably return — so every row is consistent. employeeCount is a string so Hunter's range bucket and PDL's exact count share the column.
2026-05-30 22:24:41 -04:00
Waleed 1ae1afbe23 fix(search-replace): don't auto-navigate when content edits invalidate the active match (#4819)
* fix(search-replace): don't auto-navigate when content edits invalidate the active match

* fix(search-replace): clear afterReplaceIndexRef on apply failure and zero matches

* fix(search-replace): remove duplicate setActiveSearchTarget(null) on close

* fix(search-replace): move afterReplaceIndexRef write inside handleApply past the guard

* fix(search-replace): auto-navigate when hydration resolves with no prior active match

* chore(search-replace): remove inline comments

* fix(search-replace): revert !activeMatchId guard that caused immediate re-navigation after deselect
2026-05-30 18:58:37 -07:00
WaleedandClaude Opus 4.8 20a8d85e15 improvement(integrations): validate and expand devin, cursor, and greptile (#4820)
* improvement(integrations): validate and expand devin, cursor, and greptile

- devin: fix missing org_id path segment on all session endpoints, add 7 session sub-resource tools (list messages/attachments, get/append/replace tags, archive, terminate), pagination, and is_archived output
- cursor: add get_api_key_info, list_models, list_repositories tools
- greptile: align block and docs
- normalize array outputs to default [] and tighten types

* refactor(cursor): simplify list_repositories v2 array normalization

Collapse the redundant `?? []` + `Array.isArray` double-guard into a
single Array.isArray check, per PR review feedback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(devin): scope session-tag mapping to tag ops and normalize array tag inputs

- Only map sessionTags into the tools tags param for append/replace operations,
  preventing stale sessionTags state from clobbering create_session tags
- Fall back to a wired tags value when sessionTags is empty for tag operations
- Normalize tag inputs (string or wired string[]) via normalizeTags so array
  values from other blocks no longer throw on .split

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(cursor): restore base64 file data in legacy download_artifact metadata

The legacy CursorBlock exposes only content + metadata (no v2 file
output), so metadata.data was the only way legacy-block workflows could
access downloaded artifact bytes. Restore the base64 data field and
document it in the outputs/type instead of dropping it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(devin): coerce terminateArchive to archive flag for boolean-wired input

* docs(integrations): regenerate tool docs for new devin and cursor operations

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 18:53:29 -07:00
Waleed 911586ad5a fix(security): block private/reserved IPs for hosted 1Password Connect SSRF (#4818)
* fix(security): block private/reserved IPs for hosted 1Password Connect SSRF

* test(security): use real isPrivateOrReservedIP and cover IPv6 edge cases
2026-05-30 18:13:55 -07:00
WaleedandClaude Opus 4.8 a8f86c0c83 fix(security): harden SSO domain registration, webhook path isolation, and CSV export (#4813)
* fix(security): harden KB file access, SSO domain registration, webhook path isolation, env secrets, and CSV export

* fix(sso): scope domain conflict query with indexed lower(domain) filter

Address PR review: avoid a full-table scan on every SSO provider
registration by filtering candidate rows in SQL with
lower(domain) = <normalized>, keeping the in-memory ownership check.
Also tighten the normalizeSSODomain TSDoc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: condense env route security comments

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* icons update

* chore(security): tighten inline comments in CSV export and KB file authorization

Condense verbose comment blocks to concise TSDoc/single-line form; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): validate internal serve origin in KB file authorization

Replace the bypassable isInternalFileUrl substring check in resolveInternalKbKey
with an origin allow-list (base URL, internal API base URL, TRUSTED_ORIGINS).
A crafted external host whose path is /api/files/serve/<victim-key> no longer
resolves to the victim key. Relative same-origin URLs are unaffected.

* style(sso): use idiomatic sql lower() comparison for domain conflict query

Match the repo's prevailing `sql`lower(col) = value`` idiom for the
case-insensitive SSO domain conflict lookup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): align workspace env admin gate with hasWorkspaceAdminAccess

Use the same admin check the secrets UI uses (owner, admin permission, or
org-admin) so owners and org-admins are not wrongly denied their own decrypted
workspace secrets, while read-only members remain restricted to names only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(sso): rely on lower(domain) match for conflict detection, drop dead in-memory recheck

Address PR review: the SQL `lower(domain) = <normalized>` predicate already
excludes rows that the in-memory `normalizeSSODomain(...) === domain` recheck
claimed to catch, making that recheck dead/misleading code. Match on the
canonical lower-cased domain and filter purely by ownership. Malformed legacy
values (wildcards, schemes, ports) never match an email domain at sign-in, so
excluding them is not a gap. Test DB mock now applies the lower() predicate so
the casing-variant case is genuinely exercised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): scope webhook deploy path conflict to active webhooks

findConflictingWebhookPathOwner omitted the isActive filter that the
runtime dispatcher (findAllWebhooksForPath) applies, so an inactive but
non-archived webhook from another workflow (e.g. after undeploy or
failure auto-disable) would permanently block any new deployment on that
path even though it never receives deliveries. Align the guard with the
runtime isActive + archivedAt filter; the earliest-owner runtime check
remains the authoritative cross-tenant protection. Also trims verbose
TSDoc on the webhook path-isolation helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): exclude archived workflows from webhook deploy path conflict

findConflictingWebhookPathOwner now joins workflow and filters
isNull(workflow.archivedAt), matching the runtime dispatcher
(findAllWebhooksForPath). A webhook on an archived workflow can never
receive deliveries at runtime, so it must not block legitimate path reuse
with a 409.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(security): anchor KB file ownership to earliest document in any state

A KB file's owner is now the earliest document referencing its key regardless of
state (active/archived/deleted/excluded); access is granted only when that owning
document is still active. Closes the residual where an attacker could plant an
active document to claim a file whose original document was archived or deleted.

* updated greptile icon

* revert(security): drop KB file authorization changes

Reverts the knowledge-base file-access work (origin-pinning / owner-pinning /
origin allow-list in verifyKBFileAccess) and its test. The other hardening fixes
(SSO domain registration, webhook path isolation, workspace env secrets, CSV
export) are unchanged. apps/sim/app/api/files/authorization.ts is restored to its
origin/staging baseline.

* fix(sso): treat caller's own user-scoped provider as owned during conflict check

Self-hosters often register SSO user-scoped via the CLI script (no
SSO_ORGANIZATION_ID). If they later enable organizations and reconfigure the
same domain org-scoped through the UI, the conflict check previously treated
their own user-scoped row as another tenant's and returned a misleading 409.
Recognize the caller's own user-scoped provider as owned so that migration is
allowed, while still blocking another user's or another org's domain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* revert(security): remove workspace-env admin gate

Defer to a credential-based access model (separate change). Restores
GET /api/workspaces/[id]/environment to main behavior and removes the test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(security): consolidate webhook path-collision check into one helper

Extract findConflictingWebhookPathOwner to lib/webhooks/utils.server.ts as
the single source of truth for cross-tenant path-collision detection, used by
both webhook creation paths (deploy sync and the manual /api/webhooks route).

This also repairs two latent issues in the manual route's previous inline
check, which queried with limit(1) and only webhook.archivedAt:
- limit(1) inspected one arbitrary row, so a same-workflow row could mask a
  foreign collision (false negative). The shared helper scans all matching
  rows.
- It omitted isActive/workflow.archivedAt, so inactive or archived-workflow
  webhooks (which never receive deliveries) permanently blocked path reuse.
  The helper mirrors the runtime dispatcher's filter.

Same-workflow webhook reuse for upsert is now a separate, explicit lookup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 17:35:48 -07:00
Theodore Li 1d4a2772ab fix(wait): resume live/draft async waits and preserve cell context on chained waits (#4814)
* Make workflow description nullable

* fix(wait): resume live/draft async waits and preserve cell context on chained waits
2026-05-30 19:30:12 -04:00