mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-07 05:56:17 -10:00
fix(channels): align write-path secret validation with read-path redaction
_validate_config_security used the narrow exact-match SECRET_FIELD_PATTERNS while _redact_config_secrets was hardened to also catch *_token/*_key/*_secret/ *_password suffix keys. This closed the read leak but still let inline app_token/signing_secret/client_secret values be stored on write. Route both paths through the shared _is_secret_key helper so suffix secrets are rejected at write time too, and add regression tests. Co-authored-by: Mervin Praison <[email protected]>
This commit is contained in:
1 parent
0626d0bb96
commit
971033b905
2 files changed
+32
-17
No files matched your search
@@ -76,23 +76,6 @@ SECRET_FIELD_PATTERNS = {
|
||||
"private_key", "access_token", "refresh_token", "webhook_secret"
|
||||
}
|
||||
|
||||
def _validate_config_security(config: Dict[str, Any]) -> Optional[str]:
|
||||
"""Validate channel config doesn't contain inline secrets.
|
||||
|
||||
Returns error message if secrets detected, None if safe.
|
||||
"""
|
||||
if not isinstance(config, dict):
|
||||
return None
|
||||
for key, value in config.items():
|
||||
if key.lower() in SECRET_FIELD_PATTERNS:
|
||||
if isinstance(value, str) and value and not value.startswith("env:"):
|
||||
# Check for common secret patterns
|
||||
if (value.startswith(("sk-", "xoxb-", "xoxp-", "xapp-", "am_", "bot")) or
|
||||
len(value) > 20):
|
||||
return (f"Detected inline secret in '{key}'. Use env reference: "
|
||||
f"'{key}_ref': 'env:YOUR_ENV_VAR'")
|
||||
return None
|
||||
|
||||
# Suffixes that identify secret-bearing config keys not covered by the
|
||||
# exact-match SECRET_FIELD_PATTERNS set (e.g. app_token, signing_secret).
|
||||
_SECRET_KEY_SUFFIXES = ("_token", "_key", "_secret", "_password")
|
||||
@@ -104,6 +87,24 @@ def _is_secret_key(key: str) -> bool:
|
||||
return lowered in SECRET_FIELD_PATTERNS or lowered.endswith(_SECRET_KEY_SUFFIXES)
|
||||
|
||||
|
||||
def _validate_config_security(config: Dict[str, Any]) -> Optional[str]:
|
||||
"""Validate channel config doesn't contain inline secrets.
|
||||
|
||||
Returns error message if secrets detected, None if safe.
|
||||
"""
|
||||
if not isinstance(config, dict):
|
||||
return None
|
||||
for key, value in config.items():
|
||||
if _is_secret_key(key):
|
||||
if isinstance(value, str) and value and not value.startswith("env:"):
|
||||
# Check for common secret patterns
|
||||
if (value.startswith(("sk-", "xoxb-", "xoxp-", "xapp-", "am_", "bot")) or
|
||||
len(value) > 20):
|
||||
return (f"Detected inline secret in '{key}'. Use env reference: "
|
||||
f"'{key}_ref': 'env:YOUR_ENV_VAR'")
|
||||
return None
|
||||
|
||||
|
||||
def _redact_config_secrets(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Redact secret values in config for API responses.
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from praisonaiui.features import channels as channels_mod
|
||||
from praisonaiui.features.channels import (
|
||||
ChannelsFeature,
|
||||
_redact_config_secrets,
|
||||
_validate_config_security,
|
||||
)
|
||||
|
||||
_DISCORD_TOKEN = "MTQ" + "a1b2c3d4e5f6g7h8i9j0k1l2"
|
||||
@@ -101,6 +102,19 @@ def test_redact_is_non_mutating():
|
||||
assert original["bot_token"] == _DISCORD_TOKEN
|
||||
|
||||
|
||||
# ── Write path rejects inline secrets on suffix keys ─────────────────
|
||||
|
||||
|
||||
def test_validate_rejects_inline_suffix_secret():
|
||||
err = _validate_config_security({"app_token": "xapp-1-" + "a" * 30})
|
||||
assert err is not None
|
||||
assert "app_token" in err
|
||||
|
||||
|
||||
def test_validate_allows_env_reference_suffix_key():
|
||||
assert _validate_config_security({"signing_secret": "env:SLACK_SIGNING_SECRET"}) is None
|
||||
|
||||
|
||||
# ── Read handlers must not leak ──────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user