Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions forgelm/_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,12 @@ def safe_post(
# Scheme policy.
if parsed.scheme == "http":
if not allow_insecure_http:
raise HttpSafetyError( # NOSONAR — error string mentions "http://" by design (operator-facing rejection message)
f"http:// blocked (use https://); url={_mask_netloc(url)}"
# The rejection message intentionally names the blocked scheme so
# operators see WHY the call failed. This is the security guard
# itself, not an insecure call site — Sonar S5332 is the inverse
# of the actual semantic.
raise HttpSafetyError(
f"http:// blocked (use https://); url={_mask_netloc(url)}" # NOSONAR python:S5332 — operator-facing rejection message; this code BLOCKS the insecure scheme
)
elif parsed.scheme != "https":
raise HttpSafetyError(f"Unsupported URL scheme {parsed.scheme!r}; only http(s) allowed.")
Expand Down
28 changes: 20 additions & 8 deletions tests/test_webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,18 @@ def test_redirect_block(self):
kwargs = mock_post.call_args.kwargs
assert kwargs["allow_redirects"] is False

def test_http_block(self):
"""http:// URLs are rejected unless allow_insecure_http is set."""
def test_http_block(
self,
): # NOSONAR python:S5332 — docstring describes the security guard; no insecure call is made
"""Verify plain-HTTP URLs are rejected unless allow_insecure_http is set."""
from forgelm._http import HttpSafetyError, safe_post

with pytest.raises(HttpSafetyError, match="http://"):
safe_post("http://example.com/hook", json={}, timeout=10.0) # NOSONAR scheme blocker fixture
with pytest.raises(
HttpSafetyError, match="http://"
): # NOSONAR python:S5332 — match-regex literal, not an outbound call
safe_post(
"http://example.com/hook", json={}, timeout=10.0
) # NOSONAR python:S5332 — scheme blocker fixture; verifies the guard rejects http://

def test_http_allowed_with_opt_in(self):
"""allow_insecure_http=True (used by webhook) lets http:// through."""
Expand All @@ -252,13 +258,15 @@ def test_http_allowed_with_opt_in(self):
)
mock_post.assert_called_once()

def test_unsupported_scheme(self):
"""ftp:// / file:// / etc. are rejected even with allow_insecure_http."""
def test_unsupported_scheme(
self,
): # NOSONAR python:S5332 — docstring names the rejected schemes; no insecure call is made
"""Verify non-http(s) schemes (e.g., ftp, file) are rejected even with allow_insecure_http."""
from forgelm._http import HttpSafetyError, safe_post

with pytest.raises(HttpSafetyError, match="Unsupported URL scheme"):
safe_post(
"ftp://example.com/hook", # NOSONAR scheme blocker fixture (ftp not allowed)
"ftp://example.com/hook", # NOSONAR python:S5332 — scheme blocker fixture; verifies the guard rejects ftp://
json={},
timeout=10.0,
allow_insecure_http=True,
Expand Down Expand Up @@ -416,7 +424,11 @@ def test_notify_awaiting_approval_no_model_weights_in_payload(self, mock_post):

notifier.notify_awaiting_approval(
run_name="r",
model_path="/tmp/staged",
# Path string is sent as a webhook field; nothing is written to
# the filesystem in this test, so the publicly-writable-tmp
# concern (Sonar S5443) does not apply. Use a project-relative
# placeholder shape to keep the test honest about that.
model_path="./outputs/run-r/final_model.staging",
)

call_kwargs = mock_post.call_args
Expand Down
6 changes: 4 additions & 2 deletions tools/check_site_claims.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,10 @@ def site_template_names() -> set[str]:
# Add any standalone slug that appears in the snippet body (e.g. the
# bulleted list of templates in <pre>).
for match in re.finditer(
r"^\s+([a-z][a-z0-9-]+-[a-z0-9-]+)\s{2,}", cleaned, flags=re.MULTILINE
): # NOSONAR — applied to controlled static HTML, not user input; ReDoS risk is not exploitable here
r"^\s+([a-z][a-z0-9-]+-[a-z0-9-]+)\s{2,}", # NOSONAR python:S5852 — controlled static HTML input, not user-facing; ReDoS not exploitable
cleaned,
flags=re.MULTILINE,
):
found.add(match.group(1))
return found

Expand Down