Skip to content

Commit 981f7f8

Browse files
committed
🔒 security(triggers): validate HTTP trigger proxy URL scheme at config and runtime
- Joi schema restricts proxy URLs to http/https schemes - Runtime parser throws a clear error on unsupported schemes instead of silently constructing a bad proxy - Documentation clarifies the accepted proxy URL formats
1 parent 9e23674 commit 981f7f8

File tree

3 files changed

+47
-9
lines changed

3 files changed

+47
-9
lines changed

‎app/triggers/providers/http/Http.test.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ describe('HTTP Trigger', () => {
113113
expect(() => http.validateConfiguration(config)).toThrow();
114114
});
115115

116+
test('should reject unsupported proxy URL schemes', async () => {
117+
const config = {
118+
url: 'https://example.com/webhook',
119+
proxy: 'ftp://proxy:21',
120+
};
121+
122+
expect(() => http.validateConfiguration(config)).toThrow();
123+
});
124+
116125
test('should validate GET method explicitly', async () => {
117126
const config = {
118127
url: 'https://example.com/webhook',
@@ -433,6 +442,21 @@ describe('HTTP Trigger', () => {
433442
);
434443
});
435444

445+
test('should fail closed on unsupported proxy URL schemes at runtime', async () => {
446+
const { default: axios } = await import('axios');
447+
axios.mockResolvedValue({ data: {} });
448+
http.configuration = {
449+
url: 'https://example.com/webhook',
450+
method: 'POST',
451+
proxy: 'ftp://proxy:21',
452+
};
453+
454+
await expect(http.trigger({ name: 'test' })).rejects.toThrow(
455+
'proxy URL scheme "ftp:" is unsupported',
456+
);
457+
expect(axios).not.toHaveBeenCalled();
458+
});
459+
436460
test('should use centralized outbound timeout when env override is set', async () => {
437461
const previousTimeout = process.env.DD_OUTBOUND_HTTP_TIMEOUT_MS;
438462
process.env.DD_OUTBOUND_HTTP_TIMEOUT_MS = '1234';

‎app/triggers/providers/http/Http.ts‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,28 @@ interface HttpRequestOptions extends Omit<AxiosRequestConfig, 'proxy'> {
1515
};
1616
}
1717

18+
const SUPPORTED_PROXY_PROTOCOLS = new Set(['http:', 'https:']);
19+
1820
/**
1921
* HTTP Trigger implementation
2022
*/
2123
class Http extends Trigger {
24+
private parseProxyConfiguration(proxy: string): NonNullable<HttpRequestOptions['proxy']> {
25+
const proxyUrl = new URL(proxy);
26+
if (!SUPPORTED_PROXY_PROTOCOLS.has(proxyUrl.protocol)) {
27+
throw new Error(
28+
`Unable to configure HTTP trigger ${this.getId()}: proxy URL scheme "${proxyUrl.protocol}" is unsupported`,
29+
);
30+
}
31+
32+
const defaultProxyPort = proxyUrl.protocol === 'https:' ? 443 : 80;
33+
const proxyPort = proxyUrl.port ? Number.parseInt(proxyUrl.port, 10) : defaultProxyPort;
34+
return {
35+
host: proxyUrl.hostname,
36+
port: proxyPort,
37+
};
38+
}
39+
2240
/**
2341
* Get the Trigger configuration schema.
2442
* @returns {*}
@@ -59,7 +77,9 @@ class Http extends Trigger {
5977
'auth.basic.passwordMissing': '"auth.password" is required',
6078
'auth.bearer.missing': '"auth.bearer" is required',
6179
}),
62-
proxy: this.joi.string(),
80+
proxy: this.joi.string().uri({
81+
scheme: ['http', 'https'],
82+
}),
6383
});
6484
}
6585

@@ -120,13 +140,7 @@ class Http extends Trigger {
120140
}
121141
}
122142
if (this.configuration.proxy) {
123-
const proxyUrl = new URL(this.configuration.proxy);
124-
const defaultProxyPort = proxyUrl.protocol === 'https:' ? 443 : 80;
125-
const proxyPort = proxyUrl.port ? Number.parseInt(proxyUrl.port, 10) : defaultProxyPort;
126-
options.proxy = {
127-
host: proxyUrl.hostname,
128-
port: proxyPort,
129-
};
143+
options.proxy = this.parseProxyConfiguration(this.configuration.proxy);
130144
}
131145
const response = await axios(options);
132146
return response.data;

‎content/docs/current/configuration/triggers/http/index.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The `http` trigger lets you send container update notifications via HTTP.
1818
| `DD_TRIGGER_HTTP_{trigger_name}_AUTH_USER` | ⚪ | The Auth user if BASIC Auth is enabled | | |
1919
| `DD_TRIGGER_HTTP_{trigger_name}_AUTH_PASSWORD` | ⚪ | The Auth user password if `BASIC` Auth is enabled | | |
2020
| `DD_TRIGGER_HTTP_{trigger_name}_AUTH_BEARER` | ⚪ | The Auth bearer token if `BEARER` Auth is enabled | | |
21-
| `DD_TRIGGER_HTTP_{trigger_name}_PROXY` | ⚪ | The HTTP Proxy | | |
21+
| `DD_TRIGGER_HTTP_{trigger_name}_PROXY` | ⚪ | The HTTP Proxy | Valid `http` or `https` proxy URL | |
2222

2323
<Callout type="warn">HTTP trigger auth now fails closed. If `AUTH_TYPE=BASIC`, both `AUTH_USER` and `AUTH_PASSWORD` are required. If `AUTH_TYPE=BEARER`, `AUTH_BEARER` is required.</Callout>
2424

0 commit comments

Comments
 (0)