Summary
deepsec config show prints the entire ~/.deepsec/config.yaml file verbatim, including llm.api_key in plaintext, with no redaction. Anyone running this command (or piping its output anywhere — logs, screen sharing, an AI coding assistant executing it on the user's behalf) gets the raw API key printed to stdout.
Root cause
This looks like a regression from the CLI rewrite. The current implementation in deepsec/cli/app.py:
@config_app.command("show")
def config_show() -> None:
path = config_path()
if not path.exists():
console.print(f"No DeepSec config at {path}. Run `deepsec config init`.")
return
console.print(path.read_text(encoding="utf-8"))
(deepsec/cli/app.py:342-348) just does a raw read_text() of the config file and prints it — no field filtering at all.
The legacy CLI (deepsec/spear/legacy_cli/main.py) had proper masking for secret-looking keys, e.g. in config_get (line ~2134-2146):
if isinstance(value, str) and ("key" in key.lower() or "pass" in key.lower()):
value = value[:8] + "..." if len(value) > 8 else "***"
and even config_set's confirmation message masks the echoed value:
f"[+] Set {key} = {'***' if 'key' in key.lower() or 'pass' in key.lower() else value}"
The new deepsec config subcommand set (init/set/show) doesn't carry this masking logic forward. deepsec/config/cli_constants.py (help text for the old vulnclaw config get) still documents "Secret-looking keys are masked" — but that command/behavior doesn't exist in the current deepsec CLI's show.
Repro
deepsec config set llm.provider anthropic
deepsec config set llm.api_key sk-ant-xxxxxxxx...
deepsec config show
# api_key: sk-ant-xxxxxxxx... <- full plaintext key printed
Suggested fix
Port the masking logic from the legacy config_get/config_set into the current show command — mask any key whose dotted path contains key, pass, token, secret, etc. before printing, e.g. sk-ant-xx... instead of the full value. Ideally add a test asserting config show output never contains a full-length secret string when one is configured.
Impact
Anyone running deepsec config show — including AI coding assistants executing shell commands on a user's behalf — gets the raw API key in their output/logs/transcript. This is a real-world footgun for a security-focused tool.
Summary
deepsec config showprints the entire~/.deepsec/config.yamlfile verbatim, includingllm.api_keyin plaintext, with no redaction. Anyone running this command (or piping its output anywhere — logs, screen sharing, an AI coding assistant executing it on the user's behalf) gets the raw API key printed to stdout.Root cause
This looks like a regression from the CLI rewrite. The current implementation in
deepsec/cli/app.py:(
deepsec/cli/app.py:342-348) just does a rawread_text()of the config file and prints it — no field filtering at all.The legacy CLI (
deepsec/spear/legacy_cli/main.py) had proper masking for secret-looking keys, e.g. inconfig_get(line ~2134-2146):and even
config_set's confirmation message masks the echoed value:f"[+] Set {key} = {'***' if 'key' in key.lower() or 'pass' in key.lower() else value}"The new
deepsec configsubcommand set (init/set/show) doesn't carry this masking logic forward.deepsec/config/cli_constants.py(help text for the oldvulnclaw config get) still documents "Secret-looking keys are masked" — but that command/behavior doesn't exist in the currentdeepsecCLI'sshow.Repro
Suggested fix
Port the masking logic from the legacy
config_get/config_setinto the currentshowcommand — mask any key whose dotted path containskey,pass,token,secret, etc. before printing, e.g.sk-ant-xx...instead of the full value. Ideally add a test assertingconfig showoutput never contains a full-length secret string when one is configured.Impact
Anyone running
deepsec config show— including AI coding assistants executing shell commands on a user's behalf — gets the raw API key in their output/logs/transcript. This is a real-world footgun for a security-focused tool.