backend/tests/test_config_mask_heuristic.py
Rama master · réplica de lectura del repositorio original.
"""The masking heuristic must not read a secret into an ordinary field name.
Found while putting the documented path-mapping editor on the Connections
page: the editor showed "***configured***" instead of the mapping, and saving
it would have written that literal string over the user's real paths.
Cause: the rule tested its sensitive words as substrings, and "map**pin**g"
contains "pin". Two settings were caught by it — path_mapping and the boolean
anidb_fallback_to_mapping, which GET /config therefore answered with a string
where the UI expected true or false.
"""
import pytest
from config_settings import is_sensitive_config_key
@pytest.mark.parametrize(
"key",
[
"api_key",
"opensubtitles_api_key",
"customapi_api_key_header",
"addic7ed_password",
"opensubtitles_password",
"github_token",
"tvdb_pin", # the segment really is "pin" here
"database_url", # no telltale word; masked by name
"redis_url",
],
)
def test_credentials_stay_masked(key):
assert is_sensitive_config_key(key) is True
@pytest.mark.parametrize(
"key",
[
"path_mapping", # "mapping" contains "pin"
"anidb_fallback_to_mapping", # a bool, and it contains "pin" too
"log_level",
"media_path",
"target_language",
],
)
def test_ordinary_settings_are_not_masked(key):
assert is_sensitive_config_key(key) is False
def test_the_two_rules_differ_only_on_the_mapping_false_positives():
"""Guard the change itself: nothing else may have been opened up.
The old rule was a substring test. Enumerating every declared settings
field, exactly two answers change — and both are the "map-pin-g" false
positive. If this list ever grows, the narrowing let something real out.
"""
import re
from pathlib import Path
source = Path(__file__).resolve().parents[1] / "config_settings.py"
fields = re.findall(r"^\s{4}([a-z][a-z0-9_]*)\s*:\s*[A-Za-z]", source.read_text("utf-8"), re.M)
old_parts = {"password", "pin", "secret", "token", "api_key"}
def old_rule(key):
return (
key in {"database_url", "redis_url"}
or "api_key" in key
or "key" in key.split("_")
or any(part in key for part in old_parts)
)
opened = sorted({f for f in fields if old_rule(f) and not is_sensitive_config_key(f)})
assert opened == ["anidb_fallback_to_mapping", "path_mapping"], opened
# Masking more is the safe direction; the one addition is a real API key
# whose name the old substring rule missed (#61 A01).
closed = sorted({f for f in fields if is_sensitive_config_key(f) and not old_rule(f)})
assert closed == ["opensubtitlescom_subliminal_apikey"], closed
def test_masked_config_keeps_the_path_mapping_readable(app_ctx):
"""End to end: the value reaches the editor instead of a mask."""
from config import get_settings
with app_ctx.app_context():
safe = get_settings().get_safe_config()
assert safe["path_mapping"] != "***configured***"
assert isinstance(safe["anidb_fallback_to_mapping"], bool)
assert safe["api_key"] in ("", "***configured***")