Skip to content

Commit 82536d0

Browse files
committed
don't rewrite csp
1 parent fa773df commit 82536d0

2 files changed

Lines changed: 0 additions & 181 deletions

File tree

src/http/headers.ts

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export function createHandleResponseHeaders(
1616
next: TidewaveNext,
1717
): Promise<void> {
1818
if (!isTidewaveRequest(req)) {
19-
wrapResponseHeaders(res, config);
2019
if (shouldBufferHtml(req, config)) {
2120
wrapHtmlResponseBody(res, config, getLocalPort);
2221
}
@@ -26,58 +25,6 @@ export function createHandleResponseHeaders(
2625
};
2726
}
2827

29-
export function rewriteCsp(config: TidewaveConfig, csp: string): string {
30-
const hasTrailingSemicolon = /;\s*$/.test(csp);
31-
const toolbarHost =
32-
config.toolbar === false ? '' : `${config.clientUrl || 'https://tidewave.ai'} `;
33-
const directives = csp
34-
.split(';')
35-
.map(directive => directive.trim())
36-
.filter(Boolean)
37-
.flatMap(directive => {
38-
if (directive.startsWith('frame-ancestors')) return [];
39-
40-
const [policy, values] = splitDirective(directive);
41-
if (policy !== 'script-src' || !values) return [directive];
42-
43-
if (values.includes("'unsafe-eval'")) {
44-
return [`script-src ${toolbarHost}${values}`];
45-
}
46-
47-
return [`script-src ${toolbarHost}'unsafe-eval' ${values}`];
48-
});
49-
50-
return `${directives.join('; ')}${hasTrailingSemicolon ? '; ' : ''}`;
51-
}
52-
53-
function wrapResponseHeaders(res: TidewaveResponse, config: TidewaveConfig): void {
54-
const originalSetHeader = res.setHeader.bind(res);
55-
const originalWriteHead = res.writeHead.bind(res);
56-
57-
res.removeHeader('x-frame-options');
58-
59-
res.setHeader = (name, value): TidewaveResponse => {
60-
const header = name.toLowerCase();
61-
62-
if (header === 'x-frame-options') {
63-
return res;
64-
}
65-
66-
return originalSetHeader(name, rewriteHeader(config, header, value));
67-
};
68-
69-
res.writeHead = function writeHead(...args: unknown[]): TidewaveResponse {
70-
const headersIndex = typeof args[1] === 'object' && args[1] !== null ? 1 : 2;
71-
const headers = args[headersIndex];
72-
73-
if (isHeaderMap(headers)) {
74-
args[headersIndex] = rewriteHeaders(config, headers);
75-
}
76-
77-
return originalWriteHead(...(args as Parameters<TidewaveResponse['writeHead']>));
78-
} as TidewaveResponse['writeHead'];
79-
}
80-
8128
function wrapHtmlResponseBody(
8229
res: TidewaveResponse,
8330
config: TidewaveConfig,
@@ -122,39 +69,6 @@ function wrapHtmlResponseBody(
12269
} as TidewaveResponse['end'];
12370
}
12471

125-
function rewriteHeaders(config: TidewaveConfig, headers: HeaderMap): Record<string, HeaderValue> {
126-
const rewritten: Record<string, HeaderValue> = {};
127-
128-
for (const [name, value] of Object.entries(headers)) {
129-
if (value === undefined) continue;
130-
131-
const header = name.toLowerCase();
132-
133-
if (header === 'x-frame-options') continue;
134-
135-
rewritten[name] = rewriteHeader(config, header, value);
136-
}
137-
138-
return rewritten;
139-
}
140-
141-
function rewriteHeader(
142-
config: TidewaveConfig,
143-
header: string,
144-
value: number | string | readonly string[],
145-
): HeaderValue {
146-
if (Array.isArray(value)) {
147-
if (header !== 'content-security-policy') return [...value];
148-
return value.map(item => rewriteCsp(config, item));
149-
}
150-
151-
const scalar = value as number | string;
152-
153-
if (header !== 'content-security-policy') return scalar;
154-
if (typeof scalar === 'string') return rewriteCsp(config, scalar);
155-
return scalar;
156-
}
157-
15872
function toBuffer(chunk: unknown): Buffer {
15973
if (Buffer.isBuffer(chunk)) return chunk;
16074
if (chunk instanceof Uint8Array) return Buffer.from(chunk);
@@ -191,17 +105,6 @@ function endCallback(chunk: unknown, args: unknown[]): (() => void) | undefined
191105
return typeof callback === 'function' ? (callback as () => void) : undefined;
192106
}
193107

194-
function splitDirective(directive: string): [string, string?] {
195-
const index = directive.indexOf(' ');
196-
if (index === -1) return [directive];
197-
198-
return [directive.slice(0, index), directive.slice(index + 1)];
199-
}
200-
201-
function isHeaderMap(value: unknown): value is HeaderMap {
202-
return typeof value === 'object' && value !== null && !Array.isArray(value);
203-
}
204-
205108
function isTidewaveRequest(req: TidewaveRequest): boolean {
206109
const url = req.url || '';
207110
const pathname = url.split('?')[0] || '';

test/http/index.test.ts

Lines changed: 0 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type { TidewaveRequest, TidewaveResponse } from '../../src/http/types';
33
import { handleMcp } from '../../src/http/handlers/mcp';
44
import { createHandleConfig } from '../../src/http/handlers/config';
55
import { createHandleAppHtml, createHandleHtml } from '../../src/http/handlers/html';
6-
import { createHandleResponseHeaders, rewriteCsp } from '../../src/http/headers';
76

87
// Mock request/response helpers
98
const createMockRequest = (headers: Record<string, string> = {}): Partial<TidewaveRequest> => ({
@@ -157,87 +156,4 @@ describe('HTTP Utilities', () => {
157156
});
158157
});
159158
});
160-
161-
describe('handleResponseHeaders', () => {
162-
it('should update CSP headers without the toolbar host when the toolbar is disabled', async () => {
163-
expect(
164-
rewriteCsp(
165-
{ toolbar: false },
166-
"default-src 'self' http://example.com; connect-src 'none'; script-src 'self'; frame-ancestors 'none'",
167-
),
168-
).toBe(
169-
"default-src 'self' http://example.com; connect-src 'none'; script-src 'unsafe-eval' 'self'",
170-
);
171-
});
172-
173-
it('should update CSP headers with the toolbar host when the toolbar is enabled', async () => {
174-
expect(
175-
rewriteCsp(
176-
{ toolbar: true },
177-
"default-src 'self' http://example.com; connect-src 'none'; script-src 'self'; frame-ancestors 'none'",
178-
),
179-
).toBe(
180-
"default-src 'self' http://example.com; connect-src 'none'; script-src https://tidewave.ai 'unsafe-eval' 'self'",
181-
);
182-
});
183-
184-
it('should preserve trailing semicolons while rewriting CSP headers', async () => {
185-
expect(rewriteCsp({}, "upgrade-insecure-requests; script-src 'self'; ")).toBe(
186-
"upgrade-insecure-requests; script-src https://tidewave.ai 'unsafe-eval' 'self'; ",
187-
);
188-
});
189-
190-
it('should remove X-Frame-Options and rewrite CSP headers for app responses', async () => {
191-
const req = { ...createMockRequest(), url: '/foo' };
192-
const { res, mockSetHeader, mockRemoveHeader } = createMockResponse();
193-
const next = vi.fn();
194-
195-
const handler = createHandleResponseHeaders({ toolbar: true });
196-
await handler(req as TidewaveRequest, res as TidewaveResponse, next);
197-
198-
(res as TidewaveResponse).setHeader(
199-
'content-security-policy',
200-
"script-src 'self'; frame-ancestors 'none'",
201-
);
202-
(res as TidewaveResponse).setHeader('x-frame-options', 'DENY');
203-
204-
expect(next).toHaveBeenCalled();
205-
expect(mockRemoveHeader).toHaveBeenCalledWith('x-frame-options');
206-
expect(mockSetHeader).toHaveBeenCalledWith(
207-
'content-security-policy',
208-
"script-src https://tidewave.ai 'unsafe-eval' 'self'",
209-
);
210-
expect(mockSetHeader).not.toHaveBeenCalledWith('x-frame-options', 'DENY');
211-
});
212-
213-
it('should inject the toolbar into HTML app response bodies', async () => {
214-
const req = {
215-
...createMockRequest({ accept: 'text/html,application/xhtml+xml' }),
216-
url: '/foo',
217-
};
218-
const { res, mockEnd, mockRemoveHeader } = createMockResponse();
219-
const next = vi.fn();
220-
221-
const handler = createHandleResponseHeaders(
222-
{
223-
framework: 'vite',
224-
projectName: 'demo_app',
225-
toolbar: true,
226-
},
227-
() => 4321,
228-
);
229-
await handler(req as TidewaveRequest, res as TidewaveResponse, next);
230-
231-
(res as TidewaveResponse).setHeader('Content-Type', 'text/html; charset=utf-8');
232-
(res as TidewaveResponse).end('<html><head></head><body></body></html>');
233-
234-
expect(next).toHaveBeenCalled();
235-
expect(mockEnd).toHaveBeenCalledWith(expect.stringContaining('/tc/toolbar.js'));
236-
expect(mockEnd).toHaveBeenCalledWith(
237-
expect.stringContaining('&quot;project_name&quot;:&quot;demo_app&quot;'),
238-
);
239-
expect(mockEnd).toHaveBeenCalledWith(expect.stringContaining('&quot;local_port&quot;:4321'));
240-
expect(mockRemoveHeader).toHaveBeenCalledWith('content-length');
241-
});
242-
});
243159
});

0 commit comments

Comments
 (0)