Background
Hermes v0.13 (#21228, closes #16234) — the browser tool's hybrid routing (mix of local fetch + remote rendering) could be coerced into hitting
cloud-metadata endpoints (169.254.169.254 / fd00:ec2::254 / metadata.google.internal) because the SSRF floor was only enforced on the legacy
fetch path, not the new browser-rendering path.
Current state in CrowClaw
Scope
- Modified:
packages/tools/src/ssrf-blocklist.ts (or wherever the central blocklist lives)
- New constant:
CLOUD_METADATA_HOSTS = ['169.254.169.254', 'fd00:ec2::254', 'metadata.google.internal', 'metadata.azure.com', '100.100.100.200']
- Apply at: every outbound HTTP from tools/* (fetch, browser, vision, image)
Implementation sketch
- Centralize SSRF preflight into a single
assertSafeUrl(url, { kind }) helper
kind: 'browser' / 'fetch' / 'vision' / 'image' all hit the same blocklist + DNS resolution check
- Cloud-metadata hosts return immediately with
SsrfDenied error envelope; audit-log records the attempt
- Add
tests/cloud-metadata-floor.test.ts covering all four kinds
Acceptance criteria
Effort
M — touches multiple tool entry points; centralization is worth it.
Effect
Blocks the canonical cloud-credential exfiltration path. Even without a browser tool today, file this so future browser/headless work inherits
the floor — Hermes shipped it preemptively for the same reason.
Source
Hermes #21228, #16234 · CrowClaw current: packages/tools/src/index.ts (WORKSPACE_FETCH_BLOCKLIST), packages/tools/src/vision.ts
Background
Hermes v0.13 (#21228, closes #16234) — the browser tool's hybrid routing (mix of local fetch + remote rendering) could be coerced into hitting
cloud-metadata endpoints (169.254.169.254 / fd00:ec2::254 / metadata.google.internal) because the SSRF floor was only enforced on the legacy
fetch path, not the new browser-rendering path.
Current state in CrowClaw
packages/tools/src/vision.tswas hardened in v0.8.2 fix(security): vision.analyze skips SSRF preflight on image URL fetch #261 with DNS-aware SSRF preflight forvision.analyze.packages/tools/src/index.tsdefinesweb.fetchwithWORKSPACE_FETCH_BLOCKLISTcovering RFC1918 but does not block cloud-metadataendpoints explicitly (192.0.0.0/24 was added in v0.8.2 fix(security): add 192.0.0.0/24 + 6to4/Teredo IPv6 ranges to SSRF blocklist #280 — but cloud-metadata floor still missing for the browser execution path).
Scope
packages/tools/src/ssrf-blocklist.ts(or wherever the central blocklist lives)CLOUD_METADATA_HOSTS = ['169.254.169.254', 'fd00:ec2::254', 'metadata.google.internal', 'metadata.azure.com', '100.100.100.200']Implementation sketch
assertSafeUrl(url, { kind })helperkind: 'browser'/'fetch'/'vision'/'image'all hit the same blocklist + DNS resolution checkSsrfDeniederror envelope; audit-log records the attempttests/cloud-metadata-floor.test.tscovering all four kindsAcceptance criteria
SsrfDeniedregardless of tool kindfd00:ec2::254,fe80::/10) blockedassertSafeUrl('http://attacker.example.com')where DNS resolves to169.254.169.254is blocked post-resolutionsecurity:ssrf_deniedcarrieshost,resolvedIp,kindEffort
M — touches multiple tool entry points; centralization is worth it.
Effect
Blocks the canonical cloud-credential exfiltration path. Even without a browser tool today, file this so future browser/headless work inherits
the floor — Hermes shipped it preemptively for the same reason.
Source
Hermes #21228, #16234 · CrowClaw current:
packages/tools/src/index.ts(WORKSPACE_FETCH_BLOCKLIST),packages/tools/src/vision.ts