Skip to content

Commit 13ea67b

Browse files
authored
Improve URL check (#4613)
* Improve URL check * production only
1 parent cdec21c commit 13ea67b

File tree

3 files changed

+37
-6
lines changed

3 files changed

+37
-6
lines changed

src/misc/check-allowed-url.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export function checkAllowedUrl(url: string | URL | undefined): boolean {
2+
if (process.env.NODE_ENV !== 'production') return true;
3+
4+
try {
5+
if (url == null) return false;
6+
7+
const u = typeof url === 'string' ? new URL(url) : url;
8+
if (!u.protocol.match(/^https?:$/) || u.hostname === 'unix') {
9+
return false;
10+
}
11+
12+
if (u.port !== '' && !['80', '443'].includes(u.port)) {
13+
return false;
14+
}
15+
16+
return true;
17+
} catch {
18+
return false;
19+
}
20+
}

src/misc/download-url.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import config from '../config';
77
import * as chalk from 'chalk';
88
import Logger from '../services/logger';
99
import { checkPrivateIp } from './check-private-ip';
10+
import { checkAllowedUrl } from './check-allowed-url';
1011

1112
const pipeline = util.promisify(stream.pipeline);
1213

1314
export async function downloadUrl(url: string, path: string) {
14-
const u = new URL(url);
15-
if (!u.protocol.match(/^https?:$/) || u.hostname === 'unix') {
16-
throw new StatusError('Invalid protocol', 400);
15+
if (!checkAllowedUrl(url)) {
16+
throw new StatusError('Invalid URL', 400);
1717
}
1818

1919
const logger = new Logger('download-url');
@@ -43,6 +43,11 @@ export async function downloadUrl(url: string, path: string) {
4343
},
4444
http2: false, // default
4545
retry: 0,
46+
}).on('redirect', (res: Got.Response, opts: Got.NormalizedOptions) => {
47+
if (!checkAllowedUrl(opts.url)) {
48+
logger.warn(`Invalid URL: ${opts.url}`);
49+
req.destroy();
50+
}
4651
}).on('response', (res: Got.Response) => {
4752
if (checkPrivateIp(res.ip)) {
4853
logger.warn(`Blocked address: ${res.ip}`);

src/misc/fetch.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { HttpProxyAgent } from 'http-proxy-agent';
66
import { HttpsProxyAgent } from 'https-proxy-agent';
77
import config from '../config';
88
import { checkPrivateIp } from './check-private-ip';
9+
import { checkAllowedUrl } from './check-allowed-url';
910

1011
export async function getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record<string, string>): Promise<any> {
1112
const res = await getResponse({
@@ -42,9 +43,8 @@ const OPERATION_TIMEOUT = 60 * 1000;
4243
const MAX_RESPONSE_SIZE = 10 * 1024 * 1024;
4344

4445
export async function getResponse(args: { url: string, method: 'GET' | 'POST', body?: string, headers: Record<string, string>, timeout?: number, size?: number }) {
45-
const u = new URL(args.url);
46-
if (!u.protocol.match(/^https?:$/) || u.hostname === 'unix') {
47-
throw new StatusError('Invalid protocol', 400);
46+
if (!checkAllowedUrl(args.url)) {
47+
throw new StatusError('Invalid URL', 400);
4848
}
4949

5050
const timeout = args.timeout || RESPONSE_TIMEOUT;
@@ -71,6 +71,12 @@ export async function getResponse(args: { url: string, method: 'GET' | 'POST', b
7171
retry: 0,
7272
});
7373

74+
req.on('redirect', (res, opts) => {
75+
if (!checkAllowedUrl(opts.url)) {
76+
req.cancel(`Invalid url: ${opts.url}`);
77+
}
78+
});
79+
7480
return await receiveResponce(req, args.size || MAX_RESPONSE_SIZE);
7581
}
7682

0 commit comments

Comments
 (0)