Skip to content

Commit b2fce39

Browse files
committed
add test
1 parent 1b7488d commit b2fce39

File tree

2 files changed

+146
-1
lines changed

2 files changed

+146
-1
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import http from 'http';
2+
import open from 'open';
3+
import {openURLMiddleware} from '../openURLMiddleware';
4+
5+
jest.mock('open');
6+
7+
describe('openURLMiddleware', () => {
8+
let req: http.IncomingMessage & {body?: Object};
9+
let res: jest.Mocked<http.ServerResponse>;
10+
let next: jest.Mock;
11+
12+
beforeEach(() => {
13+
req = {
14+
method: 'POST',
15+
body: {},
16+
} as any;
17+
18+
res = {
19+
writeHead: jest.fn(),
20+
end: jest.fn(),
21+
} as any;
22+
23+
next = jest.fn();
24+
jest.clearAllMocks();
25+
});
26+
27+
afterEach(() => {
28+
jest.restoreAllMocks();
29+
});
30+
31+
it('should sanitize URL with pipe character to prevent RCE', async () => {
32+
const maliciousUrl = 'https://example.com/|rm -rf /';
33+
req.body = {url: maliciousUrl};
34+
35+
await openURLMiddleware(req, res, next);
36+
37+
// Verify that open was called with a sanitized URL
38+
expect(open).toHaveBeenCalledTimes(1);
39+
const sanitizedUrl = (open as jest.Mock).mock.calls[0][0];
40+
41+
// The sanitized URL should not contain the raw pipe character that could execute shell commands
42+
// The pipe character should be encoded (as %7C) to prevent shell command execution
43+
expect(sanitizedUrl).not.toContain('|rm -rf /');
44+
expect(sanitizedUrl).not.toContain('|');
45+
// Verify the pipe character is URL-encoded (as %7C) instead of raw
46+
expect(sanitizedUrl).toContain('%7C');
47+
expect(sanitizedUrl).toMatch(/^https:\/\/example\.com/);
48+
49+
expect(res.writeHead).toHaveBeenCalledWith(200);
50+
expect(res.end).toHaveBeenCalled();
51+
});
52+
53+
it('should sanitize URL with pipe character in query string', async () => {
54+
const maliciousUrl = 'https://example.com/path?param=value|rm -rf /';
55+
req.body = {url: maliciousUrl};
56+
57+
await openURLMiddleware(req, res, next);
58+
59+
expect(open).toHaveBeenCalledTimes(1);
60+
const sanitizedUrl = (open as jest.Mock).mock.calls[0][0];
61+
62+
// The pipe character in query string should be properly encoded (as %7C)
63+
expect(sanitizedUrl).not.toContain('|rm -rf /');
64+
expect(sanitizedUrl).not.toContain('|');
65+
expect(sanitizedUrl).toContain('%7C');
66+
expect(sanitizedUrl).toMatch(/^https:\/\/example\.com/);
67+
68+
expect(res.writeHead).toHaveBeenCalledWith(200);
69+
expect(res.end).toHaveBeenCalled();
70+
});
71+
72+
it('should sanitize URL with pipe character in path', async () => {
73+
const maliciousUrl = 'https://example.com/path|rm -rf /';
74+
req.body = {url: maliciousUrl};
75+
76+
await openURLMiddleware(req, res, next);
77+
78+
expect(open).toHaveBeenCalledTimes(1);
79+
const sanitizedUrl = (open as jest.Mock).mock.calls[0][0];
80+
81+
// The pipe character in path should be properly encoded (as %7C)
82+
expect(sanitizedUrl).not.toContain('|rm -rf /');
83+
expect(sanitizedUrl).not.toContain('|');
84+
expect(sanitizedUrl).toContain('%7C');
85+
expect(sanitizedUrl).toMatch(/^https:\/\/example\.com/);
86+
87+
expect(res.writeHead).toHaveBeenCalledWith(200);
88+
expect(res.end).toHaveBeenCalled();
89+
});
90+
91+
it('should handle normal URLs without pipe characters', async () => {
92+
const normalUrl = 'https://example.com/path?param=value';
93+
req.body = {url: normalUrl};
94+
95+
await openURLMiddleware(req, res, next);
96+
97+
expect(open).toHaveBeenCalledTimes(1);
98+
const sanitizedUrl = (open as jest.Mock).mock.calls[0][0];
99+
100+
expect(sanitizedUrl).toBe('https://example.com/path?param=value');
101+
102+
expect(res.writeHead).toHaveBeenCalledWith(200);
103+
expect(res.end).toHaveBeenCalled();
104+
});
105+
106+
it('should return 400 for missing request body', async () => {
107+
req.body = undefined;
108+
109+
await openURLMiddleware(req, res, next);
110+
111+
expect(open).not.toHaveBeenCalled();
112+
expect(res.writeHead).toHaveBeenCalledWith(400);
113+
expect(res.end).toHaveBeenCalledWith('Missing request body');
114+
});
115+
116+
it('should return 400 for non-string URL', async () => {
117+
req.body = {url: 123};
118+
119+
await openURLMiddleware(req, res, next);
120+
121+
expect(open).not.toHaveBeenCalled();
122+
expect(res.writeHead).toHaveBeenCalledWith(400);
123+
expect(res.end).toHaveBeenCalledWith('URL must be a string');
124+
});
125+
126+
it('should return 400 for invalid URL format', async () => {
127+
req.body = {url: 'not-a-valid-url'};
128+
129+
await openURLMiddleware(req, res, next);
130+
131+
expect(open).not.toHaveBeenCalled();
132+
expect(res.writeHead).toHaveBeenCalledWith(400);
133+
expect(res.end).toHaveBeenCalledWith('Invalid URL format');
134+
});
135+
136+
it('should return 400 for invalid URL protocol', async () => {
137+
req.body = {url: 'file:///etc/passwd'};
138+
139+
await openURLMiddleware(req, res, next);
140+
141+
expect(open).not.toHaveBeenCalled();
142+
expect(res.writeHead).toHaveBeenCalledWith(400);
143+
expect(res.end).toHaveBeenCalledWith('Invalid URL protocol');
144+
});
145+
});

packages/cli-server-api/src/openURLMiddleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import open from 'open';
1414
/**
1515
* Open a URL in the system browser.
1616
*/
17-
async function openURLMiddleware(
17+
export async function openURLMiddleware(
1818
req: IncomingMessage & {
1919
// Populated by body-parser
2020
body?: Object;

0 commit comments

Comments
 (0)