diff --git a/forgelm/_http.py b/forgelm/_http.py index b9dd5ad9..787aa919 100644 --- a/forgelm/_http.py +++ b/forgelm/_http.py @@ -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.") diff --git a/tests/test_webhook.py b/tests/test_webhook.py index a7c5ced7..fe71437c 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -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.""" @@ -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, @@ -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 diff --git a/tools/check_site_claims.py b/tools/check_site_claims.py index 578ac614..607c50b9 100644 --- a/tools/check_site_claims.py +++ b/tools/check_site_claims.py @@ -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
).
     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