diff --git a/.changeset/curly-radios-visit.md b/.changeset/curly-radios-visit.md index c578c8550..30833180e 100644 --- a/.changeset/curly-radios-visit.md +++ b/.changeset/curly-radios-visit.md @@ -16,5 +16,7 @@ Add a new `@trigger.dev/ai` package with: values that normalize to empty after trimming - expanded unicode whitespace handling coverage to include figure space (`\u2007`) and medium mathematical space (`\u205F`) for both wrapper trimming and internal-whitespace rejection +- expanded invisible-separator rejection coverage to include mongolian vowel separator (`\u180E`) + in both wrapper and internal `baseURL` positions - deterministic baseURL validation error ordering for multi-issue inputs (internal whitespace → protocol → query/hash → credentials) - explicit default `baseURL` behavior (`https://api.trigger.dev`) and case-insensitive `HTTP(S)` protocol acceptance diff --git a/docs/tasks/streams.mdx b/docs/tasks/streams.mdx index 791ee48b2..59cd34ad1 100644 --- a/docs/tasks/streams.mdx +++ b/docs/tasks/streams.mdx @@ -679,6 +679,7 @@ Examples: - ❌ `\u200Bhttps://api.trigger.dev/custom-prefix/\u200B` (zero-width-space wrappers are rejected) - ❌ `\u200Chttps://api.trigger.dev/custom-prefix/\u200C` (zero-width-non-joiner wrappers are rejected) - ❌ `\u200Dhttps://api.trigger.dev/custom-prefix/\u200D` (zero-width-joiner wrappers are rejected) +- ❌ `\u180Ehttps://api.trigger.dev/custom-prefix/\u180E` (mongolian-vowel-separator wrappers are rejected) - ❌ `https://api.trigger.dev?foo=bar` - ❌ `https://api.trigger.dev#fragment` - ❌ `https://user:pass@api.trigger.dev` @@ -705,6 +706,7 @@ Examples: - ❌ `https://api.trigger.dev/\u2007internal` - ❌ `https://api.trigger.dev/\u202Finternal` - ❌ `https://api.trigger.dev/\u205Finternal` +- ❌ `https://api.trigger.dev/\u180Einternal` - ❌ `https://api.trigger.dev/\u3000internal` - ❌ `https://api.trigger.dev/\u2028internal` - ❌ `https://api.trigger.dev/\u2029internal` diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index deade890e..0a05e3529 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -32,6 +32,8 @@ confirmed empty-after-trim values still throw `baseURL must not be empty`. - Expanded unicode whitespace coverage with `\u2007` (figure space) and `\u205F` (medium mathematical space) across internal-whitespace rejection, wrapper trimming acceptance, and empty-after-trim validation. +- Expanded invisible-separator coverage to reject `\u180E` (mongolian vowel separator) in both + internal and wrapper `baseURL` positions. - Documented that `HTTP://` and `HTTPS://` are accepted (case-insensitive protocol matching). - Added deterministic validation ordering for multi-issue baseURL values (internal whitespace → protocol → query/hash → credentials). diff --git a/packages/ai/README.md b/packages/ai/README.md index b0aba5a09..d9648515e 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -186,6 +186,7 @@ Examples: - ❌ `\u200Bhttps://api.trigger.dev/custom-prefix/\u200B` (zero-width-space wrappers are rejected) - ❌ `\u200Chttps://api.trigger.dev/custom-prefix/\u200C` (zero-width-non-joiner wrappers are rejected) - ❌ `\u200Dhttps://api.trigger.dev/custom-prefix/\u200D` (zero-width-joiner wrappers are rejected) +- ❌ `\u180Ehttps://api.trigger.dev/custom-prefix/\u180E` (mongolian-vowel-separator wrappers are rejected) - ❌ `https://api.trigger.dev?foo=bar` (query string) - ❌ `https://api.trigger.dev#fragment` (hash fragment) - ❌ `https://user:pass@api.trigger.dev` (credentials) @@ -212,6 +213,7 @@ Examples: - ❌ `https://api.trigger.dev/\u2007internal` (internal figure-space characters) - ❌ `https://api.trigger.dev/\u202Finternal` (internal narrow no-break space characters) - ❌ `https://api.trigger.dev/\u205Finternal` (internal medium-mathematical-space characters) +- ❌ `https://api.trigger.dev/\u180Einternal` (internal mongolian-vowel-separator characters) - ❌ `https://api.trigger.dev/\u3000internal` (internal ideographic-space characters) - ❌ `https://api.trigger.dev/\u2028internal` (internal line-separator characters) - ❌ `https://api.trigger.dev/\u2029internal` (internal paragraph-separator characters) diff --git a/packages/ai/src/chatTransport.test.ts b/packages/ai/src/chatTransport.test.ts index 209a33f7c..19d7eb836 100644 --- a/packages/ai/src/chatTransport.test.ts +++ b/packages/ai/src/chatTransport.test.ts @@ -950,6 +950,17 @@ describe("TriggerChatTransport", function () { }).toThrowError("baseURL must not contain internal whitespace characters"); }); + it("throws when baseURL contains internal mongolian-vowel-separator characters", function () { + expect(function () { + new TriggerChatTransport({ + task: "chat-task", + accessToken: "pk_trigger", + baseURL: "https://api.trigger.dev/\u180Einternal", + stream: "chat-stream", + }); + }).toThrowError("baseURL must not contain internal whitespace characters"); + }); + it("throws when baseURL contains internal line-separator characters", function () { expect(function () { new TriggerChatTransport({ @@ -1027,6 +1038,17 @@ describe("TriggerChatTransport", function () { }).toThrowError("baseURL must not contain internal whitespace characters"); }); + it("throws when baseURL is wrapped with mongolian-vowel-separator characters", function () { + expect(function () { + new TriggerChatTransport({ + task: "chat-task", + accessToken: "pk_trigger", + baseURL: "\u180Ehttps://api.trigger.dev/custom-prefix/\u180E", + stream: "chat-stream", + }); + }).toThrowError("baseURL must not contain internal whitespace characters"); + }); + it("throws when baseURL contains internal zero-width-non-joiner characters", function () { expect(function () { new TriggerChatTransport({ @@ -4043,6 +4065,17 @@ describe("TriggerChatTransport", function () { }).toThrowError("baseURL must not contain internal whitespace characters"); }); + it("throws from factory when baseURL contains internal mongolian-vowel-separator characters", function () { + expect(function () { + createTriggerChatTransport({ + task: "chat-task", + accessToken: "pk_trigger", + baseURL: "https://api.trigger.dev/\u180Einternal", + stream: "chat-stream", + }); + }).toThrowError("baseURL must not contain internal whitespace characters"); + }); + it("throws from factory when baseURL contains internal line-separator characters", function () { expect(function () { createTriggerChatTransport({ @@ -4120,6 +4153,17 @@ describe("TriggerChatTransport", function () { }).toThrowError("baseURL must not contain internal whitespace characters"); }); + it("throws from factory when baseURL is wrapped with mongolian-vowel-separator characters", function () { + expect(function () { + createTriggerChatTransport({ + task: "chat-task", + accessToken: "pk_trigger", + baseURL: "\u180Ehttps://api.trigger.dev/custom-prefix/\u180E", + stream: "chat-stream", + }); + }).toThrowError("baseURL must not contain internal whitespace characters"); + }); + it("throws from factory when baseURL contains internal zero-width-non-joiner characters", function () { expect(function () { createTriggerChatTransport({ diff --git a/packages/ai/src/chatTransport.ts b/packages/ai/src/chatTransport.ts index 7e96d1e96..98922e4e9 100644 --- a/packages/ai/src/chatTransport.ts +++ b/packages/ai/src/chatTransport.ts @@ -459,7 +459,7 @@ const BASE_URL_VALIDATION_ERRORS = { // Includes standard whitespace plus common invisible separator/control marks // that can make URLs look valid while behaving unexpectedly. -const INTERNAL_WHITESPACE_REGEX = /[\s\u200B\u200C\u200D\u2060\uFEFF]/u; +const INTERNAL_WHITESPACE_REGEX = /[\s\u180E\u200B\u200C\u200D\u2060\uFEFF]/u; function resolvePayloadMapper< UI_MESSAGE extends UIMessage,