Skip to content

Commit 511cd50

Browse files
author
Treicy Sanchez Gutierrez (from Dev Box)
committed
feat(http-fetchlibrary): export defaultScrubSensitiveHeaders for composition
1 parent a3c5040 commit 511cd50

2 files changed

Lines changed: 108 additions & 41 deletions

File tree

packages/http/fetch/src/middlewares/options/redirectHandlerOptions.ts

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,55 @@ export interface RedirectHandlerOptionsParams {
3333
scrubSensitiveHeaders?: ScrubSensitiveHeaders;
3434
}
3535

36+
/**
37+
* The default implementation for scrubbing sensitive headers during redirects.
38+
* This function removes Authorization, Cookie, and Proxy-Authorization headers when the host or scheme changes.
39+
*
40+
* It is exported so that consumers providing a custom {@link ScrubSensitiveHeaders} callback can call it to
41+
* retain the default behavior and then layer additional logic on top (e.g. removing custom headers such as
42+
* `X-Api-Key`), matching the composable pattern available in the .NET and Python Kiota SDKs.
43+
*
44+
* Note: Proxy-Authorization handling is not applicable in Fetch API as proxy configuration
45+
* is handled at a lower level by the browser/runtime and is not accessible to JavaScript.
46+
* @param headers - The headers object to modify
47+
* @param originalUrl - The original request URL
48+
* @param newUrl - The new redirect URL
49+
*/
50+
export const defaultScrubSensitiveHeaders: ScrubSensitiveHeaders = (headers: Record<string, string>, originalUrl: string, newUrl: string) => {
51+
if (!headers || !originalUrl || !newUrl) {
52+
return;
53+
}
54+
55+
try {
56+
const originalUri = new URL(originalUrl);
57+
const newUri = new URL(newUrl);
58+
59+
// Remove Authorization, Cookie, and Proxy-Authorization headers if the request's scheme or host changes.
60+
// Header keys must be matched case-insensitively because FetchRequestAdapter.getRequestFromRequestInformation
61+
// lower-cases every header key before the headers object reaches this middleware, so PascalCase
62+
// property deletes such as `delete headers.Authorization` would otherwise be a no-op.
63+
const isDifferentHostOrScheme = originalUri.host.toLowerCase() !== newUri.host.toLowerCase() || originalUri.protocol.toLowerCase() !== newUri.protocol.toLowerCase();
64+
65+
if (isDifferentHostOrScheme) {
66+
for (const key of Object.keys(headers)) {
67+
const lower = key.toLowerCase();
68+
if (lower === "authorization" || lower === "cookie" || lower === "proxy-authorization") {
69+
delete headers[key];
70+
}
71+
}
72+
}
73+
} catch {
74+
// If URL parsing fails, don't modify headers
75+
// This handles cases where invalid URLs are passed
76+
return;
77+
}
78+
79+
// Note: Proxy-Authorization is not handled here as proxy configuration in Fetch API
80+
// is managed by the browser/runtime and not accessible to JavaScript code.
81+
// In environments where this matters (e.g., Node.js with custom agents), the proxy
82+
// configuration should be managed at the HTTP client level.
83+
};
84+
3685
/**
3786
* MiddlewareOptions
3887
* A class representing RedirectHandlerOptions
@@ -56,48 +105,10 @@ export class RedirectHandlerOptions implements RequestOption {
56105
private static readonly defaultShouldRetry: ShouldRedirect = () => true;
57106

58107
/**
59-
* The default implementation for scrubbing sensitive headers during redirects.
60-
* This method removes Authorization and Cookie headers when the host or scheme changes.
61-
* Note: Proxy-Authorization handling is not applicable in Fetch API as proxy configuration
62-
* is handled at a lower level by the browser/runtime and is not accessible to JavaScript.
63-
* @param headers - The headers object to modify
64-
* @param originalUrl - The original request URL
65-
* @param newUrl - The new redirect URL
108+
* The default {@link ScrubSensitiveHeaders} callback used when none is supplied.
109+
* Delegates to the exported {@link defaultScrubSensitiveHeaders} function.
66110
*/
67-
private static readonly defaultScrubSensitiveHeaders: ScrubSensitiveHeaders = (headers: Record<string, string>, originalUrl: string, newUrl: string) => {
68-
if (!headers || !originalUrl || !newUrl) {
69-
return;
70-
}
71-
72-
try {
73-
const originalUri = new URL(originalUrl);
74-
const newUri = new URL(newUrl);
75-
76-
// Remove Authorization, Cookie, and Proxy-Authorization headers if the request's scheme or host changes.
77-
// Header keys must be matched case-insensitively because FetchRequestAdapter.getRequestFromRequestInformation
78-
// lower-cases every header key before the headers object reaches this middleware, so PascalCase
79-
// property deletes such as `delete headers.Authorization` would otherwise be a no-op.
80-
const isDifferentHostOrScheme = originalUri.host.toLowerCase() !== newUri.host.toLowerCase() || originalUri.protocol.toLowerCase() !== newUri.protocol.toLowerCase();
81-
82-
if (isDifferentHostOrScheme) {
83-
for (const key of Object.keys(headers)) {
84-
const lower = key.toLowerCase();
85-
if (lower === "authorization" || lower === "cookie" || lower === "proxy-authorization") {
86-
delete headers[key];
87-
}
88-
}
89-
}
90-
} catch {
91-
// If URL parsing fails, don't modify headers
92-
// This handles cases where invalid URLs are passed
93-
return;
94-
}
95-
96-
// Note: Proxy-Authorization is not handled here as proxy configuration in Fetch API
97-
// is managed by the browser/runtime and not accessible to JavaScript code.
98-
// In environments where this matters (e.g., Node.js with custom agents), the proxy
99-
// configuration should be managed at the HTTP client level.
100-
};
111+
private static readonly defaultScrubSensitiveHeaders: ScrubSensitiveHeaders = defaultScrubSensitiveHeaders;
101112

102113
/**
103114
*

packages/http/fetch/test/node/RedirectHandlerOptions.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { assert, describe, it } from "vitest";
99

1010
import { RedirectHandlerOptions } from "../../src/middlewares/options/redirectHandlerOptions";
11+
import { defaultScrubSensitiveHeaders } from "../../src/middlewares/options/redirectHandlerOptions";
1112

1213
describe("RedirectHandlerOptions.ts", () => {
1314
describe("constructor", () => {
@@ -115,4 +116,59 @@ describe("RedirectHandlerOptions.ts", () => {
115116
RedirectHandlerOptions["defaultScrubSensitiveHeaders"]({} as any, "https://example.com", null as any);
116117
});
117118
});
119+
120+
describe("exported defaultScrubSensitiveHeaders", () => {
121+
it("Should be the same implementation used as the class default", () => {
122+
const options = new RedirectHandlerOptions();
123+
assert.equal(options.scrubSensitiveHeaders, defaultScrubSensitiveHeaders);
124+
});
125+
126+
it("Should remove Authorization, Cookie and Proxy-Authorization headers when host changes", () => {
127+
const headers = {
128+
authorization: "******",
129+
cookie: "session=SECRET",
130+
"proxy-authorization": "******",
131+
"content-type": "application/json",
132+
};
133+
defaultScrubSensitiveHeaders(headers, "https://graph.microsoft.com/v1.0/me", "https://evil.attacker.com/steal");
134+
assert.isUndefined(headers.authorization);
135+
assert.isUndefined(headers.cookie);
136+
assert.isUndefined(headers["proxy-authorization"]);
137+
assert.isDefined(headers["content-type"]); // Other headers should remain
138+
});
139+
140+
it("Should be composable with custom scrubbing logic", () => {
141+
const scrubSensitiveHeaders = (headers: Record<string, string>, originalUrl: string, newUrl: string) => {
142+
// Preserve the default behavior
143+
defaultScrubSensitiveHeaders(headers, originalUrl, newUrl);
144+
145+
// Then remove additional custom headers on host/scheme change
146+
const originalUri = new URL(originalUrl);
147+
const newUri = new URL(newUrl);
148+
const isDifferentHostOrScheme = originalUri.host.toLowerCase() !== newUri.host.toLowerCase() || originalUri.protocol.toLowerCase() !== newUri.protocol.toLowerCase();
149+
if (isDifferentHostOrScheme) {
150+
for (const key of Object.keys(headers)) {
151+
const lower = key.toLowerCase();
152+
if (lower === "x-api-key" || lower === "x-custom-auth") {
153+
delete headers[key];
154+
}
155+
}
156+
}
157+
};
158+
159+
const headers = {
160+
authorization: "******",
161+
cookie: "session=SECRET",
162+
"x-api-key": "custom-secret",
163+
"x-custom-auth": "custom-secret",
164+
"content-type": "application/json",
165+
};
166+
scrubSensitiveHeaders(headers, "https://graph.microsoft.com/v1.0/me", "https://evil.attacker.com/steal");
167+
assert.isUndefined(headers.authorization);
168+
assert.isUndefined(headers.cookie);
169+
assert.isUndefined(headers["x-api-key"]);
170+
assert.isUndefined(headers["x-custom-auth"]);
171+
assert.isDefined(headers["content-type"]);
172+
});
173+
});
118174
});

0 commit comments

Comments
 (0)