Skip to content

fix(security): add SSRF protection to browser_navigate#3058

Merged
teknium1 merged 3 commits intomainfrom
hermes/hermes-c75e42c8
Mar 25, 2026
Merged

fix(security): add SSRF protection to browser_navigate#3058
teknium1 merged 3 commits intomainfrom
hermes/hermes-c75e42c8

Conversation

@teknium1
Copy link
Copy Markdown
Contributor

Summary

Salvage of PR #3041 by @0xbyt4 — cherry-picked onto current main with two hardening improvements.

browser_navigate() had check_website_access() (domain blocklist) but was missing is_safe_url() (SSRF/private IP check). The agent could navigate the browser to 127.0.0.1, 169.254.169.254 (cloud metadata), 192.168.x.x, etc. The other URL-capable tools (web_tools.py, vision_tools.py) already had this check.

Follow-up hardening

  1. Fail-closed fallback: Changed the import fallback from lambda url: True (allow all) to lambda url: False (block all). Security guards should never fail-open — if the url_safety module can't import, block everything rather than allowing SSRF.

  2. Post-redirect SSRF check: After navigation, verifies the final URL isn't a private/internal address. If a public URL redirected to 169.254.169.254 or localhost, navigates to about:blank and returns an error. This prevents the model from reading internal content via subsequent browser_snapshot calls. Mirrors the redirect protection already in vision_tools.py.

Tests

6175 passed. 4 pre-existing cron failures (unrelated).

Closes #3041

0xbyt4 and others added 3 commits March 25, 2026 15:10
browser_navigate() only checked the website blocklist policy but did
not call is_safe_url() to block private/internal addresses. This
allowed the agent to navigate to localhost, cloud metadata endpoints
(169.254.169.254), and private network IPs via the browser.

web_tools and vision_tools already had this check. Added the same
is_safe_url() pre-flight validation before the blocklist check in
browser_navigate().
Move is_safe_url import to module level so it can be monkeypatched
in tests. Update test_browser_navigate_returns_policy_block to mock
_is_safe_url so the SSRF check passes and the policy check is reached.
Follow-up to cherry-picked PR #3041:

1. Fail-closed fallback: if url_safety module can't import, block all
   URLs instead of allowing all. Security guards should never fail-open.

2. Post-redirect SSRF check: after navigation, verify the final URL
   isn't a private/internal address. If a public URL redirected to
   169.254.169.254 or localhost, navigate to about:blank and return
   an error — prevents the model from reading internal content via
   subsequent browser_snapshot calls.
@teknium1 teknium1 merged commit ab548a9 into main Mar 25, 2026
2 checks passed
InB4DevOps pushed a commit to InB4DevOps/hermes-agent that referenced this pull request Mar 25, 2026
…3058)

* fix(security): add SSRF protection to browser_navigate

browser_navigate() only checked the website blocklist policy but did
not call is_safe_url() to block private/internal addresses. This
allowed the agent to navigate to localhost, cloud metadata endpoints
(169.254.169.254), and private network IPs via the browser.

web_tools and vision_tools already had this check. Added the same
is_safe_url() pre-flight validation before the blocklist check in
browser_navigate().

* fix: move SSRF import to module level, fix policy test mock

Move is_safe_url import to module level so it can be monkeypatched
in tests. Update test_browser_navigate_returns_policy_block to mock
_is_safe_url so the SSRF check passes and the policy check is reached.

* fix(security): harden browser SSRF protection

Follow-up to cherry-picked PR NousResearch#3041:

1. Fail-closed fallback: if url_safety module can't import, block all
   URLs instead of allowing all. Security guards should never fail-open.

2. Post-redirect SSRF check: after navigation, verify the final URL
   isn't a private/internal address. If a public URL redirected to
   169.254.169.254 or localhost, navigate to about:blank and return
   an error — prevents the model from reading internal content via
   subsequent browser_snapshot calls.

---------

Co-authored-by: 0xbyt4 <35742124+0xbyt4@users.noreply.github.com>
outsourc-e pushed a commit to outsourc-e/hermes-agent that referenced this pull request Mar 26, 2026
…3058)

* fix(security): add SSRF protection to browser_navigate

browser_navigate() only checked the website blocklist policy but did
not call is_safe_url() to block private/internal addresses. This
allowed the agent to navigate to localhost, cloud metadata endpoints
(169.254.169.254), and private network IPs via the browser.

web_tools and vision_tools already had this check. Added the same
is_safe_url() pre-flight validation before the blocklist check in
browser_navigate().

* fix: move SSRF import to module level, fix policy test mock

Move is_safe_url import to module level so it can be monkeypatched
in tests. Update test_browser_navigate_returns_policy_block to mock
_is_safe_url so the SSRF check passes and the policy check is reached.

* fix(security): harden browser SSRF protection

Follow-up to cherry-picked PR NousResearch#3041:

1. Fail-closed fallback: if url_safety module can't import, block all
   URLs instead of allowing all. Security guards should never fail-open.

2. Post-redirect SSRF check: after navigation, verify the final URL
   isn't a private/internal address. If a public URL redirected to
   169.254.169.254 or localhost, navigate to about:blank and return
   an error — prevents the model from reading internal content via
   subsequent browser_snapshot calls.

---------

Co-authored-by: 0xbyt4 <35742124+0xbyt4@users.noreply.github.com>
StreamOfRon pushed a commit to StreamOfRon/hermes-agent that referenced this pull request Mar 29, 2026
…3058)

* fix(security): add SSRF protection to browser_navigate

browser_navigate() only checked the website blocklist policy but did
not call is_safe_url() to block private/internal addresses. This
allowed the agent to navigate to localhost, cloud metadata endpoints
(169.254.169.254), and private network IPs via the browser.

web_tools and vision_tools already had this check. Added the same
is_safe_url() pre-flight validation before the blocklist check in
browser_navigate().

* fix: move SSRF import to module level, fix policy test mock

Move is_safe_url import to module level so it can be monkeypatched
in tests. Update test_browser_navigate_returns_policy_block to mock
_is_safe_url so the SSRF check passes and the policy check is reached.

* fix(security): harden browser SSRF protection

Follow-up to cherry-picked PR NousResearch#3041:

1. Fail-closed fallback: if url_safety module can't import, block all
   URLs instead of allowing all. Security guards should never fail-open.

2. Post-redirect SSRF check: after navigation, verify the final URL
   isn't a private/internal address. If a public URL redirected to
   169.254.169.254 or localhost, navigate to about:blank and return
   an error — prevents the model from reading internal content via
   subsequent browser_snapshot calls.

---------

Co-authored-by: 0xbyt4 <35742124+0xbyt4@users.noreply.github.com>
@dj0024javia
Copy link
Copy Markdown

dj0024javia commented Mar 29, 2026

there should be a flag to at least let user control the behaviour.

this completly broke the local testing/fixing iteration loop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants