diff --git a/docs/defensive-patterns.i18n.yaml b/docs/defensive-patterns.i18n.yaml index e845e8182b..9a82a28afd 100644 --- a/docs/defensive-patterns.i18n.yaml +++ b/docs/defensive-patterns.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/defensive-patterns.md -defensive-patterns.md: b6e643cad6180ea35a363f2c4131b2c24f5f70af -defensive-patterns.zh.md: e1a6abdfd2138a51a13ceaf418bf6df1d0c65579 +defensive-patterns.md: 9db582354628a13abd43c5bd052dbbfd6e52f79f +defensive-patterns.zh.md: 7bebbe3c1964f2b826afc523eaa4af7be180e36e diff --git a/docs/defensive-patterns.md b/docs/defensive-patterns.md index b6e643cad6..9db5823546 100644 --- a/docs/defensive-patterns.md +++ b/docs/defensive-patterns.md @@ -27,3 +27,7 @@ A user-supplied listener that throws must not reject the promise it runs inside ## Never hand untrusted output the ambient environment or predictable paths Spawned commands get a scrubbed env (drop `*KEY*`/`*SECRET*`/`*TOKEN*`/`*PASSWORD*`) so harness credentials cannot leak into output, `env`, or spill files. Temp/spill files use a private (0700) dir, random names, and exclusive owner-only opens (`'wx'`, `0o600`) — predictable world-readable paths invite symlink races and disclosure. + +## Unlink link-shaped paths + +A path that may be a symlink or Windows junction is removed with `lstatSync().isSymbolicLink()` then `unlinkSync`: unlink deletes only the link and refuses a real directory, so it never follows the link into its target. Windows `rmSync(link)` throws `ERR_FS_EISDIR` on a junction; recursive deletion may descend through one into its target. Reserve recursive `rmSync` for known real directories. diff --git a/docs/defensive-patterns.zh.md b/docs/defensive-patterns.zh.md index e1a6abdfd2..7bebbe3c19 100644 --- a/docs/defensive-patterns.zh.md +++ b/docs/defensive-patterns.zh.md @@ -26,4 +26,8 @@ ## 绝不将环境变量或可预测路径暴露给不可信输出 -启动的命令应使用经过清理的环境变量,移除名称匹配 `*KEY*`、`*SECRET*`、`*TOKEN*` 或 `*PASSWORD*` 的项,防止 harness 凭证通过命令输出、`env` 或 spill 文件泄漏。临时文件和 spill 文件应放在权限为 0700 的私有目录中,使用随机文件名,并以独占且仅所有者可访问的方式打开(`'wx'`、`0o600`);可预测且所有用户均可读的路径会引发符号链接竞态和信息泄露。 +启动的命令应使用经过清理的环境变量,移除名称匹配 `*KEY*`、`*SECRET*`、`*TOKEN*` 或 `*PASSWORD*` 的项,防止 harness 凭证通过命令输出、`env` 或 spill 文件泄漏。临时文件和 spill 文件应放在权限为 0700 的私有目录中,使用随机文件名,并以独占且仅所有者可访问的方式打开(`'wx'`、`0o600`);可预测且全局可读的路径会引发符号链接竞态和信息泄露。 + +## 用 unlink 删除链接形态的路径 + +可能是符号链接或 Windows junction 的路径,应先用 `lstatSync().isSymbolicLink()` 判断,再用 `unlinkSync` 删除:unlink 只删除链接本身并拒绝真实目录,因此绝不会跟随链接进入其目标。Windows 上对 junction 调用 `rmSync(link)` 会抛 `ERR_FS_EISDIR`;递归删除可能穿过 junction 进入其目标。真实目录才使用带 `recursive` 的 `rmSync`。