* fix(security): mask secret fields in API responses and extract helpers
GET /config/secrets returned raw API keys in plaintext to the browser.
GET /plugins/config returned merged config including deep-merged secrets.
POST /plugins/config could overwrite existing secrets with empty strings
when the GET endpoint returned masked values that were sent back unchanged.
Changes:
- Add src/web_interface/secret_helpers.py with reusable functions:
find_secret_fields, separate_secrets, mask_secret_fields,
mask_all_secret_values, remove_empty_secrets
- GET /config/secrets: mask all values with '••••••••'
- GET /plugins/config: mask x-secret fields with ''
- POST /plugins/config: filter empty-string secrets before saving
- pages_v3: mask secrets before rendering plugin config templates
- Remove three duplicated inline find_secret_fields/separate_secrets
definitions in api_v3.py (replaced by single imported module)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): harden secret masking against CodeRabbit findings
- Fail-closed: return 500 when schema unavailable instead of leaking secrets
- Fix falsey masking: use `is not None and != ''` instead of truthiness check
so values like 0 or False are still redacted
- Add array-item secret support: recurse into `type: array` items schema
to detect and mask secrets like accounts[].token
- pages_v3: fail-closed when schema properties missing
Addresses CodeRabbit findings on PR #276:
- Critical: fail-closed bypass when schema_mgr/schema missing
- Major: falsey values not masked (0, False leak through)
- Major: pages_v3 fail-open when schema absent
- Major: array-item secrets unsupported
Co-Authored-By: 5ymb01 <5ymb01@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: 5ymb01 <5ymb01@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>