spec(service-credential-tester): verify + strengthen no-secret-logs test + reconcile
Strengthen test_secrets_not_logged (N-2): now sends real-looking secrets through prometheus + qbittorrent test_callables (mocked at network boundary), asserts no fragments leak into caplog, verified non-vacuous. Write apply-progress.md, tick all 29 tasks, add verify-report.md (21/21 PASS). Gates green: 362+ pytest, ruff clean, npm build+lint 0 errors, 158 vitest.
This commit is contained in:
@@ -902,17 +902,55 @@ class TestServiceTestEndpoint:
|
||||
assert before == after
|
||||
|
||||
def test_secrets_not_logged(self, test_client: TestClient, caplog) -> None:
|
||||
"""No log line contains the secret value."""
|
||||
secret_value = "super-secret-hunter2"
|
||||
with caplog.at_level(logging.INFO):
|
||||
test_client.post(
|
||||
"""Secret values in the request body never reach any log line.
|
||||
|
||||
Unlike the trivial empty-secrets case, this drives the full endpoint
|
||||
path (validate -> dispatch to the real test_callable -> success log)
|
||||
with real-looking secret payloads. The per-type test_callables are
|
||||
mocked at the network boundary so they succeed, proving the endpoint
|
||||
does not log the secret values even though they are in the request body.
|
||||
"""
|
||||
api_key_secret = "glc_somethingverysecret"
|
||||
password_secret = "SUPER-SECRET-PW-12345"
|
||||
|
||||
prom_response = SimpleNamespace(raise_for_status=lambda: None, json=lambda: {"results": {}})
|
||||
qbit_client = MagicMock()
|
||||
qbit_client.maindata.return_value = {"server_state": {"qbittorrent_version": "v4.6.0"}}
|
||||
|
||||
with (
|
||||
caplog.at_level(logging.DEBUG),
|
||||
patch("media_library_viewer_api.integrations.prometheus.requests.post", return_value=prom_response),
|
||||
patch("media_library_viewer_api.integrations.qbittorrent.QbittorrentClient", return_value=qbit_client),
|
||||
):
|
||||
prom_resp = test_client.post(
|
||||
"/api/services/test",
|
||||
json={
|
||||
"service_type": "backups",
|
||||
"service_type": "prometheus",
|
||||
"name": "test",
|
||||
"config": {"ingestion_label": "default"},
|
||||
"secrets": {},
|
||||
"config": {"grafana_url": "http://grafana:3000"},
|
||||
"secrets": {"grafana_api_key": api_key_secret},
|
||||
"enabled": True,
|
||||
},
|
||||
)
|
||||
assert secret_value not in caplog.text
|
||||
qbit_resp = test_client.post(
|
||||
"/api/services/test",
|
||||
json={
|
||||
"service_type": "qbittorrent",
|
||||
"name": "test",
|
||||
"config": {"base_url": "http://qb:8080"},
|
||||
"secrets": {"username": "u", "password": password_secret},
|
||||
"enabled": True,
|
||||
},
|
||||
)
|
||||
|
||||
# Both requests must run the endpoint fully (validate + dispatch + success).
|
||||
assert prom_resp.status_code == 200
|
||||
assert prom_resp.json()["ok"] is True
|
||||
assert qbit_resp.status_code == 200
|
||||
assert qbit_resp.json()["ok"] is True
|
||||
|
||||
# Neither the full secret values nor meaningful fragments may leak into logs.
|
||||
leaked = [
|
||||
fragment for fragment in (api_key_secret, password_secret, "verysecret", "SUPER") if fragment in caplog.text
|
||||
]
|
||||
assert not leaked, f"secret fragments leaked into logs: {leaked!r}"
|
||||
|
||||
Reference in New Issue
Block a user