本机 Agent 去调带鉴权的接口,或者顺手连一下开发库,命令看着挺正常:curl -u user:secret …,或者 mysql -u… -p密码。过一会儿用 ps 扫进程列表,密码串还挂在那里。容易先怪 curl / mysql 客户端没把 argv 抹掉。更稳的做法是先拆两层:外层包命令的 shell,和真正干活的那个子进程。两层看清楚了,才知道该改配置、改注入方式,还是别让模型直接拼明文。
子进程自己可能已经抹了
MySQL 8.4 手册里的 End-User Guidelines for Password Security 写得很明白:-ppassword / --password=… 写在命令行上不安全,别的用户用 ps 一类工具可能看见。客户端初始化时通常会把 argv 里的密码覆写成零,但仍有一小段可见窗口。有些系统上覆写根本不起作用,密码会一直留在进程列表里。交互式只写 -p、不带值,让客户端自己提示输入,比把明文贴在 argv 上稳一些。非交互场景更常见的做法是权限 600(或 400)的 option file,或者用 mysql_config_editor 写进混淆过的 .mylogin.cnf,再用 --login-path。
mysql_config_editor 那一节还补了两点容易忽略的:混淆是防无意泄露,不能当不可破的加密。有管理员权限的人仍可能费点功夫解开。登录路径文件还必须只有当前用户可读写,否则工具和客户端都会直接忽略它。终端标题栏如果显示当前命令,明文密码也可能一直挂在窗口标题上——手册里也点过这个坑。
curl 一类工具在自己进程里对 -u 做覆写的行为,和 MySQL 客户端类似——你可能看到子进程那一行密码已经空了。别停在这一步就放心。
外层 shell 整段命令还在
社区里有用户报告:Claude Code 跑复合命令时,外层会用类似 zsh -c 'source … && eval …' 的方式包一层。对照官方 Security 页和 changelog,当前 npm 包版本能查到 2.1.318。Security 页谈的是权限模式、sandbox、凭证存放(macOS Keychain,Linux 上文件 mode 0600 等),并没有把「外层一定是 source && eval」写成官方固定实现。把这类现象标成用户报告更合适。本机复现时直接 ps / pstree 看自己装的版本,别把社区截图当成产品承诺。
差别在这:子进程把 -u 密码抹空,不等于外层 shell 的 argv 也干净了。外层那一行往往是整段脚本字符串,里面如果还带着明文密钥,命令跑多久,ps 就能看见多久。有人只盯 curl 那一行觉得「已经空了」,漏掉上面那个还挂着完整命令的 zsh / bash,就会误判成「已经安全」。
站里另有一篇讲 读不到 .env ≠ 命令行不泄露,角度是文件读权限和 Agent 读环境文件。这篇只盯进程列表和 argv,两边别混成一件事。
落地时少踩的几处
- 需要鉴权时,优先把密钥放进环境变量或权限收紧的配置,注入后立刻
unset掉临时变量。别让 Agent 直接拼mysql -p密码或curl -u user:pass。 - MySQL 本机脚本可以走
mysql_config_editor set --password,客户端用--login-path=…。或chmod 600的 option file,用--defaults-file指过去。 - 能挡一层就挡一层:本地 socket、只读 MCP / 自写 CLI 包在 Agent 前面,比让模型自由拼 argv 好控。敏感操作尽量走人审或固定封装,而不是对话里随手生成带密码的一行命令。
- 排查时同时看外层 shell 和子进程两行,不要只看 curl / mysql 那一行。必要时用
ps -ww/pstree -a把完整命令行展开。
不写怎么绕过沙箱,也不讨论去扫别人机器上的进程列表。你们本机用 Claude Code 跑带密钥的命令时,外层 argv 和子进程是不是经常不一致?有没有更稳、又不挡效率的写法?