feat: mask secret values in the UI and redact them from diagnostics
- Env/header values whose key looks secret (TOKEN, API_KEY, PASSWORD, AUTH, ...) render as •••••••• via a display-only delegate; a 'Show secrets' toggle reveals them. Underlying data, editing, and save are untouched. - The Add dialog switches the value field to password echo when the key name looks secret. - Copy-diagnostics now redacts secrets from args (--token <v>, --api-key=<v>, and well-known token prefixes like ghp_/sk-/xoxb-), since those reports get pasted into public bug reports. Env and header values were already omitted from diagnostics.
This commit is contained in:
+80
-1
@@ -596,6 +596,84 @@ def parse_pasted_json(text: str) -> dict[str, dict]:
|
||||
return servers
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Secrets: detection + redaction
|
||||
# --------------------------------------------------------------------------- #
|
||||
_SECRET_KEY_RE = re.compile(
|
||||
r"(?i)(token|secret|passw|api[-_]?key|apikey|auth|credential|bearer|private[-_]?key|access[-_]?key)"
|
||||
)
|
||||
|
||||
# Well-known token prefixes that identify a bare value as a secret even
|
||||
# without a telling key/flag name next to it.
|
||||
_TOKEN_PREFIXES = (
|
||||
"ghp_",
|
||||
"gho_",
|
||||
"ghu_",
|
||||
"ghs_",
|
||||
"github_pat_", # GitHub
|
||||
"sk-",
|
||||
"sk_live_",
|
||||
"sk_test_",
|
||||
"rk_live_", # OpenAI / Stripe
|
||||
"xoxb-",
|
||||
"xoxp-",
|
||||
"xoxc-",
|
||||
"xoxs-",
|
||||
"xapp-", # Slack
|
||||
"glpat-",
|
||||
"gldt-", # GitLab
|
||||
"AKIA",
|
||||
"ASIA", # AWS access key IDs
|
||||
"ya29.",
|
||||
"AIza", # Google
|
||||
"pypi-",
|
||||
"npm_",
|
||||
"dop_v1_",
|
||||
"figd_",
|
||||
)
|
||||
|
||||
MASK = "••••••••"
|
||||
|
||||
|
||||
def is_secret_key(name: str) -> bool:
|
||||
"""Does this env-var / header / flag name look like it holds a secret?"""
|
||||
return bool(_SECRET_KEY_RE.search(name or ""))
|
||||
|
||||
|
||||
def _is_secret_value(value: str) -> bool:
|
||||
return isinstance(value, str) and value.startswith(_TOKEN_PREFIXES)
|
||||
|
||||
|
||||
def redact_args(args: list[str]) -> list[str]:
|
||||
"""
|
||||
Mask secret values in an args list for display/diagnostics:
|
||||
--token abc123 -> --token •••••••• (value after a secret flag)
|
||||
--api-key=abc123 -> --api-key=•••••••• (inline flag=value)
|
||||
ghp_abc123 -> •••••••• (well-known token prefix)
|
||||
Everything else passes through untouched.
|
||||
"""
|
||||
out: list[str] = []
|
||||
mask_next = False
|
||||
for a in args:
|
||||
s = str(a)
|
||||
if mask_next:
|
||||
out.append(MASK)
|
||||
mask_next = False
|
||||
continue
|
||||
if s.startswith("-") and "=" in s and is_secret_key(s.split("=", 1)[0]):
|
||||
out.append(s.split("=", 1)[0] + "=" + MASK)
|
||||
continue
|
||||
if s.startswith("-") and is_secret_key(s):
|
||||
out.append(s)
|
||||
mask_next = True
|
||||
continue
|
||||
if _is_secret_value(s):
|
||||
out.append(MASK)
|
||||
continue
|
||||
out.append(s)
|
||||
return out
|
||||
|
||||
|
||||
def split_suspicious_args(args: list[str]) -> tuple[list[str], list[str]]:
|
||||
"""
|
||||
Detect the classic argument-entry mistake: several argv tokens typed on one
|
||||
@@ -944,7 +1022,8 @@ def diagnostics_text(name: str, data: dict) -> str:
|
||||
L.append(f"Server: {name or '(unnamed)'} [local / stdio]")
|
||||
L.append(f"Command: {data.get('command', '')}")
|
||||
if data.get("args"):
|
||||
L.append("Args: " + " ".join(str(a) for a in data["args"]))
|
||||
# Redacted: this report is designed to be pasted into bug reports.
|
||||
L.append("Args: " + " ".join(redact_args(list(data["args"]))))
|
||||
if data.get("env"):
|
||||
L.append("Env keys: " + ", ".join(data["env"].keys()))
|
||||
L.append(f"Status: {res['status'].upper()}")
|
||||
|
||||
Reference in New Issue
Block a user