fix: security release 2026-07-06#4199
Conversation
🦋 Changeset detectedLatest commit: 49c951e The changes in this PR will be included in the next version bump. This PR includes changesets to release 28 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughThis PR bundles a broad set of security hardening changes across the webapp: SSRF-safe webhook validation and fetch wrapper for alert delivery, stricter URL schemas for platform notifications, tenant/environment scoping for batch, trigger, replay, and idempotency lookups, per-table query authorization via a new TRQL table detector, rate limiting for the ai-title endpoint, prompt override source normalization, secret redaction in logs (environments, JWT context, Slack integrations, worker headers), same-origin/CSRF checks for impersonation and batch-resume redirects, an email allowlist pattern matcher, an extracted invite role ladder utility, worker group project access checks, Google email verification enforcement, a refactored/testable MFA rate limiter, an extracted removeTeamMember module, and a CLI fix propagating the MCP server's --dev-only flag. Extensive new unit and integration tests accompany each change. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.server-changes/account-access-control-hardening.md (1)
6-7: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRewrite the changelog description for users, avoiding internal terms.
The description uses "RBAC" and "impersonation" — internal/infrastructure terms that users won't understand. As per coding guidelines,
.server-changes/notes should be written for users, leading with what changed in one plain sentence and avoiding internal tool or infrastructure names.✍️ Proposed rewrite
-Harden account and access-control handling across auth, RBAC, org membership, and impersonation +Strengthened security for account access, organization membership, and administrative controlsSource: Coding guidelines
🧹 Nitpick comments (4)
.changeset/mcp-thread-dev-only-flag.md (1)
5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRewrite this changeset in user-facing language.
This reads like an internal implementation note. Please lead with the user-visible behavior and avoid tool-internal phrasing.
Source: Coding guidelines
apps/webapp/test/safeWebhookFetch.test.ts (1)
11-13: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAdd a positive test for
http://scheme.The test name says "accepts a public http(s) URL" but only asserts
https://. The rejection tests at lines 21–61 usehttp://but expect rejection based on host, not scheme — so they don't provehttp://is accepted. Adding one assertion closes this gap and matches the test's stated scope.✨ Suggested addition
it("accepts a public http(s) URL given as an IP literal (no DNS needed)", async () => { await expect(assertSafeWebhookUrl("https://93.184.216.34/hook")).resolves.toBeInstanceOf(URL); + await expect(assertSafeWebhookUrl("http://93.184.216.34/hook")).resolves.toBeInstanceOf(URL); });apps/webapp/test/detectQueryTables.test.ts (1)
1-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for top-level UNION/INTERSECT (
select_set_query)The code handles
select_set_queryat the top level (detectQueryTables.tsLines 109-110), but no test covers it. A UNION reading from different tables in each branch (e.g.,SELECT * FROM runs UNION SELECT * FROM tasks) is an important authorization case — ifvisitSelectSetor the top-level dispatch breaks, every branch table silently goes undetected.♻️ Suggested test addition
it("returns null for an unparseable query (caller denies by default)", () => { expect(detectQueryTables("definitely not a valid query !!!", allowed)).toBeNull(); }); + + it("detects tables across UNION branches", () => { + expect( + sorted(detectQueryTables("SELECT * FROM runs UNION SELECT * FROM tasks", allowed)) + ).toEqual(["runs", "tasks"]); + }); });apps/webapp/app/v3/services/dependentAttemptScope.ts (1)
9-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider consolidating with
attemptInEnvironmentWhere.
dependentAttemptWhereis functionally identical toattemptInEnvironmentWhereintriggerV1Scoping.ts— both return{ friendlyId, taskRun: { runtimeEnvironmentId: environmentId } }. If the schema for attempt lookups changes, both must be updated in sync. Consider having one re-export or delegate to the other, unless the separate domain naming is intentionally preferred for readability.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 859bda45-19b9-4073-b4fb-56de63ef5e4f
📒 Files selected for processing (88)
.changeset/mcp-thread-dev-only-flag.md.server-changes/account-access-control-hardening.md.server-changes/harden-alert-webhook-and-notification-urls.md.server-changes/query-and-api-endpoint-hardening.md.server-changes/redact-secrets-from-logs.md.server-changes/reject-cross-project-replay-environment.md.server-changes/scope-run-batch-trigger-to-tenant.mdapps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsxapps/webapp/app/models/member.server.tsapps/webapp/app/models/orgIntegration.server.tsapps/webapp/app/models/removeTeamMember.server.tsapps/webapp/app/models/safeIntegrationLog.tsapps/webapp/app/models/slackOAuthResultLog.tsapps/webapp/app/models/user.server.tsapps/webapp/app/routes/_app.@.orgs.$organizationSlug.$.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.invite/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts.new/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.settings.team/route.tsxapps/webapp/app/routes/api.v1.projects.$projectRef.alertChannels.tsapps/webapp/app/routes/api.v1.prompts.$slug.override.tsapps/webapp/app/routes/api.v1.query.tsapps/webapp/app/routes/login.mfa/route.tsxapps/webapp/app/routes/resources.batches.$batchId.check-completion.tsapps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query.ai-title.tsxapps/webapp/app/runEngine/concerns/idempotencyKeys.server.tsapps/webapp/app/services/apiAuth.server.tsapps/webapp/app/services/googleAuth.server.tsapps/webapp/app/services/googleEmailVerification.tsapps/webapp/app/services/mfa/mfaRateLimiter.server.tsapps/webapp/app/services/mfa/mfaRateLimiterGlobal.server.tsapps/webapp/app/services/platformNotifications.server.tsapps/webapp/app/services/rateLimiter.server.tsapps/webapp/app/services/rateLimiterCore.server.tsapps/webapp/app/services/safeEnvironmentLog.tsapps/webapp/app/services/safeRequestLogContext.tsapps/webapp/app/utils/email.tsapps/webapp/app/utils/emailPattern.tsapps/webapp/app/utils/inviteRoleLadder.tsapps/webapp/app/utils/sameOriginNavigation.tsapps/webapp/app/utils/sanitizeUrl.tsapps/webapp/app/v3/detectQueryTables.tsapps/webapp/app/v3/services/aiTitleRateLimiter.server.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/v3/services/alerts/deliverAlert.server.tsapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/v3/services/alerts/safeWebhookFetch.server.tsapps/webapp/app/v3/services/alerts/safeWebhookUrl.server.tsapps/webapp/app/v3/services/batchRunAccess.server.tsapps/webapp/app/v3/services/batchTriggerV3.server.tsapps/webapp/app/v3/services/dependentAttemptScope.tsapps/webapp/app/v3/services/promptOverrideSource.tsapps/webapp/app/v3/services/promptService.server.tsapps/webapp/app/v3/services/replayTaskRun.server.tsapps/webapp/app/v3/services/triggerTaskV1.server.tsapps/webapp/app/v3/services/triggerV1Scoping.tsapps/webapp/app/v3/services/worker/sanitizeWorkerHeaders.tsapps/webapp/app/v3/services/worker/workerGroupAccess.tsapps/webapp/app/v3/services/worker/workerGroupService.server.tsapps/webapp/app/v3/services/worker/workerGroupTokenService.server.tsapps/webapp/test/aiTitleRateLimiter.test.tsapps/webapp/test/batchRunAccess.test.tsapps/webapp/test/dependentAttemptScope.test.tsapps/webapp/test/detectQueryTables.test.tsapps/webapp/test/emailPattern.test.tsapps/webapp/test/engine/idempotencyParentRunScope.test.tsapps/webapp/test/googleEmailVerification.test.tsapps/webapp/test/inviteRoleLadder.test.tsapps/webapp/test/mfaRateLimiter.test.tsapps/webapp/test/promptOverrideSource.test.tsapps/webapp/test/removeTeamMember.test.tsapps/webapp/test/replayTaskRunEnvironmentScoping.test.tsapps/webapp/test/safeEnvironmentLog.test.tsapps/webapp/test/safeIntegrationLog.test.tsapps/webapp/test/safeRequestLogContext.test.tsapps/webapp/test/safeWebhookFetch.test.tsapps/webapp/test/safeWebhookUrl.test.tsapps/webapp/test/sameOriginNavigation.test.tsapps/webapp/test/sanitizeUrl.test.tsapps/webapp/test/sanitizeWorkerHeaders.test.tsapps/webapp/test/slackOAuthResultLog.test.tsapps/webapp/test/triggerV1Scoping.test.tsapps/webapp/test/workerGroupAccess.test.tsinternal-packages/llm-model-catalog/src/defaultPrices.tsinternal-packages/llm-model-catalog/src/modelCatalog.tspackages/cli-v3/src/commands/mcp.tspackages/cli-v3/src/mcp/contextOptions.test.tspackages/cli-v3/src/mcp/contextOptions.ts
💤 Files with no reviewable changes (1)
- apps/webapp/app/models/member.server.ts
## Summary 4 improvements, 5 bug fixes. ## Improvements - Add SDK and API client helpers for run bulk actions. ([#4105](#4105)) - Large batch payloads now offload to object storage instead of riding inline in the trigger request. `batchTrigger` and `batchTriggerAndWait` (and the by-id and by-task variants) offload any per-item payload over 128KB before sending, the same way single `trigger` and `triggerAndWait` already do, so a big batch no longer blows past the API body limit. ([#4165](#4165)) - Removed internal helpers that were only used by the end-of-life v3 self-hosted compute providers. ([#4194](#4194)) - Add an `onEvent` callback to `TriggerChatTransport` / `useTriggerChatTransport` that emits typed lifecycle events for sends, stream connects, first chunk, and turn completion. Send-success metrics, time-to-first-token, and "sent but never answered" watchdogs become a few lines of client code. ([#4187](#4187)) ```ts onEvent: (event) => { if (event.type === "message-sent") metrics.timing("chat.send_ms", event.durationMs); if (event.type === "first-chunk") metrics.timing("chat.ttft_ms", event.sinceSendMs ?? 0); }, ``` ## Bug fixes - fix(cli): honor the MCP server's `--dev-only` flag ([#4199](#4199)) - Fix chat turns that throw (for example from an `onTurnStart` hook) leaking their message listener, which lost or duplicated messages sent during later turns. ([#4176](#4176)) - Fix `chat.agent` and `chat.createSession` permanently dropping user messages when several arrived during a single turn: every buffered message is now dispatched as its own turn instead of only the first. ([#4176](#4176)) - Fix chat continuation runs replaying already-answered messages: turns delivered while the run was suspended now advance the session.in resume cursor, so a new run picks up exactly where the previous one left off. ([#4176](#4176)) - Fix `chat.createSession` swallowing a message sent shortly after stopping a turn: the turn's message listener now detaches when the stream settles, so those messages run as the next turn. ([#4176](#4176)) <details> <summary>Raw changeset output</summary> # Releases ## @trigger.dev/build@4.5.2 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.5.2` ## trigger.dev@4.5.2 ### Patch Changes - fix(cli): honor the MCP server's `--dev-only` flag ([#4199](#4199)) - Updated dependencies: - `@trigger.dev/core@4.5.2` - `@trigger.dev/build@4.5.2` - `@trigger.dev/schema-to-json@4.5.2` ## @trigger.dev/core@4.5.2 ### Patch Changes - Add SDK and API client helpers for run bulk actions. ([#4105](#4105)) - Large batch payloads now offload to object storage instead of riding inline in the trigger request. `batchTrigger` and `batchTriggerAndWait` (and the by-id and by-task variants) offload any per-item payload over 128KB before sending, the same way single `trigger` and `triggerAndWait` already do, so a big batch no longer blows past the API body limit. ([#4165](#4165)) - Removed internal helpers that were only used by the end-of-life v3 self-hosted compute providers. ([#4194](#4194)) ## @trigger.dev/python@4.5.2 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.5.2` - `@trigger.dev/sdk@4.5.2` - `@trigger.dev/build@4.5.2` ## @trigger.dev/react-hooks@4.5.2 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.5.2` ## @trigger.dev/redis-worker@4.5.2 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.5.2` ## @trigger.dev/rsc@4.5.2 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.5.2` ## @trigger.dev/schema-to-json@4.5.2 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.5.2` ## @trigger.dev/sdk@4.5.2 ### Patch Changes - Add SDK and API client helpers for run bulk actions. ([#4105](#4105)) - Fix chat turns that throw (for example from an `onTurnStart` hook) leaking their message listener, which lost or duplicated messages sent during later turns. ([#4176](#4176)) - Fix `chat.agent` and `chat.createSession` permanently dropping user messages when several arrived during a single turn: every buffered message is now dispatched as its own turn instead of only the first. ([#4176](#4176)) - Fix chat continuation runs replaying already-answered messages: turns delivered while the run was suspended now advance the session.in resume cursor, so a new run picks up exactly where the previous one left off. ([#4176](#4176)) - Fix `chat.createSession` swallowing a message sent shortly after stopping a turn: the turn's message listener now detaches when the stream settles, so those messages run as the next turn. ([#4176](#4176)) - Add an `onEvent` callback to `TriggerChatTransport` / `useTriggerChatTransport` that emits typed lifecycle events for sends, stream connects, first chunk, and turn completion. Send-success metrics, time-to-first-token, and "sent but never answered" watchdogs become a few lines of client code. ([#4187](#4187)) ```ts onEvent: (event) => { if (event.type === "message-sent") metrics.timing("chat.send_ms", event.durationMs); if (event.type === "first-chunk") metrics.timing("chat.ttft_ms", event.sinceSendMs ?? 0); }, ``` - Large batch payloads now offload to object storage instead of riding inline in the trigger request. `batchTrigger` and `batchTriggerAndWait` (and the by-id and by-task variants) offload any per-item payload over 128KB before sending, the same way single `trigger` and `triggerAndWait` already do, so a big batch no longer blows past the API body limit. ([#4165](#4165)) - Updated dependencies: - `@trigger.dev/core@4.5.2` ## @trigger.dev/plugins@4.5.2 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.5.2` </details> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Security release batch. Advisory details to be published with the release.