## Summary
- Move time filter logic from builder analytics to a shared
`@typebot.io/results` package so both analytics and results export can
use it
- Add time filter support to the results export workflow, allowing users
to export only filtered results
- Fix CSV files opening in the browser instead of downloading on R2 by
adding `Content-Type` and `Content-Disposition` metadata to S3 uploads
- Add `metadata` parameter to `S3UploadClient.uploadObject()` for
passing object metadata to R2/S3
## Test plan
- [ ] Export results with a time filter applied and verify only filtered
results are exported
- [ ] Verify the exported CSV file downloads directly instead of opening
in the browser
- [ ] Check that analytics time filter still works correctly after the
shared module refactor
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Update `DEFAULT_COOKIE_DOMAIN` from `typebot.io` to `typebot.com` in
telemetry constants
- Fixes "Failed to execute 'set' on 'CookieStore': Cookie domain must
domain-match current host" error that was preventing all PostHog
pageview tracking since the domain migration
## Test plan
- [ ] Verify PostHog pageviews are being recorded on typebot.com
- [ ] Confirm no cookie domain mismatch errors in browser console
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Add `script.onerror` handler in `initGoogleAnalytics` so the promise
resolves even when the GA script fails to load (ad blockers, network
errors), preventing the bot from hanging indefinitely.
- Bump `@typebot.io/js` and `@typebot.io/react` versions to `0.10.2`.
## Test plan
- [ ] Enable a Google Analytics integration block in a bot
- [ ] Block `googletagmanager.com` (e.g. via ad blocker) and verify the
bot still loads
- [ ] Check that `"Failed to load Google Analytics script"` appears in
the console
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Bump version from 3.15.2 to 3.16.0 and generate changelog covering all
changes since last release
- Fix Docker build for Nx monorepo by adding
`DATABASE_URL=postgresql://` to the `next build` step (Prisma needs it
during page data collection)
- Fix broken blog link in whatsapp-lead-generation post
## Test plan
- [x] Docker image builds successfully for both `builder` and `viewer`
(tested locally)
- [ ] Verify release workflow triggers correctly when tagging `v3.16.0`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Update the default embed library version from `0.3` to `0.x` across
the WordPress plugin and builder instructions, so it auto-resolves to
the latest `0.x.x` via jsdelivr
- Update the lib_version validation regex to accept version ranges like
`0.x`
## Test plan
- [ ] Verify
`https://cdn.jsdelivr.net/npm/@typebot.io/js@0.x/dist/web.js` resolves
correctly
- [ ] Check WordPress admin panel shows `0.x` as default
- [ ] Verify builder Popup/Bubble instructions show `0.x` for cloud
users
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Fix stored XSS vulnerability where `javascript:` URIs in text bubble
links, image click links, and toast popup links could execute arbitrary
JS in visitors' browsers
- Add `sanitizeUrl` utility that allowlists only `http:`, `https:`,
`mailto:`, and `tel:` protocols
- Add explicit `typecheck` Nx targets for `builder` and `viewer`
(Next.js projects don't get one inferred by `@nx/js/typescript`)
- Bump `@typebot.io/js` and `@typebot.io/react` to `0.10.1`
## Test plan
- [ ] Create a bot with a text bubble link set to `javascript:alert(1)`
and verify it renders as `#`
- [ ] Same test with an image click link
- [ ] Verify normal `https://` links still work
- [ ] Run `bunx nx typecheck builder` and `bunx nx typecheck viewer`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Fix broken authorization check in `getLinkedTypebots` where
`Array.filter()` received an `async` callback, causing the
`isReadTypebotForbidden` predicate to never actually filter out
unauthorized typebots (Promise is always truthy)
- Replace with `Promise.all` + synchronous `.filter()` to properly
evaluate access checks
- Any authenticated user could previously read full bot definitions
(variables, groups, webhooks) from other workspaces via a Typebot Link
block reference
## Test plan
- [ ] Verify that linked typebots the user has access to are still
returned correctly
- [ ] Verify that linked typebots from other workspaces the user does
NOT have access to are no longer returned
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Adds a new GitHub Actions workflow that runs `bunx nx affected -t
typecheck` on every pull request
- Uses the PR base branch as the Nx affected comparison base for
accurate change detection
- Sets up Node 24 + Bun with full git history for proper affected
analysis
## Test plan
- [ ] Open a PR with a type error and verify the check fails
- [ ] Open a PR with no type errors and verify the check passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- **Fix SSRF via open redirect bypass** (GHSA-jxv3-m939-w95c): HTTP
Request block now uses `safeKy` instead of `ky`, and Code block's
sandboxed `fetch` now follows redirects manually with `redirect:
"manual"` + re-validation of each `Location` hop via
`validateHttpReqUrl`.
- **Improved safeKy tests**: redirect bypass tests now run end-to-end
through `safeKy` (not just indirect Location header checks), including
chained redirect scenarios.
- **Skip Vercel preview builds**: `nx-ignore` now exits early with code
0 when `VERCEL_ENV=preview`.
## Test plan
- [x] `bunx nx test @typebot.io/lib` — 76 tests pass (0 fail, 6 skip)
- [x] `NODE_ENV=development bun test packages/lib/src/safeKy.test.ts` —
8 tests pass (redirect bypass verified end-to-end)
- [x] `bunx nx typecheck @typebot.io/bot-engine` — passes
- [x] `bunx nx typecheck @typebot.io/variables` — passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- **Fixes
[GHSA-cq66-9cwr-x8jr](https://github.com/baptisteArno/typebot.io/security/advisories/GHSA-cq66-9cwr-x8jr)**
— the previous fix for GHSA-4xc5-wfwc-jw47 was incomplete: the
bot-engine runtime still allowed any authenticated user to exfiltrate
credentials from any workspace via the preview endpoint by passing
`workspaceId: ""`
- Invert the falsy check in `getCredentials()` so that missing or empty
`workspaceId` **denies** access instead of skipping validation
- Add `z.string().min(1)` on the typebot schema's `workspaceId` to
reject empty strings at the Zod validation layer
- Tighten `getGoogleSpreadsheet` param type from `string | undefined` to
`string`
## Test plan
- [x] Typecheck passes on `credentials`, `bot-engine`, `whatsapp`
- [x] All tests pass (lint, bot-engine, whatsapp, results, lib,
rich-text, emails, builder)
- [ ] Verify that preview mode still works correctly with valid
workspaceId
- [ ] Verify that forged blocks, Google Sheets, and streaming endpoints
still load credentials for legitimate users
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Adds a `typebotId` filter to the `findResult` Prisma query, preventing
a user from loading result data (answers, variables) belonging to a
different typebot via a foreign `resultId` in the `startChat` endpoint.
- Addresses security advisory GHSA-f475-7m4x-m6mx.
## Test plan
- [x] Typecheck passes (`bunx nx typecheck bot-engine`)
- [x] All affected tests pass (bot-engine, results, builder, etc.)
- [ ] Verify that `startChat` with a `resultId` from another typebot no
longer returns that result's data
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Changes
- **Presigned POST → PUT**: Replace `generatePresignedPostPolicy` with
`generatePresignedPutUrl` across all upload endpoints (builder + viewer
v1/v2/v3). This makes uploads compatible with Cloudflare R2 which
doesn't support the S3 POST Object API. Frontend consumers now use `PUT`
with raw file body + `Content-Type`/`Cache-Control` headers instead of
`POST` with FormData.
- **XSS mitigation**: Block dangerous content types (SVG, HTML, XML, JS)
in the builder `generateUploadUrl` endpoint. Restrict frontend `accept`
attributes from `image/*` to an explicit list of safe raster types
(`png, jpeg, gif, webp, avif, bmp, tiff`). Addresses
GHSA-jj87-c343-26vp.
- **Fix file upload URL validation**: `isURL` with `require_tld: true`
rejected `localhost` and `NEXTAUTH_URL` proxy URLs for private files.
Now uses a trusted host allowlist (`localhost`, `NEXTAUTH_URL`,
`S3_PUBLIC_CUSTOM_DOMAIN`) to skip TLD requirement.
- **Docs**: Update S3 CORS policy from `POST` to `PUT`, add Cloudflare
R2 to supported providers list.
- **Bump**: `@typebot.io/js` and `@typebot.io/react` → `0.10.0`
## Verification
- Tested avatar upload on builder with R2 bucket (PUT succeeds, image
displays)
- Verified CORS preflight passes after R2 bucket config
- Confirmed `generateUploadUrl` rejects `image/svg+xml` with 400
- All unit tests pass (`nx affected -t test`)
- Typecheck passes on all affected packages
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
Introduces `safeKy` — a `ky` instance with built-in SSRF validation
(`validateHttpReqUrl`) — and applies it to all server-side fetch calls
where the URL originates from user input.
## Changes
- **`packages/lib/src/ky.ts`**: Added `safeKy` export — a `ky` instance
that validates URLs against private IPs, loopback, cloud metadata
endpoints, and other SSRF vectors before making the request. The
existing `ky` instance is unchanged for trusted internal API calls.
-
**`packages/forge/blocks/openai/src/handlers/createTranscriptionHandler.ts`**:
Replaced raw `fetch(options.url)` with `safeKy.get(options.url)` — this
was the vulnerability reported in GHSA-h3v3-c6cq-q763.
- **`packages/forge/blocks/gmail/src/helpers/buildEmail.ts`**: `ky.get`
→ `safeKy.get` for attachment URL downloads.
-
**`packages/forge/blocks/openai/src/helpers/splitUserTextMessageIntoOpenAIBlocks.ts`**:
`ky.get` → `safeKy.get` for image URL detection.
-
**`packages/forge/blocks/blink/src/handlers/sendFeedEventHandler.ts`**:
`ky.head` → `safeKy.head` for attachment metadata fetching (keeps `ky`
for the Blink API call).
- **`packages/ai/src/splitUserTextMessageIntoBlocks.ts`**: `ky.get` →
`safeKy.get` for image URL detection.
- **`packages/whatsapp/src/getOrUploadMedia.ts`**: `ky.get` →
`safeKy.get` for media downloads (keeps `ky` for WhatsApp API uploads).
- **`packages/lib/src/safeKy.test.ts`**: Tests verifying `safeKy` blocks
loopback, private IPs, cloud metadata, and non-HTTP protocols.
## Verification
- `bunx nx typecheck` passes on all affected packages
(`@typebot.io/openai-block`, `@typebot.io/gmail-block`,
`@typebot.io/blink-block`, `@typebot.io/ai`, `@typebot.io/whatsapp`)
- `bunx nx test @typebot.io/lib` — 70 tests pass (66 existing + 4 new
`safeKy` tests)
- All pre-commit hook tests pass
- Manual QA: test with public URLs to confirm functionality is
preserved, then test with `http://127.0.0.1` or `http://169.254.169.254`
to confirm SSRF is blocked
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Add `generateUploadUrlProcedure` to the `fileUploadBuilderRouter` so
the builder's API exposes the `/v3/generate-upload-url` endpoint
- Since `apiHost` now points to the builder origin, file upload requests
from the bot preview were hitting a missing endpoint (previously routed
to the viewer)
## Verification
- Test a bot in the builder preview that has a file upload block
- Confirm the upload request to `/api/v3/generate-upload-url` succeeds
and file uploads work
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>