Skip to content

fix(security): block SSRF via user-supplied ICS feed URLs#259

Open
ttonyxx wants to merge 1 commit into
security/02-session-cookie-flagsfrom
security/03-ics-ssrf-guard
Open

fix(security): block SSRF via user-supplied ICS feed URLs#259
ttonyxx wants to merge 1 commit into
security/02-session-cookie-flagsfrom
security/03-ics-ssrf-guard

Conversation

@ttonyxx

@ttonyxx ttonyxx commented May 29, 2026

Copy link
Copy Markdown
Collaborator

Severity: 🟡 Medium — authenticated SSRF

Stacked on #258 (→ #257). Targets that branch so the diff is scoped to the SSRF fix.

The problem

/user/add-ics-calendar-account stores an arbitrary user-supplied feedUrl, and ICSCalendar.GetCalendarEvents later fetched it with:

resp, err := http.Get(cal.FeedURL)

The URL is fully attacker-controlled, giving any signed-in user an SSRF primitive. They can make the server fetch:

  • the cloud metadata endpoint http://169.254.169.254/… (instance credential theft),
  • internal-only services reachable from the host,
  • localhost admin interfaces.

The fix

New safeGet in services/calendar/safe_http.go:

  • Allows only http/https schemes.
  • Dials through an http.Client whose net.Dialer.Control hook rejects any non-public resolved IP: loopback, RFC1918 private, link-local (incl. 169.254.169.254), unspecified, multicast, and CGNAT 100.64.0.0/10.
  • Because the check runs on the resolved address at connect time, it also defeats DNS rebinding and re-validates on every redirect hop.

ICSCalendar.GetCalendarEvents now calls safeGet. Added unit tests for the IP classifier and scheme rejection (both pass).

=== RUN   TestIsDisallowedIP            --- PASS
=== RUN   TestSafeGetRejectsNonHTTPSchemes --- PASS

🤖 Generated with Claude Code

## Why this change is needed

`/user/add-ics-calendar-account` lets an authenticated user store an arbitrary
`feedUrl`, and `ICSCalendar.GetCalendarEvents` later fetched it with a plain
`http.Get(cal.FeedURL)`. Because the URL is fully attacker-controlled, this is
a Server-Side Request Forgery (SSRF) primitive: a user could point the feed at

- the cloud metadata endpoint (`http://169.254.169.254/...`) to try to steal
  instance credentials,
- internal-only services reachable from the server, or
- `localhost` admin interfaces,

and have the server issue the request from inside the trust boundary.

## What this does

- Adds `safeGet` (services/calendar/safe_http.go), which only allows `http`/
  `https` URLs and dials through a client whose `Control` hook rejects any
  connection to a non-public IP (loopback, RFC1918 private, link-local incl.
  169.254.169.254, unspecified, multicast, and CGNAT 100.64.0.0/10).
- Because the check runs on the resolved address at connect time, it also
  defeats DNS-rebinding and re-validates on every redirect hop.
- `ICSCalendar.GetCalendarEvents` now uses `safeGet` instead of `http.Get`.
- Adds unit tests for the IP classification and scheme rejection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ttonyxx ttonyxx force-pushed the security/02-session-cookie-flags branch from d2919b6 to e3c36eb Compare May 29, 2026 18:49
@ttonyxx ttonyxx force-pushed the security/03-ics-ssrf-guard branch from c5364ea to 62df3e5 Compare May 29, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant