fix(security): treat quoted false as false in browser SSRF guards

This commit is contained in:
Yukipukii1
2026-04-26 05:23:55 +03:00
committed by Teknium
parent 2a0fc97c76
commit 7317d69f19
4 changed files with 46 additions and 3 deletions

View File

@@ -235,3 +235,21 @@ class TestPostRedirectSsrf:
assert result["success"] is True
assert result["url"] == final
class TestAllowPrivateUrlsConfig:
@pytest.fixture(autouse=True)
def _reset_cache(self):
browser_tool._allow_private_urls_resolved = False
browser_tool._cached_allow_private_urls = None
yield
browser_tool._allow_private_urls_resolved = False
browser_tool._cached_allow_private_urls = None
def test_browser_config_string_false_stays_disabled(self, monkeypatch):
monkeypatch.setattr(
"hermes_cli.config.read_raw_config",
lambda: {"browser": {"allow_private_urls": "false"}},
)
assert browser_tool._allow_private_urls() is False

View File

@@ -259,6 +259,20 @@ class TestGlobalAllowPrivateUrls:
with patch("hermes_cli.config.read_raw_config", return_value=cfg):
assert _global_allow_private_urls() is True
def test_config_security_string_false_stays_disabled(self, monkeypatch):
"""Quoted false must not opt out of SSRF protection."""
monkeypatch.delenv("HERMES_ALLOW_PRIVATE_URLS", raising=False)
cfg = {"security": {"allow_private_urls": "false"}}
with patch("hermes_cli.config.read_raw_config", return_value=cfg):
assert _global_allow_private_urls() is False
def test_config_browser_string_false_stays_disabled(self, monkeypatch):
"""Legacy browser.allow_private_urls also normalises quoted false."""
monkeypatch.delenv("HERMES_ALLOW_PRIVATE_URLS", raising=False)
cfg = {"browser": {"allow_private_urls": "false"}}
with patch("hermes_cli.config.read_raw_config", return_value=cfg):
assert _global_allow_private_urls() is False
def test_config_security_takes_precedence_over_browser(self, monkeypatch):
"""security section is checked before browser section."""
monkeypatch.delenv("HERMES_ALLOW_PRIVATE_URLS", raising=False)

View File

@@ -67,6 +67,7 @@ from typing import Dict, Any, Optional, List, Tuple
from pathlib import Path
from agent.auxiliary_client import call_llm
from hermes_constants import get_hermes_home
from utils import is_truthy_value
try:
from tools.website_policy import check_website_access
@@ -639,7 +640,11 @@ def _allow_private_urls() -> bool:
try:
from hermes_cli.config import read_raw_config
cfg = read_raw_config()
_cached_allow_private_urls = bool(cfg.get("browser", {}).get("allow_private_urls"))
browser_cfg = cfg.get("browser", {})
if isinstance(browser_cfg, dict):
_cached_allow_private_urls = is_truthy_value(
browser_cfg.get("allow_private_urls"), default=False
)
except Exception as e:
logger.debug("Could not read allow_private_urls from config: %s", e)
return _cached_allow_private_urls

View File

@@ -29,6 +29,8 @@ import os
import socket
from urllib.parse import urlparse
from utils import is_truthy_value
logger = logging.getLogger(__name__)
# Hostnames that should always be blocked regardless of IP resolution
@@ -107,12 +109,16 @@ def _global_allow_private_urls() -> bool:
cfg = read_raw_config()
# security.allow_private_urls (preferred)
sec = cfg.get("security", {})
if isinstance(sec, dict) and sec.get("allow_private_urls"):
if isinstance(sec, dict) and is_truthy_value(
sec.get("allow_private_urls"), default=False
):
_cached_allow_private = True
return _cached_allow_private
# browser.allow_private_urls (legacy fallback)
browser = cfg.get("browser", {})
if isinstance(browser, dict) and browser.get("allow_private_urls"):
if isinstance(browser, dict) and is_truthy_value(
browser.get("allow_private_urls"), default=False
):
_cached_allow_private = True
return _cached_allow_private
except Exception: