- Add Neon as the recommended production Postgres provider in
self-hosting docs, with a non-affiliate note.
- Link database recommendations to `https://typebot.com/neon`.
- Ignore `.context` files from Biome checks.
- Copy .agents/skills from the repository root during Conductor
workspace setup.
- Copy .claude/skills from the repository root during Conductor
workspace setup.
- Ensure both skill directories exist before copying ignored skill
files.
- Append missing result headers to saved column orders so newly added
response blocks stay visible.
- Reuse the normalized column order for selected-results CSV export
instead of a local missing-header fallback.
- Add regression coverage for default, saved, and legacy column orders.
- Block IPv6 unspecified addresses in the shared SSRF IP validator.
- Add validator regressions for compressed and expanded IPv6 unspecified
literals.
- Add a safeKy regression that verifies [::] requests are rejected
before reaching a local IPv6 wildcard listener.
- Prevent signed upload proxy URLs from using internal request origins
in self-hosted reverse-proxy setups.
- Resolve runtime upload proxy URLs from `NEXT_PUBLIC_VIEWER_URL` and
builder upload proxy URLs from `NEXTAUTH_URL`.
- Add regression coverage for internal container origins like
`https://2e862faf612f:3000`.
- Update the manual self-hosting deploy guide for the current Nx/Bun
workflow.
- Replace stale PM2 commands with repo-root Nx start commands for
builder and viewer.
- Expand the Nginx sample to cover separate builder and viewer domains
and streaming support.
- Replace direct browser presigned PUT uploads with signed Typebot
upload proxy URLs.
- Generate or validate upload object keys server-side while preserving
legacy v1/v2/v3 file-input upload contracts.
- Keep builder slot uploads stable for replaceable assets and use
generated names for runtime file uploads.
- Store active file-input MIME types as safe attachment downloads while
keeping safe image uploads inline.
- Update upload clients and docs to support both raw PUT proxy uploads
and form-data uploads.
- Secure Google Sheets OAuth state with a signed payload, expiry, user
binding, and HttpOnly nonce cookie.
- Enforce workspace and typebot write authorization before generating
consent URLs and before callback side effects.
- Scope Google Sheets credential creation and typebot updates in a
transaction, and clear the OAuth state cookie after callback.
- Add OAuth state verification to the Forge popup flow and centralize
OAuth block definition lookup.
- Add tests for signed Google Sheets OAuth state parsing and redirect
sanitization.
- Limit the PartyKit deploy workflow to pushes that change
`packages/partykit`.
- Remove the broken `turbo-ignore` gate and deploy through the Nx
target.
- Validate that WhatsApp preview webhook test sessions belong to the
authorized typebot before resuming them.
- Require the preview session to still be waiting on the requested
webhook block.
- Share WhatsApp preview phone normalization between preview creation
and test webhook execution.
- Verify Meta WhatsApp webhooks with optional app secrets while
preserving soft compatibility for existing credentials.
- Add optional 360Dialog webhook secret validation and update flows for
existing WhatsApp credentials.
- Validate Meta WABA and phone number access, then auto-subscribe the
Meta app to the WABA during setup.
- Clear and disable WhatsApp integration when the active credentials are
removed, including published bot state.
- Preserve raw webhook request bodies, document preview app secret
configuration, and add focused webhook verification tests.
- Update related tooling, Biome ignore rules, opensrc guidance, and
small formatting/type-safety cleanup.
- Add a production script to update the WhatsApp status webhook forward
URL on draft and published typebot settings.
- Register the script in `@typebot.io/scripts` and add the settings
project reference needed for typechecking.
- Ignore local `typebot-prod-db` skill folders for agent tooling.
- Added an "Authentication fails or users are randomly logged out"
section to `apps/docs/self-hosting/troubleshoot.mdx` covering the common
causes (rotated `NEXTAUTH_SECRET` / `ENCRYPTION_SECRET`, builder/viewer
secret mismatch, mismatched `NEXTAUTH_URL`, unreachable or reset
database).
- Pointed users to tail the builder logs to surface the actual NextAuth
/ Prisma error behind the generic "Check server logs" message.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Pexels picker could repeatedly fetch the same/empty pages when
changing filters such as Square and scrolling to the bottom. This
happened because the intersection observer kept firing while the last
item stayed visible, pagination did not track whether more results were
available, and duplicated videos could be appended.
This patch:
- uses Pexels 1-based pagination
- tracks whether more videos are available from total_results
- prevents concurrent observer fetches
- resets pagination on search/filter changes
- deduplicates videos by id
before:
https://github.com/user-attachments/assets/f5ca5675-b958-41a7-a4b8-fc92a5576a89
after:
https://github.com/user-attachments/assets/590fc103-9dc9-4a85-b95e-3d1d9f9eefb6
---------
Co-authored-by: Baptiste Arnaud <baptiste@typebot.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add chunk count and score threshold options to Ask Model, pass them to
the OpenAI file_search tool, and constrain Score threshold to the 0-1
range in the builder.
---------
Co-authored-by: ghisson <ghisson@LAPTOP-DQ8OKN2P>
Co-authored-by: ghisson <ghisson@LAPTOP-DQ8OKN2P.localdomain>
- Added `sanitizeCsvCell` helper that escapes values starting with `=`,
`+`, `-`, `@`, `\t`, or `\r` with a leading apostrophe to prevent
CSV/formula injection (CWE-1236) in spreadsheet apps.
- Applied sanitization to both server-side CSV streams
(`streamAllResultsToCsv`, `streamAllResultsToCsvV2`) for cells and
headers.
- Applied sanitization to client-side exports (`SelectionToolbar`,
`ExportAllResultsDialog`) for cells and header keys.
- Scope `result` lookup in `handleExecuteWebhook` to the authorized
`typebotId`, closing a cross-tenant IDOR where a caller with read access
to one typebot could resume another typebot's waiting webhook session by
supplying a foreign `resultId`.
- Store newly created API tokens as SHA-256 hashes while returning the
raw token once.
- Authenticate bearer tokens against both hashed and legacy plaintext
records, then lazily hash legacy records on successful use.
- Seed Playwright API tokens as hashes.
- Add Conductor setup and run scripts for local workspaces.
## Summary
Fixes 18 open Dependabot alerts and migrates affected code to the new
major versions:
- `@opentelemetry/sdk-node` → `^0.217.0` (Prometheus exporter DoS,
GHSA-q7rr-3cgh-j5r3)
- `nodemailer` → `^8.0.5` across all manifests + root override
(GHSA-vvjj-xcjg-gr5g, GHSA-c7w3-x93f-qmm8)
- `ai` → `^5.0.52` (GHSA-rwvc-j5jr-mgvh); legacy 3.x dep removed from
`packages/deprecated/legacy` and replaced with a small in-tree
`OpenAIStream` + `StreamingTextResponse` shim
- Provider SDKs aligned to v5 peer: `@ai-sdk/openai`, `anthropic`,
`groq`, `mistral`, `perplexity`, `deepseek`, `togetherai`, `openRouter`,
`dify-ai-provider`
### AI SDK v4 → v5 migration
- `parseTools`: `parameters` renamed to `inputSchema`
- `runChatCompletion` / `runChatCompletionStream`: `maxSteps` replaced
by `stopWhen(stepCountIs(maxSteps))`;
`usage.{prompt,completion,total}Tokens` replaced by
`totalUsage.{input,output,total}Tokens`
- New `toLegacyDataStream` helper that re-emits the v4 data-stream
protocol (`0:text`, `3:error`, `9:tool_call`, …) so existing consumers
in `embeds/js` and the OpenAI `askAssistant` / `askModel` handlers keep
working
- `compatibility: "strict"` removed from `createOpenAI` (option dropped
in v5)
- `formatDataStreamPart` / `processDataStream` imports moved to
`@ai-sdk/ui-utils` (legacy package pinned at 1.2.11)
### E2E test follow-up
Second commit fixes Playwright tests that broke once the env-resolved
URLs / new SDK surface kicked in:
- `fileUpload`: assert exported URL contains `parseS3PublicBaseUrl()`
(not `S3_ENDPOINT`) so it works with `S3_PUBLIC_CUSTOM_DOMAIN`; verify
post-deletion via cache-busted `request.get` instead of a CDN-cached new
tab.
- `ssrf`: assert on the actual "Security validation failed" log emitted
by the pre-flight check; fixture now maps `response.statusCode` into a
`Status` variable so `Status: …` assertions resolve.
- Root `dev` script includes `@typebot.io/partykit` so the webhook
listener e2e test can hit PartyKit on `:1999`.
Also fixes a pre-existing broken anchor link in `whatsapp-ai-agent.mdx`
that blocked the landing-page link checker.
## Test plan
- [ ] `bunx nx test` passes
- [ ] `bunx nx typecheck` passes
- [ ] `bunx nx affected -t
format-and-lint,lint-repo,check-broken-links,test --parallel=4` passes
(pre-commit)
- [ ] `bun run dev` boots builder, viewer, workflows **and** PartyKit
- [ ] Viewer Playwright suite: `fileUpload.spec.ts`, `ssrf.spec.ts`,
`webhookListener.spec.ts` all green
- [ ] Manual smoke: OpenAI `askAssistant` block streams correctly in the
embed (v4 data-stream protocol preserved)
- [ ] Manual smoke: Anthropic / Mistral / Groq blocks still execute
end-to-end
- [ ] Manual smoke: send a test email through a workspace SMTP block
(nodemailer v8)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Self-hosted deployments often have legitimate internal corporate APIs on
RFC1918 ranges (10/8, 172.16/12, 192.168/16) — e.g., a backend chat API
exposed only on the internal cluster network. Since v3.14, the SSRF
mitigation introduced for [CVE-2025-64709 /
GHSA-8gq9-rw7v-3jpr](https://github.com/baptisteArno/typebot.io/security/advisories/GHSA-8gq9-rw7v-3jpr)
blocks every private range unconditionally, which prevents HTTP Request
blocks (and Function blocks via fetch) from reaching those APIs without
exposing them to the public internet.
The advisory itself listed hostname allowlisting as one of the
recommended mitigations (item #5: "Implement an SSRF-safe proxy or apply
hostname allowlists for outgoing requests"), and this PR implements it
as an opt-in env var.
## What changes
- New env var `SSRF_ALLOWED_HOSTS` (comma-separated hostnames) parsed in
`packages/env`
- `validateHttpReqUrl` now accepts an `allowedHosts` parameter
(symmetric with the existing `lookupHost` injection point); the env var
is the default
- When the URL's hostname matches an entry, `validateIPAddress` is
called with `{ allowPrivateRanges: true }`, which **only** skips the
RFC1918 range checks (10/8, 172.16/12, 192.168/16)
## What the allowlist does NOT relax
Every other protection remains active even for allowlisted hosts:
- ✅ Link-local 169.254.0.0/16 — **the actual CVE vector** (AWS/GCP/Azure
metadata)
- ✅ Loopback 127.0.0.0/8 and IPv6 ::1
- ✅ 0.0.0.0/8
- ✅ IPv6 link-local fe80::/10 and unique local fc00::/7
- ✅ Cloud metadata hostnames (\`metadata.google.internal\`,
\`metadata.goog\`, \`metadata\`)
- ✅ \`localhost\` in production
- ✅ Decimal/hex/octal IP encoding bypasses
- ✅ IMDS bypass headers (\`X-aws-ec2-metadata-token*\`,
\`Metadata-Flavor\`)
This is the deliberate design: **even if an attacker controls DNS for an
allowlisted hostname and points it to 169.254.169.254, the link-local
check still fires.** The allowlist intentionally narrows what's relaxed
— corp LAN access, not metadata-service access.
## Test plan
- [x] All existing 53 SSRF tests still pass unchanged (default behavior
preserved when env unset)
- [x] New \`describe\` block covering 14 cases:
- RFC1918 hostnames pass when listed (10/8, 172.16/12, 192.168/16,
direct IP literal)
- Link-local **still blocks** for allowlisted host (DNS hijack defense)
- Loopback **still blocks** for allowlisted host
- Direct \`169.254.169.254\` IP literal **still blocks** even when
listed
- \`metadata.google.internal\` **still blocks** even when listed
- Decimal-encoded metadata IP **still blocks** even when listed
- Default behavior preserved when \`allowedHosts\` is undefined or empty
- Hostname not in allowlist still blocks
- Case-insensitive matching (URL parser normalizes hostname)
- No subdomain wildcarding (exact match only)
- [x] \`bun test\` green: 63/63 in \`validateHttpReqUrl.test.ts\`
- [x] \`tsc --noEmit\` green for \`packages/lib\` and \`packages/env\`
- [x] Full \`nx affected\` test suite green (whatsapp, feature-flags,
spaces, rich-text, root, emails, bot-engine, results, builder, lib — all
passed)
## Use case
Currently, self-hosters facing this hit dead-ends: their internal corp
DNS resolves to 10.x, the validator rejects it, and the only escape
valves are (a) expose the API publicly (security regression — adds
attack surface), (b) downgrade to ≤ v3.13.x (re-introduces the
vulnerable code path), or (c) maintain a fork with the validator patched
(fragile, breaks on every upgrade). An opt-in env var resolves this
without weakening the core mitigation.
I'm opening a companion issue (#2475) explaining the use case in more
detail and to gather feedback if a different design is preferred — happy
to iterate.
- Revert #2487 (trigger_onepick OAuth param) and #2486 (setAppId +
NEXT_PUBLIC_GOOGLE_SHEETS_APP_ID env var) which broke the Google Sheets
picker in production.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Add `trigger_onepick=true` parameter to the Google Sheets OAuth
consent URL, required by Google for the `drive.file` scope since the
April 21, 2026 enforcement; without it the picker iframe returns 401.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Call `PickerBuilder.setAppId()` with the Cloud Project number when
building the Google Sheets picker (required by Google when the OAuth
flow uses the `drive.file` scope, otherwise the picker iframe returns
401).
- Add new optional client env var `NEXT_PUBLIC_GOOGLE_SHEETS_APP_ID` in
`packages/env`.
- Document the new variable and setup step in
`apps/docs/self-hosting/configuration.mdx`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Delete archived results in batches of 500 before
`prisma.typebot.deleteMany` to avoid Prisma cascade wrapping the entire
delete in a single Vitess transaction that exceeds the max duration
- Use `prisma.$primary().result.findMany` for the batch lookup so read
replica lag does not exit the loop while results still exist on primary
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Added a new "Is there a status page?" entry to the FAQ pointing to
status.typebot.io.
- Added a "Definitions" section to the analytics doc explaining Views,
Starts, Completions (no input remaining + at least one answer + no
pending client-side action expecting a dedicated reply) and the
per-block drop-off rate.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When clicking on an image in the chat preview/runtime, the X button to
close the modal appeared misplaced outside the correct area. This
happened because the button was positioned with `position: fixed`,
remaining stuck to the entire page viewport instead of being inside the
modal/chat container.
before:
<img width="490" height="542" alt="image"
src="https://github.com/user-attachments/assets/ed17cf29-6397-46c7-9779-01aec89c3c5b"
/>
after:
<img width="502" height="523" alt="image"
src="https://github.com/user-attachments/assets/8ac64782-6af4-4417-bad1-971450e0f66c"
/>
---------
Co-authored-by: Baptiste Arnaud <baptiste@typebot.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add a new "Add a VAT ID for B2B reverse charge" section to
`apps/docs/workspace/subscription.mdx`
- Document the steps to add a VAT ID through the Stripe billing portal
- Clarify that the reverse charge only applies to future invoices, not
retroactively
- Note that without a VAT ID, destination VAT is charged as for a
standard B2C customer
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add a "Common pitfalls" section to `apps/docs/editor/graph.mdx`
warning that a block without an outgoing edge stops the flow.
- Add a note in `apps/docs/theme/overview.mdx` clarifying that toggling
Typebot branding (or any theme change) requires a republish to go live.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add a Pro-only `<Note>` callout at the top of
`deploy/web/custom-domain.mdx`
- Add a "Switch workspace" section in `workspace.mdx` covering the
workspace dropdown
- Add a FAQ entry "I don't see my bots after login" pointing to the
workspace switcher
- Add new guide `guides/external-messaging-apps.mdx` on integrating
Typebot with external messaging apps (KakaoTalk, LINE, Telegram, etc.)
via the HTTP API
- Register the new guide in `mint.json`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add `skipDisplayConditionCheck` option to
`injectVariableValuesInButtonsInputBlock` and
`injectVariableValuesInPictureChoiceBlock` to skip runtime
display-condition filtering
- Propagate the flag from `validateAndParseInputMessage` when
`skipValidation` is true, so transcript compute no longer filters out
the item the user actually chose
- Show an error state in `ResultDialog` when the transcript query fails
instead of rendering an empty container
- Add a unit test reproducing the bug (choice item with
`displayCondition` on an unset session variable)
- Add `bunfig.toml` + `test-preload.ts` for `bot-engine` to mock
`isolated-vm` and set `SKIP_ENV_CHECK` at test time
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add `context: { user }` parameter to `handleGetSheets` to access the
authenticated user
- Fetch the workspace and validate access with `isReadWorkspaceFobidden`
before calling `getGoogleSpreadsheet`
- Aligns `handleGetSheets` with the existing security pattern used by
`handleGetSpreadsheetName` and `handleGetAccessToken` in the same router
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Clarify that logs are per-result and not a global activity feed in
`results/overview.mdx`
- Add `guides/user-commands.mdx` with a concrete Reply event + Jump +
Return recipe for `restart` / `help` commands
- Cross-link Reply event vs Command event in `editor/events/reply.mdx`
and `editor/events/command.mdx`
- Add FAQ entry explaining that a persistent text input is not native,
with keyword and custom-UI workarounds
- Restructure `workspace/subscription.mdx` with a Steps-based
cancel/downgrade flow and explicit Free plan limits (200 chats, 1 seat)
- Register the new user-commands guide in `mint.json` navigation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove the "If this is urgent" section and link to
`typebot.co/urgent-support` from the how-to-get-help docs page
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix `validatingLookup` to handle `{ all: true }` DNS lookup mode that
undici passes, which returns an array of addresses instead of a single
string
- Add localhost bypass in development mode to match existing
`validateHttpReqUrl` behavior
- Without this fix, `fetch()` in Set Variable code blocks silently
failed for external URLs
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add `createSafeDispatcher` with a `validatingLookup` that checks
resolved IPs at TCP connection time, preventing DNS rebinding TOCTOU
attacks (GHSA-hgqq-whf5-mrrf)
- Pass the safe undici dispatcher in `safeFetchWithoutChunkedEncoding`
(`ky.ts`) and in the isolated VM fetch wrapper (`executeFunction.ts`)
- Export `parseIPAddress`, `validateIPAddress` and `ParsedIP` from
`validateHttpReqUrl.ts` for reuse in the dispatcher
- Add unit tests for `validatingLookup` and E2E test bot/spec for SSRF
scenarios
- Add `@types/bun` to `packages/lib` tsconfig
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Upgrade model from `claude-opus-4-5` to `claude-opus-4-6` in both
workflows
- Fix permissions from `read` to `write` for `contents`,
`pull-requests`, and `issues`
- Replace deprecated `plugin_marketplaces`/`plugins` with a direct
`prompt` in review workflow
- Add `synchronize` trigger to review on every push
- Add `--max-turns 5` to review workflow
- Remove redundant `additional_permissions` and unused comments
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bind credential updates to workspace ownership in
`handleUpdateOAuthCredentials` to prevent cross-workspace OAuth
credential takeover (GHSA-3788-7276-x4j4)
- Require write access in `handleGetAccessToken` to prevent guest
members from obtaining Google Sheets OAuth tokens (GHSA-qjpp-9cqc-jhh8)
- Require write access in `handleListModels` to prevent guest members
from exfiltrating OpenAI API keys (GHSA-gc3v-9whw-6wjh)
- Remove deprecated unauthenticated upload endpoint that allowed
arbitrary S3 object writes (GHSA-m7f5-3wcm-x2c4)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- **New "Ask Model" action** in the OpenAI block using the Responses API
(`openai.responses.stream()`), supporting multi-turn conversations via
`previous_response_id`, built-in tools (file search with vector store
IDs, web search, code interpreter), custom function calling, and
streaming.
- **Deprecated "Ask Assistant"** action: hidden from the action dropdown
(still functional for existing typebots), with Sentry tracking for
production usage monitoring.
- **Updated template** (`openai-assistant-chat.json`) to use the new Ask
Model action with `gpt-5.4`.
- **Merged `chatModels` + `reasoningModels`** into a single `models`
list in constants.
- **Updated docs** replacing Ask Assistant documentation with Ask Model.
## Test plan
- [ ] Create a typebot with OpenAI → "Ask Model", configure model +
message + instructions, verify streaming works
- [ ] Test multi-turn: verify Response ID variable persists across
exchanges
- [ ] Test built-in tools: web search toggle, vector store IDs tag
input, code interpreter toggle
- [ ] Test custom function calling loop
- [ ] Verify "Ask Assistant" still works for existing typebots but is
hidden from the dropdown for new ones
- [ ] Verify the template loads correctly in the builder
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The package imports date-fns and date-fns-tz but didn't declare them,
causing Docker builds with --filter to fail on resolution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>