Files
FabledSteward/tests/core/test_plugin_secrets.py
T
bvandeusenandClaude Opus 5.5 eea29123f9
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m13s
CI / unit (push) Successful in 1m27s
CI / publish (push) Successful in 1m2s
fix(settings): a plugin's password is sealed at rest, and a blank box keeps it (#5671)
The UniFi password sat in plugin.unifi in plain text: SECRET_KEYS covers
whole settings, and a plugin's settings are one dict. Saving the plugin's
settings page also wiped it, since the password box is never filled back
in and its blank value was stored.

- A plugin field whose name holds password, secret or token is a
  credential (is_plugin_secret). set_setting seals it inside the dict,
  every read opens it, startup seals any plaintext left from before, and
  the undecryptable-secrets banner names it as plugin.<name>.<field>.
- The settings page uses the same test for its password boxes, and a blank
  box keeps the saved value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:06:31 -04:00

35 lines
1.5 KiB
Python

"""A plugin's credentials are sealed inside its settings dict (#5671): the
UniFi password was stored in plain text, so a backup would have carried it."""
from steward.core.crypto import init_crypto, is_encrypted
from steward.core.settings import _decode, is_plugin_secret, seal_plugin_secrets
def setup_function():
init_crypto("unit-test-secret-key")
def test_which_fields_are_credentials():
assert is_plugin_secret("password") and is_plugin_secret("client_secret")
assert is_plugin_secret("api_token")
assert not is_plugin_secret("username") and not is_plugin_secret("host")
def test_sealing_touches_only_plaintext_secrets_and_reading_opens_them():
cfg = {"enabled": True, "host": "https://10.0.0.1", "username": "admin",
"password": "hunter2", "poll_interval_seconds": 60}
sealed, n = seal_plugin_secrets(cfg)
assert n == 1 and is_encrypted(sealed["password"])
assert {k: v for k, v in sealed.items() if k != "password"} == {
k: v for k, v in cfg.items() if k != "password"}
again, n2 = seal_plugin_secrets(sealed) # idempotent
assert n2 == 0 and again == sealed
assert _decode(sealed, "plugin.unifi") == cfg
assert seal_plugin_secrets({"password": ""}) == ({"password": ""}, 0)
assert seal_plugin_secrets("not a dict") == ("not a dict", 0)
def test_only_plugin_settings_are_opened():
# A dict under a non-plugin key is left alone: only plugin.* is opened.
sealed, _ = seal_plugin_secrets({"password": "x"})
assert _decode(sealed, "thresholds.cpu") == sealed