Skip to content

Commit 2644de5

Browse files
Reject internal whitespace in normalized baseURL values
Co-authored-by: Eric Allam <eric@trigger.dev>
1 parent e92a84e commit 2644de5

File tree

6 files changed

+37
-0
lines changed

6 files changed

+37
-0
lines changed

.changeset/curly-radios-visit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ Add a new `@trigger.dev/ai` package with:
99
- rich default task payloads (`chatId`, trigger metadata, messages, request context) with optional payload mapping
1010
- reconnect-aware stream handling on top of Trigger.dev Realtime Streams v2
1111
- strict `baseURL` normalization/validation (trimming, path-safe slash handling, absolute `http(s)` URLs only, no query/hash/credentials)
12+
- rejection of internal whitespace characters in normalized `baseURL` values
1213
- deterministic baseURL validation error ordering for multi-issue inputs (protocol → query/hash → credentials)
1314
- explicit default `baseURL` behavior (`https://api.trigger.dev`) and case-insensitive `HTTP(S)` protocol acceptance

docs/tasks/streams.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,11 +671,13 @@ Examples:
671671
-`https://user:pass@api.trigger.dev`
672672
-`ftp://api.trigger.dev`
673673
-`ws://api.trigger.dev` / `wss://api.trigger.dev`
674+
-`https://api.trigger.dev/\ninternal`
674675

675676
Validation errors use these exact messages:
676677

677678
- `baseURL must not be empty`
678679
- `baseURL must be a valid absolute URL`
680+
- `baseURL must not contain internal whitespace characters`
679681
- `baseURL must use http or https protocol`
680682
- `baseURL must not include query parameters or hash fragments`
681683
- `baseURL must not include username or password credentials`

packages/ai/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
- Added explicit validation that `baseURL` uses `http` or `https`.
2727
- Added explicit validation that `baseURL` excludes query parameters and hash fragments.
2828
- Added explicit validation that `baseURL` excludes username/password credentials.
29+
- Added explicit validation that `baseURL` excludes internal whitespace characters.
2930
- Documented that `HTTP://` and `HTTPS://` are accepted (case-insensitive protocol matching).
3031
- Added deterministic validation ordering for multi-issue baseURL values
3132
(protocol → query/hash → credentials).

packages/ai/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,13 @@ Examples:
178178
-`https://user:pass@api.trigger.dev` (credentials)
179179
-`ftp://api.trigger.dev` (non-http protocol)
180180
-`ws://api.trigger.dev` / `wss://api.trigger.dev` (websocket protocols are rejected)
181+
-`https://api.trigger.dev/\ninternal` (internal whitespace characters)
181182

182183
Validation errors use these exact messages:
183184

184185
- `baseURL must not be empty`
185186
- `baseURL must be a valid absolute URL`
187+
- `baseURL must not contain internal whitespace characters`
186188
- `baseURL must use http or https protocol`
187189
- `baseURL must not include query parameters or hash fragments`
188190
- `baseURL must not include username or password credentials`

packages/ai/src/chatTransport.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,17 @@ describe("TriggerChatTransport", function () {
720720
}).toThrowError("baseURL must be a valid absolute URL");
721721
});
722722

723+
it("throws when baseURL contains internal whitespace characters", function () {
724+
expect(function () {
725+
new TriggerChatTransport({
726+
task: "chat-task",
727+
accessToken: "pk_trigger",
728+
baseURL: "https://api.trigger.dev/\ninternal",
729+
stream: "chat-stream",
730+
});
731+
}).toThrowError("baseURL must not contain internal whitespace characters");
732+
});
733+
723734
it("throws when baseURL is a relative path", function () {
724735
expect(function () {
725736
new TriggerChatTransport({
@@ -3250,6 +3261,17 @@ describe("TriggerChatTransport", function () {
32503261
}).toThrowError("baseURL must be a valid absolute URL");
32513262
});
32523263

3264+
it("throws from factory when baseURL contains internal whitespace characters", function () {
3265+
expect(function () {
3266+
createTriggerChatTransport({
3267+
task: "chat-task",
3268+
accessToken: "pk_trigger",
3269+
baseURL: "https://api.trigger.dev/\ninternal",
3270+
stream: "chat-stream",
3271+
});
3272+
}).toThrowError("baseURL must not contain internal whitespace characters");
3273+
});
3274+
32533275
it("throws from factory when baseURL protocol is not http or https", function () {
32543276
expect(function () {
32553277
createTriggerChatTransport({

packages/ai/src/chatTransport.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ export function createTriggerChatTransport<
451451
const BASE_URL_VALIDATION_ERRORS = {
452452
empty: "baseURL must not be empty",
453453
invalidAbsoluteUrl: "baseURL must be a valid absolute URL",
454+
containsWhitespace: "baseURL must not contain internal whitespace characters",
454455
invalidProtocol: "baseURL must use http or https protocol",
455456
queryOrHash: "baseURL must not include query parameters or hash fragments",
456457
credentials: "baseURL must not include username or password credentials",
@@ -474,6 +475,8 @@ function normalizeBaseUrl(baseURL: string) {
474475
throw new Error(BASE_URL_VALIDATION_ERRORS.empty);
475476
}
476477

478+
assertBaseUrlHasNoInternalWhitespace(normalizedBaseUrl);
479+
477480
let parsedBaseUrl: URL;
478481
try {
479482
parsedBaseUrl = new URL(normalizedBaseUrl);
@@ -488,6 +491,12 @@ function normalizeBaseUrl(baseURL: string) {
488491
return normalizedBaseUrl;
489492
}
490493

494+
function assertBaseUrlHasNoInternalWhitespace(baseUrl: string) {
495+
if (/\s/.test(baseUrl)) {
496+
throw new Error(BASE_URL_VALIDATION_ERRORS.containsWhitespace);
497+
}
498+
}
499+
491500
function assertValidBaseUrlProtocol(parsedBaseUrl: URL) {
492501
if (
493502
parsedBaseUrl.protocol !== "http:" &&

0 commit comments

Comments
 (0)