Skip to content
Merged
14 changes: 13 additions & 1 deletion services/account_unification/app/healthcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,26 @@
import json
import sys
import urllib.request
from urllib.parse import urlparse

DEFAULT_URL = "http://127.0.0.1:8099/healthz"

# The healthcheck only ever contacts the local HTTP(S) /healthz endpoint.
# Restricting the scheme keeps urllib from being pointed at the local
# filesystem via ``file://`` (the risk flagged by Semgrep
# python.lang.security.audit.dynamic-urllib-use-detected).
_ALLOWED_SCHEMES = ("http", "https")


def main(url: str = DEFAULT_URL) -> int:
"""Check the configured health endpoint and return a shell status code."""
if urlparse(url).scheme not in _ALLOWED_SCHEMES:
print(f"healthcheck failed: unsupported URL scheme in {url!r}", file=sys.stderr)
return 1
try:
with urllib.request.urlopen(url, timeout=5) as response: # noqa: S310
# The scheme is restricted to http(s) above and this only probes the
# local /healthz endpoint, so the dynamic-URL audit finding is mitigated.
with urllib.request.urlopen(url, timeout=5) as response: # noqa: S310 # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
body = json.loads(response.read().decode("utf-8"))
except Exception as exc: # pragma: no cover - network failure path
print(f"healthcheck failed: {exc}", file=sys.stderr)
Expand Down
12 changes: 9 additions & 3 deletions services/account_unification/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ readme = "README.md"
requires-python = ">=3.11"
license = { text = "Apache-2.0" }
dependencies = [
"fastapi==0.139.0",
"fastapi==0.140.13",
"pydantic==2.13.4",
"httpx==0.28.1",
"pyyaml==6.0.3",
"uvicorn==0.51.0",
"uvicorn==0.52.0",
]

[project.optional-dependencies]
dev = [
"httpx2>=2.5.0",
"interrogate==1.7.0",
"pytest==9.1.1",
"ruff==0.15.21",
"ruff==0.16.0",
]

[tool.setuptools]
Expand All @@ -36,6 +36,12 @@ addopts = "-q"
line-length = 100
target-version = "py311"

[tool.ruff.lint]
# Pin the classic default rule set (pyflakes + pycodestyle E4/E7/E9). ruff 0.16
# widened its implicit default to add flake8-bugbear/pyupgrade/etc.; this keeps
# the established lint policy explicit and stable across ruff upgrades.
select = ["E4", "E7", "E9", "F"]

[tool.interrogate]
exclude = ["tests", ".venv"]
fail-under = 80
5 changes: 5 additions & 0 deletions services/account_unification/tests/test_healthcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,8 @@ def fake_urlopen(url: str, *, timeout: int) -> _Response:

assert healthcheck.main("http://service/healthz") == 1
assert "healthcheck failed: connection refused" in capsys.readouterr().err


def test_healthcheck_rejects_non_http_scheme(capsys):
assert healthcheck.main("file:///etc/passwd") == 1
assert "unsupported URL scheme" in capsys.readouterr().err
Loading
Loading