Summary
When the global showURL application setting is enabled (labeled "Display IP/URL on status page" in the UI), the public status page API endpoint (GET /api/v1/status-page/:url) returns the full monitor object — including the secret field containing Bearer tokens used for HTTP monitor authentication — to any unauthenticated visitor. This exposes sensitive credentials used to authenticate the monitoring server against monitored endpoints.
Details
Root cause: In server/src/controllers/statusPageController.ts lines 119-127, the code conditionally strips sensitive fields from monitor objects returned on public status pages:
const normalizedMonitors = sortedMonitors.map((monitor) => {
const normalizedChecks = NormalizeData(monitor.recentChecks, 10, 100);
if (!showURL) {
const { url, port, secret, notifications, ...rest } = monitor;
return { ...rest, checks: normalizedChecks };
}
return { ...monitor, checks: normalizedChecks }; // Line 126 — includes ALL fields including secret
});
When showURL is true, line 126 spreads the entire monitor object into the response, including the secret field. The secret is correctly stripped when showURL is false (line 123), but when the admin enables "Display IP/URL," the intent is to show URL and port — not authentication credentials.
The secret field contains Bearer tokens used in monitor HTTP requests (HttpProvider.ts line 150: headers: monitor.secret ? { Authorization: \Bearer ${secret}` } : undefined`).
Taint path:
- Admin enables
showURL in global app settings (UI label: "Display IP/URL on status page")
- Admin creates published status page with HTTP monitors that have
secret configured
- Any unauthenticated user accesses
GET /api/v1/status-page/:url
verifyStatusPageAccess middleware allows unauthenticated access to published pages
- Controller fetches full monitor objects via
monitorsRepository.findByIds() (no field exclusion)
- Because
showURL is true, the full monitor object (including secret) is in the JSON response
UI vs API discrepancy: The client-side BaseStatusPage.tsx only renders monitor.url, not monitor.secret. However, the API JSON response contains the secret in plaintext, extractable via curl.
Config-backed exposure: showURL defaults to false (AppSettings.ts line 39). The UI label says "Display IP/URL on status page" with no warning about credential exposure.
Affected commit: ce4cb63
PoC
Prerequisites: Checkmate instance with an HTTP monitor configured with a secret (Bearer token), admin has enabled showURL and published a status page.
# Step 1: Create HTTP monitor with Bearer token
TOKEN=$(curl -s -X POST http://localhost:52345/api/v1/auth/login -H 'Content-Type: application/json' -d '{"email":"admin@example.com","password":"AdminPass1!"}' | jq -r '.data.token')
curl -s -X POST http://localhost:52345/api/v1/monitors/ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"type":"http","name":"internal-api","url":"https://internal-api.example.com/health","secret":"sk-prod-service-account-token-abc123","interval":60000}'
# Step 2: Enable showURL
curl -s -X PATCH http://localhost:52345/api/v1/settings/ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"showURL":true}'
# Step 3: Create and publish status page with the monitor
curl -s -X POST http://localhost:52345/api/v1/status-page/ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"companyName":"Example","url":"example-status","isPublished":true,"monitors":["<monitor-id>"],"type":"uptime"}'
# Step 4: Unauthenticated access exposes the secret
# No auth token needed — published status pages are public
curl -s http://localhost:52345/api/v1/status-page/example-status | jq '.data.monitors[0].secret'
# Expected output: "sk-prod-service-account-token-abc123"
Negative case: With showURL set to false, the same request returns null for the secret field — confirming the conditional stripping works when the setting is disabled.
Impact
This allows any unauthenticated user who discovers a published status page URL to extract Bearer tokens used by the Checkmate server to authenticate against monitored HTTP endpoints:
- Credential theft: Authentication tokens (API keys, service account tokens) are exposed in plaintext
- Lateral movement: Stolen credentials can access internal APIs
- Cloud service compromise: Tokens for cloud APIs (AWS, GCP, Azure) are exposed
- No authentication required: The status page API is publicly accessible
Limiting factor: Requires showURL to be enabled by an admin (defaults to false). The UI label "Display IP/URL on status page" gives no indication that credentials will also be exposed.
CVSS 3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
CWE: CWE-200
Suggested fix: Always strip secret and notifications regardless of showURL:
const { secret, notifications, ...rest } = monitor;
return { ...rest, checks: normalizedChecks };
Patches
Fixed in v3.9.2 (commit cc1814f). The public status-page payload now uses a strict allowlist of monitor display fields; secret and all other internal fields are never returned regardless of the showURL setting.
If you cannot upgrade immediately: disable the "Display IP/URL on status page" setting or unpublish status pages until you are on 3.9.2+.
Credits
- Thai Son Dinh from VinSOC Labs (R&D)
Summary
When the global
showURLapplication setting is enabled (labeled "Display IP/URL on status page" in the UI), the public status page API endpoint (GET /api/v1/status-page/:url) returns the full monitor object — including thesecretfield containing Bearer tokens used for HTTP monitor authentication — to any unauthenticated visitor. This exposes sensitive credentials used to authenticate the monitoring server against monitored endpoints.Details
Root cause: In
server/src/controllers/statusPageController.tslines 119-127, the code conditionally strips sensitive fields from monitor objects returned on public status pages:When
showURListrue, line 126 spreads the entire monitor object into the response, including thesecretfield. Thesecretis correctly stripped whenshowURLisfalse(line 123), but when the admin enables "Display IP/URL," the intent is to show URL and port — not authentication credentials.The
secretfield contains Bearer tokens used in monitor HTTP requests (HttpProvider.tsline 150:headers: monitor.secret ? { Authorization: \Bearer ${secret}` } : undefined`).Taint path:
showURLin global app settings (UI label: "Display IP/URL on status page")secretconfiguredGET /api/v1/status-page/:urlverifyStatusPageAccessmiddleware allows unauthenticated access to published pagesmonitorsRepository.findByIds()(no field exclusion)showURListrue, the full monitor object (includingsecret) is in the JSON responseUI vs API discrepancy: The client-side
BaseStatusPage.tsxonly rendersmonitor.url, notmonitor.secret. However, the API JSON response contains the secret in plaintext, extractable viacurl.Config-backed exposure:
showURLdefaults tofalse(AppSettings.tsline 39). The UI label says "Display IP/URL on status page" with no warning about credential exposure.Affected commit: ce4cb63
PoC
Prerequisites: Checkmate instance with an HTTP monitor configured with a
secret(Bearer token), admin has enabledshowURLand published a status page.Negative case: With
showURLset tofalse, the same request returnsnullfor thesecretfield — confirming the conditional stripping works when the setting is disabled.Impact
This allows any unauthenticated user who discovers a published status page URL to extract Bearer tokens used by the Checkmate server to authenticate against monitored HTTP endpoints:
Limiting factor: Requires
showURLto be enabled by an admin (defaults tofalse). The UI label "Display IP/URL on status page" gives no indication that credentials will also be exposed.CVSS 3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
CWE: CWE-200
Suggested fix: Always strip
secretandnotificationsregardless ofshowURL:Patches
Fixed in v3.9.2 (commit cc1814f). The public status-page payload now uses a strict allowlist of monitor display fields;
secretand all other internal fields are never returned regardless of theshowURLsetting.If you cannot upgrade immediately: disable the "Display IP/URL on status page" setting or unpublish status pages until you are on 3.9.2+.
Credits