diff --git a/deploy/lib/env-defaults.sh b/deploy/lib/env-defaults.sh index ecf5b69..eaa6b25 100644 --- a/deploy/lib/env-defaults.sh +++ b/deploy/lib/env-defaults.sh @@ -160,6 +160,28 @@ unset _am_self_missing _am_c # 实测:`setpriv --reuid=65534 env -i HOME=/root` ⇒ `touch $HOME/probe` 被拒。 # 以 **root** 跑时 `/root` 写得进去,所以这条只在非 root 下触发 —— # 判据落在"**能不能写**",不落在"路径长得像不像"。 +# +# ★★ 但"验证可写"**不能用 `[ -w ]`**(dsh 实测 2026-09-25,修本文件自己的一个假绿): +# `[ -w ]` 查的是**权限位**。以 uid=0 跑时它对任何 root 属主的目录都返回**真**, +# 而**实际拦截可能在另一层**。本机实测(同一台、同一 shell): +# [ -w /root ] ⇒ true +# touch /root/x ⇒ Permission denied ← 能写才算数 +# [ -w /opt/agentmail ] ⇒ true +# touch /opt/agentmail/x ⇒ Permission denied +# (本会话每次 shell 都带着 `landlock-run: partial enforcement` —— 拦截在沙箱层, +# 权限位那层**看不出来**。) +# ⇒ 后果正是本文件要防的那件事,而且更绕:`_am_home=/root` 被**误判为可用** ⇒ +# `HOME=/root` 放行 ⇒ ② 段拿 `$HOME/.cache/go-build` 当 GOCACHE ⇒ 那里用的是 +# **真写探针**(297 行)⇒ 探针失败 ⇒ 报出**「GOCACHE 不可写」**。 +# ⇒ 根因是 HOME,报出来的是 GOCACHE —— **误指了对象**,与本文件存在的理由正好相反。 +# ⇒ 所以这里也改用**真写探针**(与 297 行同一个办法,不再一处真写、一处只看权限位)。 +_am_can_write() { + [ -n "${1:-}" ] || return 1 + [ -d "$1" ] || return 1 + { : > "$1/.agentmail-wprobe-$$"; } 2>/dev/null || return 1 + rm -f "$1/.agentmail-wprobe-$$" + return 0 +} _am_uid="$(id -u 2>/dev/null || echo 0)" _am_home_given=0 if [ -z "${HOME:-}" ]; then @@ -174,9 +196,10 @@ else _am_home="$HOME" fi # 目录必须落在**调用者写得进去**的地方,否则就是把问题往后推。 -# 注意:`mkdir -p` 对已存在的不可写目录会**返回成功**,所以必须再单独判 `-w`。 +# 注意:`mkdir -p` 对已存在的不可写目录会**返回成功**,所以必须再单独判可写 —— +# 而那个"单独判"**必须真去写**(见上面 ★★:`[ -w ]` 在沙箱/ACL 那一层会假绿)。 _am_fallback="${TMPDIR:-/tmp}/agentmail-home-$_am_uid" -if [ -z "$_am_home" ] || [ ! -d "$_am_home" ] || [ ! -w "$_am_home" ]; then +if [ -z "$_am_home" ] || [ ! -d "$_am_home" ] || ! _am_can_write "$_am_home"; then if [ "$_am_home_given" = "1" ] && [ -n "$_am_home" ]; then # 调用者显式给了 HOME 却不可写:**说清楚**再改判,不静默换目录 # (静默改 HOME 会让"东西写到哪去了"变成一个谜)。 @@ -188,12 +211,21 @@ if [ -z "$_am_home" ] || [ ! -d "$_am_home" ] || [ ! -w "$_am_home" ]; then # 或 passwd 里的 home 不存在,例如 nobody 的 /nonexistent)。 # 原先只报"调用者给的不可写",于是这条**悄悄换了 HOME**: # 东西写到哪去了变成一个谜 —— 与本文件存在的理由(把环境问题说清楚)正好相反。 - printf ' [WARN] 按身份推出来的 HOME=%s 不可用(非 root / passwd 里没有可用目录);改判到 %s\n' \ - "${_am_home:-(空)}" "$_am_fallback" >&2 + # + # ★ 括注原写"(非 root / passwd 里没有可用目录)"—— 那是**按 uid 猜的原因**, + # 而判据已改成**真写探针**(见上 ★★)⇒ 现在 uid=0 也可能走到这里 + # (能写与否取决于沙箱/ACL,不取决于身份)。**不要再报一个没验过的原因**: + # 只报"写不进去"这个已测事实,并附上身份供人判断。 + # + # ⚠️ 文案前半句 `按身份推出来的 HOME=%s 不可用` **是 pi 的判据匹配的锚** + # (`plugins/pi-mail-bridge/test/env-guard.test.mjs:268` 用 assert.match 锁它, + # 理由是"兜底路径改了 HOME 就必须留痕")。⇒ **锚不许动**,只把括注改准。 + printf ' [WARN] 按身份推出来的 HOME=%s 不可用(已实测真写探针;uid=%s);改判到 %s\n' \ + "${_am_home:-(空)}" "$_am_uid" "$_am_fallback" >&2 fi _am_home="$_am_fallback" fi -if mkdir -p "$_am_home" 2>/dev/null && [ -w "$_am_home" ]; then +if mkdir -p "$_am_home" 2>/dev/null && _am_can_write "$_am_home"; then if [ "$_am_home" != "${HOME:-}" ]; then HOME="$_am_home" export HOME