Commit Graph

1928 Commits

Author SHA1 Message Date
Baptiste Arnaud
512c988951
👌 Add camera capture options to file uploads (#2560)
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
## What changed

- add an optional Capture setting for image-only File Upload blocks with
None, Back camera, and Front camera choices
- persist the standard `environment` and `user` values and apply them
through the runtime file input `capture` attribute
- preserve existing behavior when capture is unset, including the legacy
Android accept workaround
- normalize extension-based image allowlists for capture while keeping
server-side validation fail-closed
- update schemas, generated OpenAPI specs, embed package versions, and
focused tests
- install Playwright Chromium during Codex worktree setup and ignore
local Revue session state

## Validation

- pre-commit affected format, lint, link, test, and OpenAPI targets:
passed
- affected tests across 14 projects under `.env.dev.example`: passed
- typecheck for builder, viewer, JS embed, File Input, input schemas,
lib, and bot engine: passed
- targeted Viewer Playwright file-upload capture test: passed (2 tests)
- targeted schema, allowlist, runtime helper, and upload validation
suites: passed

## Browser behavior

The standard capture attribute is a browser hint. Supported mobile
browsers may open the selected camera directly; desktop and unsupported
browsers may continue to show a regular file chooser.
2026-07-15 18:53:17 +02:00
Baptiste Arnaud
e7be877bc1
🐛 Fix Android camera for image extension uploads (#2558)
## What changed

- Detect image extensions in file upload allowlists using the shared
`extensionFromMimeType` table.
- Add the corresponding image MIME hints while preserving the original
extensions and the existing `capture=camera` Android workaround.
- Normalize casing and leading dots, deduplicate equivalent entries, and
prefer valid dotted extensions regardless of input order.
- Add focused unit coverage for image-only, mixed, non-image, duplicate,
and casing scenarios.
- Bump the JavaScript and React embed packages to `0.10.6` and ignore
local Revue comments.

## Why

The Android camera workaround was only enabled when the configured
allowlist already contained an `image/*` MIME type. Bots configured with
image extensions such as `.jpg`, `.jpeg`, and `.png` therefore never
received the workaround.

## Impact

Android users can access the existing camera option when a file upload
block is configured with image extensions. Browser hints are enriched
only on the client; server-side validation remains based on the block's
original options.

The `capture=camera` token remains a non-standard Android-specific
workaround and may continue to vary across browsers.

## Validation

- `bunx nx test @typebot.io/js` — 7 tests passed
- `bunx nx typecheck @typebot.io/js`
- `bunx nx format-and-lint`
- `git diff --check`

The repository-wide pre-commit affected test suite could not complete
locally because unrelated packages required missing environment
variables and existing SSRF tests failed. The targeted package tests,
typecheck, and repository formatting checks passed.
2026-07-13 11:35:47 +02:00
Baptiste Arnaud
ef1b4c67c5
🔧 Prepare Ask Assistant migration campaign
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
2026-07-04 16:12:30 +02:00
Baptiste Arnaud
fa2e2e92f4
Disable daily churn analysis cron
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
2026-07-04 09:35:25 +02:00
Baptiste Arnaud
9d2d7ead8e
🔧 Restore OpenAPI docs generation pipeline (#2553)
## Summary

- Restores OpenAPI docs generation, lost in the oRPC migration: new
`generate-openapi` nx targets regenerate
`apps/docs/openapi/{builder,viewer}.json` offline from the oRPC routers,
reusing the exact options served by the live `/api/openapi.json`
endpoints. Both specs are regenerated in this PR (builder.json was stale
since March).
- Fixes spec generation crashing on the spaces router (Effect Schema had
no JSON Schema converter — this also breaks the live builder
`/api/openapi.json` today) and sanitizes an `AppendValue(s)` component
name that made Mintlify silently render every builder API page empty.
- Keeps the docs from going stale again: pre-commit regenerates affected
specs and auto-stages them, CI fails PRs with stale specs, and a new
check requires lockstep `@typebot.io/js` / `@typebot.io/react` version
bumps whenever embed files change.

## Test plan

- [x] `bunx nx generate-openapi builder viewer` produces valid OpenAPI
3.1 files matching the live endpoints (works without env: isolated-vm
stubbed, dummy DATABASE_URL auto-matched to the generated Prisma client)
- [x] All 34 `openapi:` references in `apps/docs/api-reference/*.mdx`
resolve against the new specs; pages verified rendering in `mintlify
dev` (params, unions, curl samples)
- [x] Embeds version check: fails when js or react files change without
both bumps, passes on the historical 0.10.5 bump commit, ignores
unrelated changes
- [x] Pre-commit pipeline (lint, tests, generation, embeds check) passes
end to end
- [ ] Confirm the "Check OpenAPI docs are up to date" step passes on
this PR

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 20:19:08 +02:00
Baptiste Arnaud
d039794797
🔒️ Fix TAC scan header findings (#2549)
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
## What changed

- Upgraded Next.js from 16.1.6 to 16.2.9 across the Next-consuming
packages and lockfile.
- Hardened builder response headers by disabling the powered-by header,
adding CSP directives, setting same-origin CORS for non-API pages, and
applying no-store headers to public entry assets/routes.
- Blocked OPTIONS, TRACE, and TRACK in the builder proxy with a 405
response and an explicit Allow header.

## Why

This addresses TAC scan findings around framework disclosure, cache
behavior, response hardening, and risky HTTP methods on builder routes.

## Validation

- `bunx nx typecheck builder` passed.
- `bunx nx typecheck` currently fails on an unrelated landing-page
telemetry header typing issue at
`apps/landing-page/src/features/telemetry/server/trackPageView.ts:85`
(`"Set-Cookie"` is not in `TypedHeaders<ResponseHeaderMap>`).
2026-06-29 16:16:36 +02:00
Baptiste Arnaud
70b4d5d77f
Fix Dependabot vulnerabilities
Updates esbuild, nodemailer, and undici direct requirements to patched versions for the current Dependabot alerts.
2026-06-29 08:19:20 +02:00
Baptiste Arnaud
ab729561d4
🐛 Fix file upload advisory (#2544)
- Removed deprecated viewer upload URL V1/V2 routes and handlers.
- Hardened V3 upload paths by validating session-derived path segments.
- Updated API docs and OpenAPI output to advertise only V3
generate-upload-url.
- Added file-input upload URL test coverage and package test wiring.
2026-06-27 16:32:26 +02:00
Baptiste Arnaud
9c81300e5a
🐛 Fix Send Email attachment handling (#2543)
- Restrict Send Email attachments to valid Typebot upload URLs.
- Resolve private Typebot upload URLs to short-lived storage URLs before
sending.
- Enable Nodemailer local file access protection for Send Email
messages.
- Fix WhatsApp public media upload keys to avoid a duplicated public
prefix.
- Add regression tests for rejected attachment origins and valid Typebot
uploads.
2026-06-27 16:30:06 +02:00
Baptiste Arnaud
03c8dd967f
🐛 Harden email login codes (#2542)
- Add failed-attempt tracking to verification tokens in Prisma schemas
and the PostgreSQL migration.
- Record invalid email login code attempts with an atomic counter and
retire active login tokens after five failures.
- Mark Auth.js email login verification tokens separately from other
verification-token flows.
- Use cryptographically secure random six-digit email login codes.
- Add auth package Vitest coverage, including parallel failed-attempt
handling.
2026-06-27 15:36:28 +02:00
Baptiste Arnaud
c7ae1c162e
🐛 Fix misc errors (#2540)
- Return a no-op response for Stripe subscription updates when the
customer is no longer tied to a workspace.
- Validate user profile string fields against database limits before
Prisma updates.
- Return a bad request for imported typebots missing the legacy start
block or group.
- Truncate saved answer content to the MySQL TEXT byte limit before
persisting.
- Cap WhatsApp interactive body text at Meta's 1024-character limit and
add conversion tests.
2026-06-27 11:07:17 +00:00
Baptiste Arnaud
54e1fa1a0f
🐛 Fix WhatsApp audio file upload URLs (#2539)
- Fix WhatsApp audio media conversion for file upload inputs so saved
variables receive the uploaded file URL.
- Keep existing audio reply behavior for text input audio clips and
other non-file inputs.
- Add focused WhatsApp conversion tests for file-upload audio and
preserved audio responses.
2026-06-27 05:59:48 +00:00
Baptiste Arnaud
b86864ca60
🐛 Improve expired data cleanup reliability (#2536)
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
Monthly job / clean (push) Has been cancelled
- Reduces expired chat session delete chunks to keep cleanup
transactions smaller.
- Forces old chat session lookups to use the primary database before
deletion.
- Adds structured progress, timing, and error logging to expired data
cleanup steps.
2026-06-24 18:38:42 +02:00
Baptiste Arnaud
248f7515d8
🐛 Fix S3 operations without full config (#2533)
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
- Added a shared S3 config guard that requires bucket, endpoint, access
key, and secret key.
- Skipped S3 copy, delete, and URL replacement operations when storage
is not fully configured.
- Moved typebot, workspace, and results cleanup callers to rely on
guarded S3 helpers.
- Added tests for incomplete and complete S3 config detection.
2026-06-18 17:30:39 +02:00
Baptiste Arnaud
9224f4f770
🐛 Fix embed video overlay in preview (#2532)
## Summary
- Keeps video bubbles above the absolute typing/background layer in the
embedded chat renderer.
- Applies the same stacking approach to direct video URLs and embedded
video iframes.
- Bumps `@typebot.io/js` and `@typebot.io/react` from `0.10.4` to
`0.10.5`.

## Root cause
Chrome could place the full-size absolute `bubble-typing` layer over
video bubbles because the media content shared the same stacking level.
That made the overlay sit above the HTML video element in builder embed
preview.

## Validation
- `bunx nx typecheck @typebot.io/react`
- `bunx nx build @typebot.io/react --configuration=development`
- `bunx nx format-and-lint`
- Commit hook: `nx affected -t
format-and-lint,lint-repo,check-broken-links,test --parallel=4`

Note: I could not visually verify the exact provided local typebot URL
in this workspace because it returns `Typebot not found` locally.
2026-06-18 16:50:13 +02:00
Baptiste Arnaud
f00ccba8ed
🐛 Fix stored WhatsApp phone lookup fallback (#2531)
- Fallback to the saved WhatsApp credential name when Meta phone lookup
fails for an existing stored credential.
- Keep new Meta token setup validation strict so setup errors still
surface.
2026-06-17 18:57:26 +02:00
Baptiste Arnaud
3db24c49bd
👌 Configure WhatsApp webhook forwarding URL (#2529)
- Adds a WhatsApp webhook forwarding URL field directly in the deploy
UI.
- Moves forwarding enablement to an explicit "Forward webhooks" switch
and keeps forwarded events independent from that switch.
- Normalizes legacy URL-only settings through Zod while preserving
explicit disabled configs.
- Restricts forwarding URLs to HTTP(S) and aligns the production update
script with the shared schema.
- Flushes debounced URL inputs on blur and covers legacy, disabled,
empty-event, and invalid-protocol cases in tests.
2026-06-17 15:56:16 +02:00
Baptiste Arnaud
c5516cd24c
👌 Configure WhatsApp forwarded events (#2528)
- Add WhatsApp settings to enable forwarding and choose all events,
error statuses, or marketing statuses.
- Forward selected Meta webhook payloads while preserving legacy
errorAndMarketingStatusWebhookForwardUrl behavior.
- Add regression coverage for legacy URLs, disabled configs, and
all-events forwarding.
2026-06-17 12:38:31 +02:00
Baptiste Arnaud
3b321f4ba1
🐛 Ignore expected WhatsApp webhook errors (#2527)
- Added a viewer ORPC Sentry filter for expected production WhatsApp
webhook validation failures.
- Kept invalid webhook secret, signature, and payload responses flowing
back to callers without reporting them to Sentry.
2026-06-17 12:37:37 +02:00
Baptiste Arnaud
c82ac4324a
🐛 Fix embedded audio uploads (#2523)
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
- Keep CORS headers on upload proxy error responses.
- Surface failed audio/file uploads as Typebot errors instead of leaving
the recorder stuck.
- Prevent recorded-audio Send clicks from submitting the host page form
in embedded bots.
- Prevent duplicate embed uploads while an upload is already in flight.
- Disable recorder abort while recorded audio is being processed or
uploaded.
- Bump @typebot.io/js and @typebot.io/react to 0.10.4.
2026-06-08 15:52:56 +02:00
Baptiste Arnaud
b252a9c996
🐛 Fix OpenAI chat completions endpoint (#2522)
- Fixed the OpenAI chat completion handler to explicitly use the AI SDK
chat model for custom base URLs.
- Added regression coverage to verify OpenAI-compatible requests target
`/chat/completions` with the expected payload.
2026-06-08 13:13:36 +02:00
Baptiste Arnaud
2fd5510641
🐛 Fix OpenAI audio transcription uploads (#2521)
- Normalize `audio/webm` upload filenames to `.webm` instead of `.weba`.
- Send OpenAI transcription requests with an explicit supported audio
filename.
- Log OpenAI transcription failures as block logs instead of bubbling a
500.
- Add focused tests for WebM upload naming and transcription filename
normalization.
2026-06-08 12:22:08 +02:00
Baptiste Arnaud
08cb6ea10f
🐛 Fix missing result columns in CSV export (#2513)
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
- 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.
2026-05-25 11:33:23 +02:00
Baptiste Arnaud
f56c3c3f77
🐛 Block IPv6 unspecified SSRF targets (#2511)
- 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.
2026-05-24 15:46:09 +02:00
Baptiste Arnaud
9d6708bbee
🐛 Fix upload proxy public URL (#2508)
- 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`.
2026-05-22 15:56:26 +02:00
Baptiste Arnaud
a64e82b612
🐛 Fix unsafe upload URL generation (#2502)
- 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.
2026-05-21 18:37:04 +02:00
Baptiste Arnaud
36a6186101
🐛 Fix WhatsApp preview webhook authorization (#2499)
- 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.
2026-05-21 17:01:12 +02:00
Baptiste Arnaud
e296c870bc
🐛 Fix WhatsApp webhook verification (#2498)
- 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.
2026-05-21 16:45:32 +02:00
Baptiste Arnaud
30cbc616e0
🐛 Fix WhatsApp status forwarding SSRF protection (#2497)
- Use the SSRF-protected safeKy client for workspace-configured WhatsApp
status forwarding URLs.
2026-05-21 15:24:37 +02:00
Baptiste Arnaud
5861031f72
🔧 Add WhatsApp status forward URL update script (#2496)
- 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.
2026-05-21 11:29:55 +02:00
ghisson
fa7cc8c3f2
👌 (openai) Add Ask Model file search controls (#2483)
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>
2026-05-21 08:02:44 +02:00
Baptiste Arnaud
89682dd4ad
🐛 Sanitize CSV exports against formula injection (#2493)
- 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.
2026-05-19 18:46:54 +02:00
Baptiste Arnaud
6f915c3096
🐛 Prevent cross-typebot webhook resume IDOR (#2494)
- 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`.
2026-05-19 18:45:19 +02:00
Baptiste Arnaud
fdcc1784c9
🔧 Hash API tokens (#2492)
- 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.
2026-05-19 18:17:11 +02:00
Baptiste Arnaud
6f289f647f
🔒️ Upgrade vulnerable deps (ai v5, nodemailer v8, otel sdk-node 0.217) (#2491)
## 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>
2026-05-19 16:30:36 +02:00
Kleber Rocha
5b5f82d6c0
🔒️ Add SSRF_ALLOWED_HOSTS env for self-hosted internal APIs (#2474)
## 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.
2026-05-15 11:30:29 +02:00
Baptiste Arnaud
67c7c86b1a
Revert Google Sheets picker fixes (#2486, #2487) (#2488)
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Partykit server / deploy (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
- 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)
2026-05-12 17:12:31 +02:00
Baptiste Arnaud
8e67415766
🐛 Fix Google Sheets picker 401 by setting Cloud Project AppId (#2486)
- 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>
2026-05-12 16:30:43 +02:00
Baptiste Arnaud
85eb843c42
🐛 Fix monthly cron tx timeout when deleting archived typebots (#2481)
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Partykit server / deploy (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
- 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>
2026-05-01 10:26:12 +00:00
Baptiste Arnaud
30682a258a
📝 Document status page and analytics completion criteria (#2480)
- 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>
2026-05-01 11:10:10 +02:00
マーティンズJR
53e90c497d
fix the button position close in modal (#2476)
Some checks failed
Create Tag / create-tag (push) Has been cancelled
Deploy Partykit server / deploy (push) Has been cancelled
Deploy Workflows (Fly.io) / deploy (push) Has been cancelled
Monthly job / clean (push) Has been cancelled
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>
2026-04-29 08:58:46 +00:00
Baptiste Arnaud
050f9061d3
🐛 Fix transcript compute crash on choice items with session-var display condition (#2468)
- 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 `bunf​ig.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>
2026-04-20 17:32:18 +02:00
Baptiste Arnaud
892870ff86
🐛 Fix SSRF safe dispatcher DNS lookup handling (#2462)
- 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>
2026-04-15 12:05:54 +02:00
Baptiste Arnaud
b25c41b02b
🐛 Fix SSRF bypass via DNS rebinding in HTTP request and script fetch flows (#2461)
- 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>
2026-04-15 09:28:06 +00:00
Baptiste Arnaud
7ae4c007d0
🐛 Fix credential access control and remove vulnerable S3 upload endpoint (#2459)
- 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>
2026-04-15 10:39:54 +02:00
Baptiste Arnaud
20d11a5678
Add Ask Model action using OpenAI Responses API (#2455)
## 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>
2026-04-13 14:02:35 +00:00
Baptiste Arnaud
e1530b6f90
🐛 Add missing date-fns dependencies to @typebot.io/results
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>
2026-04-13 15:10:11 +02:00
Baptiste Arnaud
90bc7a94ef
👌 Add time filter to results export and fix CSV download on R2 (#2449)
## 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>
2026-04-13 15:04:27 +02:00
Baptiste Arnaud
55b290043b
🐛 Fix PostHog tracking by updating cookie domain to typebot.com (#2447)
## 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>
2026-04-13 12:08:18 +00:00
Baptiste Arnaud
d3c15f32c8
🐛 Handle GA script load failure to prevent bot from hanging (#2446)
## 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>
2026-04-13 12:18:57 +02:00