feat: warn when a secret-shaped value is found in args instead of env (#1r)
CI / Lint (ruff) (pull_request) Successful in 8s
CI / Tests (py3.10) (pull_request) Successful in 10s
CI / Tests (py3.12) (pull_request) Successful in 9s

Adds `args_secret_warning(data)` to bcc_core — returns a warning string
when any arg positional value looks like a raw credential (token prefix,
value following a secret-named flag, or URL with embedded user:pass like
postgres://user:pass@host).  `--flag=value` inline forms are intentionally
skipped (the flag name already labels the value).

Adds `secret_warn` QLabel in ServerEditor's stdio page; shown/hidden by
`_check_args()` on every field change, and cleared on deselect or
stdio→remote type switch.  Non-blocking — save path is not touched.

Closes #1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AJ
2026-07-02 11:59:17 -04:00
parent cda7d72e44
commit f4648b3c06
3 changed files with 112 additions and 0 deletions
+46
View File
@@ -801,6 +801,10 @@ _TOKEN_PREFIXES = (
MASK = "••••••••"
# Matches userinfo credentials embedded in a URL: scheme://user:pass@host
# Fires on postgres://user:pass@host but NOT on https://host/path or ssh://user@host.
_EMBEDDED_CRED_RE = re.compile(r"://[^:@/\s]+:[^:@/\s]+@")
def is_secret_key(name: str) -> bool:
"""Does this env-var / header / flag name look like it holds a secret?"""
@@ -841,6 +845,48 @@ def redact_args(args: list[str]) -> list[str]:
return out
def args_secret_warning(data: dict) -> str | None:
"""
Return a warning string when any arg looks like a raw secret that would
be better placed in `env`. Returns None when no concern is found.
Skips --flag=value inline pairs (already partially self-documenting).
Fires on:
- positional values that start with a well-known token prefix (ghp_, sk-, …)
- values that follow a secret-named flag (--token abc, --api-key abc)
- URLs with embedded user:pass credentials (postgres://user:pass@host)
"""
args = [str(a) for a in (data.get("args") or [])]
mask_next = False
for a in args:
if mask_next:
mask_next = False
if not a.startswith("-"):
return (
"An arg value following a secret-named flag looks like a credential. "
"Where the server supports it, prefer Environment variables — "
"args are visible in process listings."
)
continue
# --flag=value inline: skip (the flag name already labels it)
if a.startswith("-") and "=" in a:
continue
# --secretflag (no inline value): flag the next positional arg
if a.startswith("-") and is_secret_key(a):
mask_next = True
continue
if a.startswith("-"):
continue
# Positional value: check for token prefix or embedded URL credentials
if _is_secret_value(a) or _EMBEDDED_CRED_RE.search(a):
return (
"An arg value looks like a credential. "
"Where the server supports it, prefer Environment variables — "
"args are visible in process listings."
)
return None
def split_suspicious_args(args: list[str]) -> tuple[list[str], list[str]]:
"""
Detect the classic argument-entry mistake: several argv tokens typed on one