Files
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

215 lines
7.6 KiB
Python

"""Tests for the diagnose-gated ``SecureLogger`` wrapper.
``security.secure_logging.logger.exception()`` must log at ERROR level
while attaching the active exception to the record ONLY when both
``LDR_APP_DEBUG`` and ``LDR_LOGURU_DIAGNOSE`` are truthy. With the
exception stripped, no sink can render the traceback or the
``__cause__`` chain — the #4183 leak class (headers/URLs embedded in
wrapped exceptions) cannot reach any log output.
"""
import pytest
from local_deep_research.security.secure_logging import (
SecureLogger,
env_truthy,
is_diagnose_mode,
logger,
)
_DEBUG_VAR = "LDR_APP_DEBUG"
_DIAGNOSE_VAR = "LDR_LOGURU_DIAGNOSE"
def _set_env(monkeypatch, debug, diagnose):
"""Set/unset both gating vars. ``None`` means unset."""
for var, value in ((_DEBUG_VAR, debug), (_DIAGNOSE_VAR, diagnose)):
if value is None:
monkeypatch.delenv(var, raising=False)
else:
monkeypatch.setenv(var, value)
def _log_wrapped_exception(message="request failed: scrubbed"):
"""Log *message* from inside a handler for a cause-chained exception.
The inner ``ValueError`` carries a fake credential — the same shape
as the #4183 leak, where the secret lived only in the ``__cause__``
chain, not in the logged message.
"""
try:
try:
raise ValueError("api_key=sk-LEAK-0123456789abcdef")
except ValueError as inner:
raise RuntimeError("wrapped") from inner
except RuntimeError:
logger.exception(message)
class TestEnvTruthy:
@pytest.mark.parametrize("value", ["1", "true", "TRUE", "yes", " YES "])
def test_truthy_values(self, monkeypatch, value):
monkeypatch.setenv(_DEBUG_VAR, value)
assert env_truthy(_DEBUG_VAR) is True
@pytest.mark.parametrize("value", ["", "0", "false", "no", "on", "2"])
def test_falsy_values(self, monkeypatch, value):
monkeypatch.setenv(_DEBUG_VAR, value)
assert env_truthy(_DEBUG_VAR) is False
def test_unset_is_falsy(self, monkeypatch):
monkeypatch.delenv(_DEBUG_VAR, raising=False)
assert env_truthy(_DEBUG_VAR) is False
class TestIsDiagnoseMode:
@pytest.mark.parametrize(
("debug", "diagnose", "expected"),
[
(None, None, False),
("1", None, False),
(None, "1", False),
("0", "1", False),
("1", "0", False),
("1", "1", True),
("true", "yes", True),
("YES ", "TRUE", True),
],
)
def test_truth_table(self, monkeypatch, debug, diagnose, expected):
_set_env(monkeypatch, debug, diagnose)
assert is_diagnose_mode() is expected
class TestExceptionDefaultMode:
"""Both flags unset — production behaviour."""
def test_logs_at_error_level_without_traceback(
self, monkeypatch, loguru_caplog_full
):
_set_env(monkeypatch, None, None)
with loguru_caplog_full.at_level("ERROR"):
_log_wrapped_exception()
records = [
r
for r in loguru_caplog_full.records
if "request failed: scrubbed" in r.getMessage()
]
assert records, "the event must still be logged"
assert all(r.levelname == "ERROR" for r in records)
assert all(r.exc_info is None for r in records)
assert "Traceback" not in loguru_caplog_full.text
def test_cause_chain_secret_never_reaches_output(
self, monkeypatch, loguru_caplog_full
):
"""#4183 regression: the fixture renders ``{exception}`` with
``backtrace=True``, so if the exception were attached the
``__cause__`` chain (holding the credential) WOULD appear here."""
_set_env(monkeypatch, None, None)
with loguru_caplog_full.at_level("ERROR"):
_log_wrapped_exception()
assert "request failed: scrubbed" in loguru_caplog_full.text
assert "sk-LEAK" not in loguru_caplog_full.text
assert "api_key" not in loguru_caplog_full.text
@pytest.mark.parametrize(
("debug", "diagnose"),
[("1", None), (None, "1"), ("1", "0"), ("0", "1")],
)
def test_single_flag_does_not_enable_traceback(
self, monkeypatch, loguru_caplog_full, debug, diagnose
):
_set_env(monkeypatch, debug, diagnose)
with loguru_caplog_full.at_level("ERROR"):
_log_wrapped_exception()
assert "request failed: scrubbed" in loguru_caplog_full.text
assert "Traceback" not in loguru_caplog_full.text
assert "sk-LEAK" not in loguru_caplog_full.text
class TestExceptionDiagnoseMode:
def test_both_flags_render_traceback(self, monkeypatch, loguru_caplog_full):
_set_env(monkeypatch, "1", "1")
with loguru_caplog_full.at_level("ERROR"):
_log_wrapped_exception()
records = [
r
for r in loguru_caplog_full.records
if "request failed: scrubbed" in r.getMessage()
]
assert records
assert all(r.levelname == "ERROR" for r in records)
assert "Traceback" in loguru_caplog_full.text
assert "RuntimeError" in loguru_caplog_full.text
class TestRecordAttribution:
def test_record_points_at_caller_not_wrapper(self, monkeypatch):
"""``depth=1`` must attribute the record to the calling module so
log locations stay useful and ``logger.disable()`` namespacing
keys off the caller, not ``secure_logging``."""
_set_env(monkeypatch, None, None)
captured = []
handler_id = logger.add(
lambda message: captured.append(message.record),
format="{message}",
level="ERROR",
)
try:
try:
raise RuntimeError("boom")
except RuntimeError:
logger.exception("attribution check")
finally:
logger.remove(handler_id)
records = [r for r in captured if r["message"] == "attribution check"]
assert records
record = records[-1]
assert record["module"] == "test_secure_logging"
assert record["function"] == "test_record_points_at_caller_not_wrapper"
assert record["exception"] is None
class TestWrapperChaining:
def test_bind_keeps_gated_exception(self, monkeypatch, loguru_caplog_full):
_set_env(monkeypatch, None, None)
bound = logger.bind(component="test")
assert isinstance(bound, SecureLogger)
with loguru_caplog_full.at_level("ERROR"):
try:
try:
raise ValueError("api_key=sk-LEAK-0123456789abcdef")
except ValueError as inner:
raise RuntimeError("wrapped") from inner
except RuntimeError:
bound.exception("bound scrubbed message")
assert "bound scrubbed message" in loguru_caplog_full.text
assert "Traceback" not in loguru_caplog_full.text
assert "sk-LEAK" not in loguru_caplog_full.text
def test_patch_returns_wrapper(self):
patched = logger.patch(lambda record: None)
assert isinstance(patched, SecureLogger)
def test_no_active_exception_does_not_raise(
self, monkeypatch, loguru_caplog_full
):
_set_env(monkeypatch, None, None)
with loguru_caplog_full.at_level("ERROR"):
logger.exception("called outside except block")
assert "called outside except block" in loguru_caplog_full.text
def test_delegates_other_methods_to_loguru(
self, monkeypatch, loguru_caplog_full
):
with loguru_caplog_full.at_level("INFO"):
logger.info("plain info via wrapper")
assert "plain info via wrapper" in loguru_caplog_full.text