fix(uptime): allow RFC 1918 IPs for admin-configured monitors#855
fix(uptime): allow RFC 1918 IPs for admin-configured monitors#855Wikid82 merged 13 commits intodevelopmentfrom
Conversation
…e-non-major-updates
…n-major-updates chore(deps): update release-drafter/release-drafter digest to 44a942e (feature/beta-release)
HTTP/HTTPS uptime monitors targeting LAN addresses (192.168.x.x, 10.x.x.x, 172.16.x.x) permanently reported 'down' on fresh installs because SSRF protection rejects RFC 1918 ranges at two independent checkpoints: the URL validator (DNS-resolution layer) and the safe dialer (TCP-connect layer). Fixing only one layer leaves the monitor broken in practice. - Add IsRFC1918() predicate to the network package covering only the three RFC 1918 CIDRs; 169.254.x.x (link-local / cloud metadata) and loopback are intentionally excluded - Add WithAllowRFC1918() functional option to both SafeHTTPClient and ValidationConfig; option defaults to false so existing behaviour is unchanged for every call site except uptime monitors - In uptime_service.go, pass WithAllowRFC1918() to both ValidateExternalURL and NewSafeHTTPClient together; a coordinating comment documents that both layers must be relaxed as a unit - 169.254.169.254 and the full 169.254.0.0/16 link-local range remain unconditionally blocked; the cloud-metadata error path is preserved - 21 new tests across three packages, including an explicit regression guard that confirms RFC 1918 blocks are still applied without the option set (TestValidateExternalURL_RFC1918BlockedByDefault) Fixes issues 6 and 7 from the fresh-install bug report.
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
✅ Supply Chain Verification Results✅ PASSED 📦 SBOM Summary
🔍 Vulnerability Scan
📎 Artifacts
Generated by Supply Chain Verification workflow • View Details |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Enables admin-configured uptime monitors to target RFC 1918 private IP ranges by adding an explicit opt-in (WithAllowRFC1918) to both SSRF enforcement layers (URL validation + dial-time guard), plus accompanying unit/integration tests and documentation.
Changes:
- Add
AllowRFC1918+WithAllowRFC1918()to bothinternal/securityURL validation andinternal/networksafe dialer logic (with sharednetwork.IsRFC1918predicate). - Wire the option through
UptimeService.checkMonitorfor HTTP/HTTPS monitors (dual-layer relaxation). - Add tests for RFC 1918 behavior across network/security/services and update planning/QA docs.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
backend/internal/network/safeclient.go |
Adds IsRFC1918, option flag, and safe dialer bypass/selection behavior for RFC 1918. |
backend/internal/security/url_validator.go |
Adds AllowRFC1918 config + option and skips private-IP blocking for RFC 1918 when enabled (including IPv4-mapped IPv6 handling). |
backend/internal/services/uptime_service.go |
Passes WithAllowRFC1918() to both validator and safe HTTP client; documents rationale. |
backend/internal/network/safeclient_test.go |
Adds RFC 1918 predicate and safe dialer/client tests. |
backend/internal/security/url_validator_test.go |
Adds RFC 1918 validation option tests (including mapped IPv6 cases). |
backend/internal/services/uptime_service_test.go |
Adds integration tests for monitor checks. |
docs/plans/current_spec.md |
Documents the implementation plan, invariants, and test plan. |
docs/reports/qa_report_pr3.md |
Adds QA/security report for PR-3 verification steps and outcomes. |
.github/workflows/auto-changelog.yml |
Updates pinned release-drafter action SHA. |
You can also share your feedback on Copilot code review. Take the survey.
…n-major-updates chore(deps): update module github.com/greenpau/caddy-security to v1.1.49 (feature/beta-release)
…fore error reporting - IPv4-mapped cloud metadata (::ffff:169.254.169.254) previously fell through the IPv4-mapped IPv6 detection block and returned the generic private-IP error instead of the cloud-metadata error, making the two cases inconsistent - The IPv4-mapped error path used ip.String() (the raw ::ffff:… form) directly rather than sanitizeIPForError, potentially leaking the unsanitized IPv6 address in error messages visible to callers - Now extracts the IPv4 from the mapped address before both the cloud-metadata comparison and the sanitization call, so ::ffff:169.254.169.254 produces the same "access to cloud metadata endpoints is blocked" error as 169.254.169.254 and the error message is always sanitized through the shared helper - Updated the corresponding test to assert the cloud-metadata message and the absence of the raw IPv6 representation in the error text
…message The settings handler SSRF test table expected the generic "private ip" error string for the cloud-metadata case (169.254.169.254). After the url_validator was updated to return a distinct "cloud metadata" error for that address, the handler test's errorContains check failed on every CI run. Updated the test case expectation from "private" to "cloud metadata" to match the more precise error message now produced by the validator.
Problem
HTTP/HTTPS uptime monitors targeting LAN addresses (
192.168.x.x,10.x.x.x,172.16.x.x) permanently reported "down" on fresh installs. The root cause: SSRF protection operates at two independent checkpoints — the URL validator (DNS-resolution phase) and the safe dialer (TCP-connect phase). Fixing only one layer still blocks the connection at the other.Fixes issues 6 and 7 from the fresh-install bug report.
Solution
Add a
WithAllowRFC1918()functional option to both protection layers and pass it fromuptime_service.goto both simultaneously. The option is off by default — no other call site is affected.RFC 1918 scope (strict)
Only the three canonical private ranges are permitted when the option is active:
10.0.0.0/8172.16.0.0/12192.168.0.0/16Still unconditionally blocked:
169.254.0.0/16— link-local / cloud metadata (including169.254.169.254)127.0.0.0/8) — unlessWithAllowLocalhost()is also setChanges
backend/internal/network/safeclient.goIsRFC1918()predicate;AllowRFC1918field +WithAllowRFC1918()option; safeDialer bypass in both validation and selection loopsbackend/internal/security/url_validator.goAllowRFC1918field +WithAllowRFC1918()option; Phase 4 private-IP loop bypass for both IPv4-mapped and plain IPv6 addressesbackend/internal/services/uptime_service.goWithAllowRFC1918()passed to bothValidateExternalURLandNewSafeHTTPClientwith a coordinating commentTests
21 new tests across 3 packages (92.1% / 94.1% / 86.0% coverage):
TestIsRFC1918_*— comprehensive predicate coverage including boundary addresses, nil input, IPv4-mapped IPv6TestSafeDialer_AllowRFC1918_*— direct dial to192.168.1.1, metadata still blocked, loopback still blockedTestValidateExternalURL_WithAllowRFC1918_*— all three ranges permitted; metadata and loopback still rejectedTestValidateExternalURL_RFC1918BlockedByDefault— regression guard: confirms RFC 1918 is blocked when option is absentTestCheckMonitor_*— uptime service integration testsSecurity
169.254.169.254protection preserved — falls through RFC 1918 check (not in CIDRs) and hits the cloud-metadata error path unchangedIsRFC1918(nil)returnsfalse— nil-safeValidateExternalURLcall sites are untouchedAllowRFC1918defaults tofalse— no behaviour change without explicit opt-inRequireManagementAccess()) to configure a monitor