Skip to content

fix(security): add SSRF protection to goto command#125

Open
haoyu-haoyu wants to merge 1 commit intogarrytan:mainfrom
haoyu-haoyu:fix/ssrf-url-validation-goto
Open

fix(security): add SSRF protection to goto command#125
haoyu-haoyu wants to merge 1 commit intogarrytan:mainfrom
haoyu-haoyu:fix/ssrf-url-validation-goto

Conversation

@haoyu-haoyu
Copy link

Summary

The goto command accepts any URL without restriction, allowing navigation to private networks, cloud metadata endpoints, and localhost services. In an agentic workflow where URLs may come from untrusted LLM output, this is a Server-Side Request Forgery (SSRF) vector.

What's blocked

Category Examples Attack vector
Private IPv4 10.x.x.x, 172.16-31.x.x, 192.168.x.x Internal service access
Cloud IMDS 169.254.169.254, 100.100.100.200 AWS/GCP/Alibaba credential theft
Loopback 127.0.0.1, ::1 Localhost service probe
Reserved hostnames localhost, metadata.google.internal Metadata endpoint access
Reserved suffixes *.local, *.internal, *.localhost mDNS / internal DNS

What's allowed

All public URLs continue to work unchanged. The validation runs before page.goto() and throws a clear error message explaining why the URL was blocked.

Implementation

Single isBlockedURL() function added to write-commands.ts:

  • Parses URL, extracts hostname
  • Checks against explicit blocklist, suffix blocklist, IPv4 range checks
  • Returns null (allowed) or error message string (blocked)
  • Malformed URLs pass through to let Playwright handle them normally

Test plan

# Blocked:
browse goto http://169.254.169.254/latest/meta-data/  # AWS IMDS
browse goto http://localhost:8080/admin                # loopback
browse goto http://192.168.1.1/                        # private network
browse goto http://metadata.google.internal/           # GCP IMDS

# Allowed:
browse goto https://example.com                        # public URL
browse goto https://github.com                         # public URL

Fixes #17

The goto command previously accepted any URL without restriction,
allowing navigation to private networks, cloud metadata endpoints
(169.254.169.254, metadata.google.internal), and localhost services.
In an agentic workflow where URLs may come from untrusted input, this
is an SSRF vector.

Fix: add isBlockedURL() validation before page.goto() that blocks:
- Private IPv4 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Link-local / cloud IMDS (169.254.0.0/16, 100.64.0.0/10)
- Loopback (127.0.0.0/8, ::1)
- Reserved hostnames (localhost, metadata.google.internal, etc.)
- Reserved suffixes (.local, .internal, .localhost)

Fixes garrytan#17

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

[Security] SSRF and local resource access via unrestricted URL handling

1 participant