Repository navigation
fix(api): block private/loopback URLs in scrape targets, alerts, ClickHouse + cross-tenant label override - #32
Merged
Conversation
…ations, ClickHouse settings Addresses DeepSec CRITICAL `ssrf` and HIGH `ssrf` / `cross-tenant-id` findings. Authenticated tenants previously had several paths to make the Maple worker (or the customer-side collector) issue requests at attacker-chosen internal hosts and metadata endpoints, and to poison another tenant's telemetry by overriding the `maple_org_id` label. Changes: - `apps/api/src/lib/url-validator.ts` (new): `validateExternalUrlSync`, Effect wrapper, and `safeFetch`. Rejects non-http(s) schemes, plus loopback / RFC1918 / link-local / cloud-metadata / unique-local / IPv4-mapped IPv6 ranges by hostname/IP literal. `safeFetch` issues with `redirect: "manual"` and re-validates each Location target (max 5 hops). String-based by design — the API runs on Cloudflare Workers where `dns.lookup` is not available; this catches naive literal-IP and known-hostname SSRF and is paired with validate-on-write so stored URLs cannot point at internal targets in the first place. - `ScrapeTargetsService.validateUrl` now delegates to the shared validator; `probe` uses `safeFetch` instead of `fetch(... redirect: "follow")`. `validateLabelsJson` now rejects reserved label keys (`maple_*`, `__*`, `job`, `instance`). - `routes/sd.http.ts` applies user-supplied `labelsJson` BEFORE canonical system labels, so `maple_org_id` (and other `maple_*` / `__*` / `job` keys) cannot be overridden at the discovery layer even if a stale row pre-dates the new write-time validation. - `AlertsService.createDestination` and `updateDestination` now call `validateDestinationUrl` for slack / webhook / hazel URLs before persisting them. - `AlertDeliveryDispatch` swaps the four user-controlled-URL fetches (slack, webhook, hazel, hazel-oauth) for `safeFetch`. PagerDuty uses a hardcoded URL and is unchanged. - `OrgClickHouseSettingsService.normalizeHttpUrl` now blocks private/loopback hosts. The blank-password reuse path now requires the URL, user, and database to match the stored row before reusing the encrypted password — otherwise an admin could redirect the stored credential at a different host (`secrets-exposure` HIGH). Tests: `apps/api/src/lib/url-validator.test.ts` (30 cases covering scheme allowlist, IPv4/IPv6 private ranges, blocked hostnames, and `safeFetch` redirect re-validation). Existing `AlertsService` tests continue to pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
URL parsers canonicalise `::ffff:a.b.c.d` to the hex form `::ffff:HHHH:HHHH` with leading zeros stripped per group (e.g. `10.0.0.1` → `::ffff:a00:1`, `127.0.0.1` → `::ffff:7f00:1`). The original regex set matched only the dotted-quad form, leaving canonicalised mapped addresses unblocked. Replace the per-range regexes with a decoder that parses `::ffff:HHHH:HHHH` back to dotted-quad and reuses the IPv4 private range check. Add 5 new mapped-address regression cases. Found while exhaustively testing PR #1 against an attack matrix — URL parser canonicalisation is something I should have anticipated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Sep 28, 2026
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the 3 CRITICAL
ssrffindings and the bulk of the 13 HIGHssrfcluster, plus 2 HIGHcross-tenant-id.apps/api/src/lib/url-validator.ts:validateExternalUrlSync, Effect wrapper, andsafeFetch. Rejects non-http(s) schemes plus loopback / RFC1918 / link-local / cloud-metadata / unique-local / IPv4-mapped IPv6 ranges.safeFetchissues withredirect: \"manual\"and re-validates each Location target (max 5 hops).ScrapeTargetsService(validateUrl + probe),AlertsService(destination URL persistence),AlertDeliveryDispatch(slack/webhook/hazel/hazel-oauth fetches),NotificationDispatcher,OrgClickHouseSettingsService(URL validation + blank-password reuse only when URL/user/db unchanged).routes/sd.http.tsnow applies userlabelsJsonBEFORE canonical labels somaple_org_id/maple_*/__*/jobcannot be overridden via service discovery.ScrapeTargetsService.validateLabelsJsonalso rejects reserved keys at write time.bd6d81ab): URL parsers canonicalise::ffff:127.0.0.1to::ffff:7f00:1(hex form, leading zeros stripped). The original regex matched only the dotted-quad form, so an IPv4-mapped IPv6 attack URL bypassed the check. Replaced with a decoder that parses the hex form back to dotted-quad and reuses the IPv4 check.Note on Cloudflare Workers
The API runs on Workers, where
dns.lookupis unavailable. This validator is string-based: it catches naive literal-IP and known-hostname SSRF and is paired with validate-on-write so stored URLs cannot point at internal targets in the first place. DNS-rebinding is a known limitation; the customer-side collector that consumes service discovery is the bigger residual risk and should be addressed via egress network policy.Test plan
url-validator.test.ts(scheme allowlist, IPv4/IPv6 private ranges, IPv4-mapped IPv6 in both dotted-quad and hex form, redirect re-validation).AlertsServicetests still pass.bun turbo typecheckpasses across all 18 packages.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmithwith what you need.