diff --git a/docs/tasks/streams.mdx b/docs/tasks/streams.mdx index f0ac175de..375cacd2e 100644 --- a/docs/tasks/streams.mdx +++ b/docs/tasks/streams.mdx @@ -656,7 +656,8 @@ If `onError` is omitted, reconnect still returns `null` and continues without ca `baseURL` supports optional path prefixes and trailing slashes; both trigger and stream URLs are normalized consistently, surrounding whitespace is trimmed before normalization, and the resulting value must not be empty. The value must also be a valid absolute URL using -the `http` or `https` protocol, without query parameters or hash fragments. +the `http` or `https` protocol, without query parameters, hash fragments, or embedded +username/password credentials. For richer TypeScript ergonomics in app code, `@trigger.dev/ai` also exports: diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 7d61540b2..f298742f4 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -25,3 +25,4 @@ - Added explicit validation that `baseURL` is a valid absolute URL. - Added explicit validation that `baseURL` uses `http` or `https`. - Added explicit validation that `baseURL` excludes query parameters and hash fragments. +- Added explicit validation that `baseURL` excludes username/password credentials. diff --git a/packages/ai/README.md b/packages/ai/README.md index 44c8ef0dc..c83a66a0d 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -164,6 +164,7 @@ both cleanup steps (`set` inactive state and `delete`) even if one of them fails - `baseURL` must be a valid absolute URL. - `baseURL` must use the `http` or `https` protocol. - `baseURL` must not include query parameters or hash fragments. +- `baseURL` must not include username/password URL credentials. ## `ai.tool(...)` example diff --git a/packages/ai/src/chatTransport.test.ts b/packages/ai/src/chatTransport.test.ts index 4b47251bd..947ba07f6 100644 --- a/packages/ai/src/chatTransport.test.ts +++ b/packages/ai/src/chatTransport.test.ts @@ -675,6 +675,17 @@ describe("TriggerChatTransport", function () { }).toThrowError("baseURL must not include query parameters or hash fragments"); }); + it("throws when baseURL includes username or password credentials", function () { + expect(function () { + new TriggerChatTransport({ + task: "chat-task", + accessToken: "pk_trigger", + baseURL: "https://user:pass@example.com/base", + stream: "chat-stream", + }); + }).toThrowError("baseURL must not include username or password credentials"); + }); + it("accepts https baseURL values without throwing", function () { expect(function () { new TriggerChatTransport({ @@ -2925,6 +2936,17 @@ describe("TriggerChatTransport", function () { }).toThrowError("baseURL must not include query parameters or hash fragments"); }); + it("throws from factory when baseURL includes username or password credentials", function () { + expect(function () { + createTriggerChatTransport({ + task: "chat-task", + accessToken: "pk_trigger", + baseURL: "https://user:pass@example.com/base", + stream: "chat-stream", + }); + }).toThrowError("baseURL must not include username or password credentials"); + }); + it("accepts https baseURL values from factory without throwing", function () { expect(function () { createTriggerChatTransport({ diff --git a/packages/ai/src/chatTransport.ts b/packages/ai/src/chatTransport.ts index aba58f5d2..62fcbe367 100644 --- a/packages/ai/src/chatTransport.ts +++ b/packages/ai/src/chatTransport.ts @@ -484,6 +484,10 @@ function normalizeBaseUrl(baseURL: string) { throw new Error("baseURL must not include query parameters or hash fragments"); } + if (parsedBaseUrl.username.length > 0 || parsedBaseUrl.password.length > 0) { + throw new Error("baseURL must not include username or password credentials"); + } + return normalizedBaseUrl; }