Skip to content

Commit 947cfa0

Browse files
committed
Suppress SonarCloud hotspots on intentional negative-test URLs/IPs
Add NOSONAR markers on literal insecure URLs and non-loopback/private IPs that exist to verify the SSRF validator and server guards reject them. Extract variables so each NOSONAR sits on the flagged line. Replace the unused ruff A001 noqa on docs/source/conf.py with a pylint disable for the Sphinx-required `copyright` binding.
1 parent 13a4ee5 commit 947cfa0

4 files changed

Lines changed: 18 additions & 11 deletions

File tree

docs/source/conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Sphinx configuration for automation_file."""
2+
23
from __future__ import annotations
34

45
import os
@@ -8,7 +9,7 @@
89

910
project = "automation_file"
1011
author = "JE-Chen"
11-
copyright = "2026, JE-Chen" # noqa: A001 - Sphinx requires this name
12+
copyright = "2026, JE-Chen" # pylint: disable=redefined-builtin # Sphinx requires this name
1213
release = "0.0.32"
1314

1415
extensions = [

tests/test_http_server.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def test_http_server_executes_action() -> None:
3333
server = start_http_action_server(host="127.0.0.1", port=0)
3434
host, port = server.server_address
3535
try:
36-
status, body = _post(f"http://{host}:{port}/actions", [["test_http_echo", {"value": "hi"}]])
36+
url = f"http://{host}:{port}/actions" # NOSONAR: loopback test server, not exposed
37+
status, body = _post(url, [["test_http_echo", {"value": "hi"}]])
3738
assert status == 200
3839
assert json.loads(body) == {"execute: ['test_http_echo', {'value': 'hi'}]": "hi"}
3940
finally:
@@ -49,7 +50,8 @@ def test_http_server_rejects_missing_auth() -> None:
4950
)
5051
host, port = server.server_address
5152
try:
52-
status, _ = _post(f"http://{host}:{port}/actions", [["test_http_echo", {"value": 1}]])
53+
url = f"http://{host}:{port}/actions" # NOSONAR: loopback test server, not exposed
54+
status, _ = _post(url, [["test_http_echo", {"value": 1}]])
5355
assert status == 401
5456
finally:
5557
server.shutdown()
@@ -64,8 +66,9 @@ def test_http_server_accepts_valid_auth() -> None:
6466
)
6567
host, port = server.server_address
6668
try:
69+
url = f"http://{host}:{port}/actions" # NOSONAR: loopback test server, not exposed
6770
status, body = _post(
68-
f"http://{host}:{port}/actions",
71+
url,
6972
[["test_http_echo", {"value": 1}]],
7073
headers={"Authorization": "Bearer s3cr3t"},
7174
)
@@ -76,5 +79,6 @@ def test_http_server_accepts_valid_auth() -> None:
7679

7780

7881
def test_http_server_rejects_non_loopback() -> None:
82+
non_loopback = "8.8.8.8" # NOSONAR: literal non-loopback IP required to verify rejection
7983
with pytest.raises(ValueError):
80-
start_http_action_server(host="8.8.8.8", port=0)
84+
start_http_action_server(host=non_loopback, port=0)

tests/test_tcp_server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ def test_server_reports_bad_json(server) -> None:
6666

6767

6868
def test_start_server_rejects_non_loopback() -> None:
69+
non_loopback = "8.8.8.8" # NOSONAR: literal non-loopback IP required to verify rejection
6970
with pytest.raises(ValueError):
70-
start_autocontrol_socket_server(host="8.8.8.8", port=_free_port())
71+
start_autocontrol_socket_server(host=non_loopback, port=_free_port())
7172

7273

7374
def test_start_server_allows_non_loopback_when_opted_in() -> None:

tests/test_url_validator.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"url",
1313
[
1414
"file:///etc/passwd",
15-
"ftp://example.com/x",
15+
"ftp://example.com/x", # NOSONAR: literal insecure URL required to verify rejection
1616
"gopher://example.com",
1717
"data:,hello",
1818
],
@@ -37,9 +37,9 @@ def test_reject_empty_url() -> None:
3737
[
3838
"http://127.0.0.1/",
3939
"http://localhost/",
40-
"http://10.0.0.1/",
41-
"http://169.254.1.1/",
42-
"http://[::1]/",
40+
"http://10.0.0.1/", # NOSONAR: literal private IP required to verify SSRF rejection
41+
"http://169.254.1.1/", # NOSONAR: literal link-local IP required to verify SSRF rejection
42+
"http://[::1]/", # NOSONAR: literal loopback IPv6 required to verify SSRF rejection
4343
],
4444
)
4545
def test_reject_loopback_and_private_ip(url: str) -> None:
@@ -48,5 +48,6 @@ def test_reject_loopback_and_private_ip(url: str) -> None:
4848

4949

5050
def test_reject_unresolvable_host() -> None:
51+
url = "http://definitely-not-a-real-host-abc123.invalid/" # NOSONAR: literal unresolvable URL required to verify rejection
5152
with pytest.raises(UrlValidationException):
52-
validate_http_url("http://definitely-not-a-real-host-abc123.invalid/")
53+
validate_http_url(url)

0 commit comments

Comments
 (0)