mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-22 10:58:15 +00:00
Stop array-item secrets being wiped, and logging them
Three review findings from #485 that I missed when addressing that PR; it has since merged, so they land here. 1. Array-item secrets destroyed by any unrelated save (data loss). remove_empty_secrets recursed into dicts but let a list fall through to the scalar branch and kept it verbatim. Lists merge by *replacement*, so the blanks the masked form posts back went straight over the stored array: stored [{"name":"a","token":"REAL-A"}, {"name":"b","token":"REAL-B"}] posted [{"name":"a","token":""}, {"name":"b","token":""}] merged [{"name":"a","token":""}, {"name":"b","token":""}] -> both credentials gone Same failure as the scalar api_key case fixed earlier, one container deeper. Lists now prune element-wise, and a list with nothing real in it is dropped so the stored one is left alone. Where one entry does change, the new merge_secrets merges by index instead of replacing. Two details the first attempt got wrong, both caught by existing tests: - An emptied dict item must stay {}, not None. ConfigManager's _strip_secrets_recursive treats a secrets list as *parallel* to the regular one ({} = "item i has no secrets"); a None makes it stop looking parallel, and it then drops the whole key from the main config -- silently deleting the items' non-secret fields too. - The incoming list's length wins. The regular config's list is authoritative about how many items exist, so preserving surplus stored entries would let the two fall out of step and make deleting an entry impossible. 2. Submitted credentials written to the journal (security). save_plugin_config logged `Full config: {plugin_config}` at INFO and `Config that failed: {plugin_config}` at ERROR. Both run before separate_secrets, so plugin_config still held the values just typed into the form. Now keys only. Swept the rest of web_interface/ and src/ for the same shape -- these were the only two. 3. Restart banner kept stale wording. showRestartPending() cleared the stored custom text but left the DOM element alone, so a config save could show the previous update's message. The default is read back from the server-rendered copy rather than duplicated in JS, so the template stays the one owner of the string. Verified: 556 passed, 1 skipped across the web suite. Mutation-checked -- reverting api_v3 fails the logging guard and the array-merge test; reverting either half of the secret_helpers change fails the unit tests. New end-to-end coverage drives the real endpoint, not just the helpers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW
This commit is contained in:
co-authored by
Claude Opus 5
parent
9c0c0dc851
commit
d64fa9761a
@@ -22,7 +22,8 @@ logger = logging.getLogger(__name__)
|
||||
from src.web_interface.api_helpers import success_response, error_response, validate_request_json
|
||||
from src.web_interface.errors import ErrorCode
|
||||
from src.web_interface.secret_helpers import (find_secret_fields, mask_all_secret_values,
|
||||
remove_empty_secrets, separate_secrets,
|
||||
merge_secrets, remove_empty_secrets,
|
||||
separate_secrets,
|
||||
strip_masked_values)
|
||||
from src.web_interface.error_handler import describe_exception, redact_text
|
||||
from src.plugin_system.operation_types import OperationType
|
||||
@@ -1296,7 +1297,10 @@ def save_main_config():
|
||||
if secrets_config:
|
||||
if plugin_id not in current_secrets:
|
||||
current_secrets[plugin_id] = {}
|
||||
current_secrets[plugin_id] = deep_merge(current_secrets[plugin_id], secrets_config)
|
||||
# Lists merge by replacement, so deep_merge here wrote a
|
||||
# blanked array straight over the stored credentials.
|
||||
current_secrets[plugin_id] = merge_secrets(
|
||||
current_secrets[plugin_id], secrets_config)
|
||||
# Save secrets file
|
||||
api_v3.config_manager.save_raw_file_content('secrets', current_secrets)
|
||||
|
||||
@@ -5675,8 +5679,10 @@ def save_plugin_config():
|
||||
if schema:
|
||||
# Log what we're validating for debugging
|
||||
logger.info(f"Validating config for {plugin_id}")
|
||||
# Only the shape. plugin_config still holds the submitted secret
|
||||
# values at this point -- separate_secrets does not run until
|
||||
# below -- so logging it wrote live credentials to the journal.
|
||||
logger.info(f"Config keys being validated: {list(plugin_config.keys())}")
|
||||
logger.info(f"Full config: {plugin_config}")
|
||||
|
||||
# Get enhanced schema keys (including injected core properties)
|
||||
# We need to create an enhanced schema to get the actual allowed keys
|
||||
@@ -5699,7 +5705,8 @@ def save_plugin_config():
|
||||
# Log validation errors for debugging
|
||||
logger.error(f"Config validation failed for {plugin_id}")
|
||||
logger.error(f"Validation errors: {validation_errors}")
|
||||
logger.error(f"Config that failed: {plugin_config}")
|
||||
# Keys only, for the same reason as above.
|
||||
logger.error(f"Config keys that failed: {list(plugin_config.keys())}")
|
||||
logger.error(f"Schema properties: {list(enhanced_schema.get('properties', {}).keys())}")
|
||||
|
||||
# Also print to console for immediate visibility
|
||||
@@ -5750,7 +5757,9 @@ def save_plugin_config():
|
||||
if secrets_config:
|
||||
if plugin_id not in current_secrets:
|
||||
current_secrets[plugin_id] = {}
|
||||
current_secrets[plugin_id] = deep_merge(current_secrets[plugin_id], secrets_config)
|
||||
# See above -- secrets lists must merge element-wise.
|
||||
current_secrets[plugin_id] = merge_secrets(
|
||||
current_secrets[plugin_id], secrets_config)
|
||||
# Save secrets file
|
||||
try:
|
||||
api_v3.config_manager.save_raw_file_content('secrets', current_secrets)
|
||||
|
||||
Reference in New Issue
Block a user