Skip to content

Commit 7197221

Browse files
committed
feat(ssl): remove hardened HTTPS validation kit for proxied connections; add E2E test file tls-revocation.spec.js
1 parent ead8ab7 commit 7197221

File tree

5 files changed

+106
-33
lines changed

5 files changed

+106
-33
lines changed

package-lock.json

Lines changed: 8 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"feed": "^4.2.2",
9595
"form-data": "~4.0.0",
9696
"gamedig": "^4.2.0",
97-
"hardened-https-agent": "^1.3.0",
97+
"hardened-https-agent": "~1.5.0",
9898
"html-escaper": "^3.0.3",
9999
"http-cookie-agent": "~5.0.4",
100100
"http-graceful-shutdown": "~3.1.7",

server/model/monitor.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,6 @@ class Monitor extends BeanModel {
530530
const { httpAgent, httpsAgent } = Proxy.createAgents(proxy, {
531531
httpsAgentOptions: httpsAgentOptions,
532532
httpAgentOptions: httpAgentOptions,
533-
hardenedHttpsValidationKitOptions: hardenedHttpsValidationKitOptions,
534533
});
535534

536535
options.proxy = false;

server/proxy.js

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ const { debug } = require("../src/util");
66
const { UptimeKumaServer } = require("./uptime-kuma-server");
77
const { CookieJar } = require("tough-cookie");
88
const { createCookieAgent } = require("http-cookie-agent/http");
9-
const { HardenedHttpsValidationKit } = require("hardened-https-agent");
109

1110
class Proxy {
1211

@@ -88,16 +87,15 @@ class Proxy {
8887
/**
8988
* Create HTTP and HTTPS agents related with given proxy bean object
9089
* @param {object} proxy proxy bean object
91-
* @param {object} options http and https agent options with hardened https validation kit options
90+
* @param {object} options http and https agent options
9291
* @returns {{httpAgent: Agent, httpsAgent: Agent}} New HTTP and HTTPS agents
9392
* @throws Proxy protocol is unsupported
9493
*/
9594
static createAgents(proxy, options) {
96-
const { httpAgentOptions, httpsAgentOptions, hardenedHttpsValidationKitOptions } = options || {};
95+
const { httpAgentOptions, httpsAgentOptions } = options || {};
9796
let agent;
9897
let httpAgent;
9998
let httpsAgent;
100-
let validationKit = hardenedHttpsValidationKitOptions ? new HardenedHttpsValidationKit(hardenedHttpsValidationKitOptions) : null;
10199

102100
let jar = new CookieJar();
103101

@@ -115,50 +113,42 @@ class Proxy {
115113
debug(`Proxy URL: ${proxyUrl.toString()}`);
116114
debug(`HTTP Agent Options: ${JSON.stringify(httpAgentOptions)}`);
117115
debug(`HTTPS Agent Options: ${JSON.stringify(httpsAgentOptions)}`);
118-
if (validationKit) {
119-
debug(`Hardened HTTPS ValidationKit Options: ${JSON.stringify(hardenedHttpsValidationKitOptions)}`);
120-
}
121116

122117
switch (proxy.protocol) {
123118
case "http":
124-
case "https": {
119+
case "https":
125120
// eslint-disable-next-line no-case-declarations
126121
const HttpCookieProxyAgent = createCookieAgent(HttpProxyAgent);
122+
// eslint-disable-next-line no-case-declarations
123+
const HttpsCookieProxyAgent = createCookieAgent(HttpsProxyAgent);
124+
127125
httpAgent = new HttpCookieProxyAgent(proxyUrl.toString(), {
128126
...(httpAgentOptions || {}),
129127
...proxyOptions,
130128
});
131-
132-
// eslint-disable-next-line no-case-declarations
133-
const HttpsCookieProxyAgent = createCookieAgent(HttpsProxyAgent);
134-
let httpsProxyAgentOptions = {
129+
httpsAgent = new HttpsCookieProxyAgent(proxyUrl.toString(), {
135130
...(httpsAgentOptions || {}),
136131
...proxyOptions,
137-
};
138-
httpsAgent = new HttpsCookieProxyAgent(proxyUrl.toString(), !validationKit ? httpsProxyAgentOptions : validationKit.applyBeforeConnect(httpsProxyAgentOptions));
139-
validationKit?.attachToAgent(httpsAgent);
132+
});
133+
140134
break;
141-
}
142135
case "socks":
143136
case "socks5":
144137
case "socks5h":
145-
case "socks4": {
138+
case "socks4":
146139
// eslint-disable-next-line no-case-declarations
147140
const SocksCookieProxyAgent = createCookieAgent(SocksProxyAgent);
148-
let socksProxyAgentOptions = {
141+
agent = new SocksCookieProxyAgent(proxyUrl.toString(), {
149142
...httpAgentOptions,
150143
...httpsAgentOptions,
151144
tls: {
152145
rejectUnauthorized: httpsAgentOptions.rejectUnauthorized,
153146
},
154-
};
155-
agent = new SocksCookieProxyAgent(proxyUrl.toString(), !validationKit ? socksProxyAgentOptions : validationKit.applyBeforeConnect(socksProxyAgentOptions));
156-
validationKit?.attachToAgent(agent);
147+
});
157148

158149
httpAgent = agent;
159150
httpsAgent = agent;
160151
break;
161-
}
162152

163153
default: throw new Error(`Unsupported proxy protocol provided. ${proxy.protocol}`);
164154
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { expect, test } from "@playwright/test";
2+
import { login, restoreSqliteSnapshot, screenshot } from "../util-test";
3+
4+
/**
5+
* Create an HTTP monitor via the UI and navigate back to the dashboard.
6+
* @param {import('@playwright/test').Page} page - Playwright page instance.
7+
* @param {{ name: string, url: string, ignoreTls?: boolean }} param1 - Monitor parameters.
8+
* @returns {Promise<void>} Resolves when the monitor has been saved and the dashboard is shown.
9+
*/
10+
async function createHttpMonitor(page, { name, url, ignoreTls = false }) {
11+
await page.goto("./add");
12+
await login(page);
13+
14+
await expect(page.getByTestId("monitor-type-select")).toBeVisible();
15+
await page.getByTestId("monitor-type-select").selectOption("http");
16+
17+
await page.getByTestId("friendly-name-input").fill(name);
18+
await page.getByTestId("url-input").fill(url);
19+
20+
if (ignoreTls) {
21+
// No data-testid, so select by id
22+
await page.locator("#ignore-tls").check();
23+
}
24+
25+
await page.getByTestId("save-button").click();
26+
await page.waitForURL("/dashboard/*");
27+
}
28+
29+
/**
30+
* Wait until the monitor status badge matches the expected value.
31+
* @param {import('@playwright/test').Page} page - Playwright page instance.
32+
* @param {string} expected - Expected status text (case-insensitive). One of: up | down | pending | maintenance.
33+
* @param {number} timeoutMs - Max time to wait in milliseconds. Default: 15000.
34+
* @returns {Promise<void>} Resolves when the expected status appears or rejects on timeout.
35+
*/
36+
async function waitForStatus(page, expected, timeoutMs = 15000) {
37+
await expect(page.getByTestId("monitor-status")).toHaveText(expected, {
38+
ignoreCase: true,
39+
timeout: timeoutMs,
40+
});
41+
}
42+
43+
test.describe("TLS Revocation (Policy: OCSP Mixed with failHard=false)", () => {
44+
test.beforeEach(async ({ page }) => {
45+
await restoreSqliteSnapshot(page);
46+
});
47+
48+
test("if a domain is not revoked, the monitor should be UP", async ({ page }, testInfo) => {
49+
await createHttpMonitor(page, { name: "not-revoked",
50+
url: "https://www.example.com" });
51+
await waitForStatus(page, "up");
52+
await screenshot(testInfo, page);
53+
});
54+
55+
test("if a domain is OCSP-revoked, the monitor should be DOWN", async ({ page }, testInfo) => {
56+
await createHttpMonitor(page, { name: "ocsp-revoked",
57+
url: "https://aaacertificateservices.comodoca.com:444" });
58+
await waitForStatus(page, "down");
59+
await screenshot(testInfo, page);
60+
});
61+
62+
// TODO: Modify this test when we support CRLs
63+
test("if a domain is CRL-only revoked, the monitor should be UP (because we don't support CRLs yet and failHard=false)", async ({ page }, testInfo) => {
64+
await createHttpMonitor(page, { name: "crl-only-revoked",
65+
url: "https://revoked.badssl.com" });
66+
await waitForStatus(page, "up");
67+
await screenshot(testInfo, page);
68+
});
69+
70+
// StackOverflow uses Let's Encrypt which recently dropped OCSP support (c.f. https://letsencrypt.org/2025/08/06/ocsp-service-has-reached-end-of-life), making it a reliable test target
71+
test("if OCSP is unavailable for a domain, the monitor should be UP (because failHard=false)", async ({ page }, testInfo) => {
72+
await createHttpMonitor(page, { name: "ocsp-unavailable",
73+
url: "https://stackoverflow.com" });
74+
await waitForStatus(page, "up");
75+
await screenshot(testInfo, page);
76+
});
77+
78+
test("if ignoreTls=true for a domain, the monitor should be UP (regardless of the certificate status)", async ({ page }, testInfo) => {
79+
await createHttpMonitor(page, { name: "tls-ignored",
80+
url: "https://aaacertificateservices.comodoca.com:444",
81+
ignoreTls: true });
82+
await waitForStatus(page, "up");
83+
await screenshot(testInfo, page);
84+
});
85+
});

0 commit comments

Comments
 (0)