From 7c517f7fcc07f4686fcf4e067d69ca49dd2c1e6a Mon Sep 17 00:00:00 2001 From: host Date: Sun, 8 Nov 2026 06:01:54 +0000 Subject: [PATCH] =?UTF-8?q?fix(chv-muse-r12fix2-2):=20chv-sync=20=E5=9B=9E?= =?UTF-8?q?=E8=90=BD=E6=98=BE=E6=80=A7=E5=8C=96=EF=BC=9A=E8=AD=A6=E5=91=8A?= =?UTF-8?q?=20+=20=E6=AF=8F=E5=9B=9E=E8=90=BD=E4=B8=80=E8=A1=8C=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=EF=BC=88P6-2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R12 复测:worker 226 期间 chv-sync 全程静默走直接路径回落,pull/ push/status 表面 rc=0,privsep/safety 一行没跑——回落只有一句 stderr 警告,无事后可数的痕。本提交让一次回落留两处痕:stderr 警告保留 “worker 不在”标记(措辞加“未走 worker,privsep 未生效”,供驱动 识别)+ 追加一行 JSON 审计到 $USER_HOME/.chv-sync-fallback-audit.log(CHV_SYNC_FALLBACK_LOG 可改 位置;事件/动作/仓/时间戳,delegated_to_worker=false、 privsep_active=false)。审计写失败只记 stderr,绝不中断同步(与 worker _audit 同款口径)。gap 文档诚实边界第 4 条同步更新。 取舍:不改 fail-closed,仍是 fail-open。理由:旧镜像(无 worker) 须仍能同步,改 fail-closed 会把所有旧镜像的同步当场 brick,且无 镜像外恢复手段;本轮项 1 已让新镜像的 worker 开机即起,回落只在 worker 真挂 / 旧镜像时发生,不再是常态路径。旧镜像退役后改为无 worker 即 fail-closed(原定方向不变,见 chv-sync.sh 头注)。 测试:ChvSyncDelegationTest 新增——回落审计一行 JSON(字段全)、 两次回落两行、审计落盘失败不中断同步;走 worker 时不留回落审计 (反向对照)。 --- chv-guest/chv-sync.sh | 34 +++++++++++++++++++++++---- cloud/tests/test_chv_muse_p6.py | 41 +++++++++++++++++++++++++++++++++ docs/chv-muse-p6-gap.md | 11 +++++---- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/chv-guest/chv-sync.sh b/chv-guest/chv-sync.sh index 49c58e0..4ccaa08 100755 --- a/chv-guest/chv-sync.sh +++ b/chv-guest/chv-sync.sh @@ -35,8 +35,17 @@ WORKSPACE="${CHV_WORKSPACE:-/home/chv/workspace}" USER_HOME="${CHV_USER_HOME:-/home/chv}" SYNC_USER="${CHV_USER:-chv}" # privsep worker(feat-chv-muse-p6):socket 在时 pull/push/status -# 改走 worker(类型化请求),不在时走旧的直接路径 + 警告(过渡兼容, -# 旧镜像退役后改为无 worker 即 fail-closed)。 +# 改走 worker(类型化请求),不在时走旧的直接路径(过渡兼容)。 +# round-12 修复轮第二轮(项 2):回落仍是 fail-open,不改 +# fail-closed——旧镜像(无 worker)须仍能同步,改 fail-closed 会把 +# 所有旧镜像的同步当场 brick,且无镜像外恢复手段;本轮项 1 已让新 +# 镜像的 worker 开机即起,回落只在 worker 真挂 / 旧镜像时发生,不 +# 再是常态路径。但回落不再静默:每次回落都在 stderr 警告(保留 +# “worker 不在”标记供驱动识别)+ 追加一行 JSON 审计 +# ($USER_HOME/.chv-sync-fallback-audit.log,可用 +# CHV_SYNC_FALLBACK_LOG 改位置;写失败只记 stderr,绝不中断同步, +# 与 worker 的 _audit 同款口径)。事后 grep 该审计文件即能数出回落 +# 次数、动作与仓。旧镜像退役后改为无 worker 即 fail-closed。 WORKER_SOCK="${CHV_GITEA_WORKER_SOCK:-/run/chv-gitea/worker.sock}" WORKER_CLIENT="${CHV_GITEA_WORKER_CLIENT:-/usr/local/bin/chv-sync-worker-client}" @@ -52,6 +61,21 @@ try_worker() { "$WORKER_CLIENT" "$action" "$@" } +# 回落显性化(round-12 修复轮第二轮,项 2):一次回落留两处痕—— +# stderr 警告 + 一行 JSON 审计(事件/动作/仓/时间戳,明确记 +# delegated_to_worker=false、privsep_active=false)。审计写失败 +# 只记 stderr,绝不中断同步。 +fallback_warn() { + local action="$1" repo="${2:-}" + echo "chv-sync: worker 不在,走旧的直接路径(过渡兼容):未走 worker,privsep 未生效" >&2 + local log="${CHV_SYNC_FALLBACK_LOG:-$USER_HOME/.chv-sync-fallback-audit.log}" + local ts + ts="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unknown)" + repo="${repo//\"/}" + printf '%s\n' "{\"event\":\"chv_sync_worker_fallback\",\"action\":\"$action\",\"repo\":\"$repo\",\"delegated_to_worker\":false,\"privsep_active\":false,\"ts\":\"$ts\"}" >> "$log" 2>/dev/null \ + || echo "chv-sync: 回落审计落盘失败:$log" >&2 +} + # 解析仓目录:显式给则用它;否则取 WORKSPACE 下唯一的 git 仓库。 resolve_repo() { if [ -n "${1:-}" ]; then @@ -128,7 +152,7 @@ cmd_pull() { local rc=0 try_worker pull "${1:-}" || rc=$? [ "$rc" != "42" ] && exit "$rc" - echo "chv-sync: worker 不在,走旧的直接路径(过渡兼容)" >&2 + fallback_warn pull "${1:-}" local dir dir="$(resolve_repo "${1:-}")" [ -z "$(git -C "$dir" status --porcelain)" ] \ @@ -142,7 +166,7 @@ cmd_push() { local rc=0 try_worker push "${1:-}" || rc=$? [ "$rc" != "42" ] && exit "$rc" - echo "chv-sync: worker 不在,走旧的直接路径(过渡兼容)" >&2 + fallback_warn push "${1:-}" local dir dir="$(resolve_repo "${1:-}")" if git -C "$dir" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then @@ -159,7 +183,7 @@ cmd_status() { local rc=0 try_worker status "${1:-}" || rc=$? [ "$rc" != "42" ] && exit "$rc" - echo "chv-sync: worker 不在,走旧的直接路径(过渡兼容)" >&2 + fallback_warn status "${1:-}" local dir dir="$(resolve_repo "${1:-}")" git -C "$dir" status -sb diff --git a/cloud/tests/test_chv_muse_p6.py b/cloud/tests/test_chv_muse_p6.py index 47144c0..ee010bc 100644 --- a/cloud/tests/test_chv_muse_p6.py +++ b/cloud/tests/test_chv_muse_p6.py @@ -11,6 +11,8 @@ 缺失 fail-closed、git argv 里只有 helper 配置绝无 token、输出先 扫描后返回、block 阻断、flag 标记; - chv-sync.sh 委托:worker 在时走 worker、不在时走旧路径 + 警告; + 回落每次留 stderr 警告 + 一行 JSON 审计(fail-open 保留,见 + chv-sync.sh 头注); - 执行器推理边界审计钩子:只记指纹、不记值、失败不中断。 """ @@ -653,6 +655,8 @@ class ChvSyncDelegationTest(unittest.TestCase): self.env["CHV_WORKSPACE"] = self.ws self.env["CHV_USER_HOME"] = self.tmp.name self.env["CHV_GITEA_WORKER_CLIENT"] = self.client + self.audit = os.path.join(self.tmp.name, "fallback-audit.log") + self.env["CHV_SYNC_FALLBACK_LOG"] = self.audit def tearDown(self): self.tmp.cleanup() @@ -668,7 +672,42 @@ class ChvSyncDelegationTest(unittest.TestCase): self.tmp.name, "no-worker.sock") proc = self._run_sync("status") self.assertIn("worker 不在", proc.stderr) + self.assertIn("privsep 未生效", proc.stderr) self.assertNotEqual(proc.returncode, 0) + # 一次回落一行 JSON 审计:动作 + 未委托 + privsep 未生效。 + with open(self.audit) as fh: + lines = [line for line in fh if line.strip()] + self.assertEqual(len(lines), 1) + record = json.loads(lines[0]) + self.assertEqual(record["event"], "chv_sync_worker_fallback") + self.assertEqual(record["action"], "status") + self.assertIs(record["delegated_to_worker"], False) + self.assertIs(record["privsep_active"], False) + self.assertIn("ts", record) + + def test_fallback_audit_accumulates_one_line_per_call(self): + self.env["CHV_GITEA_WORKER_SOCK"] = os.path.join( + self.tmp.name, "no-worker.sock") + self._run_sync("status") + self._run_sync("status") + with open(self.audit) as fh: + lines = [line for line in fh if line.strip()] + self.assertEqual(len(lines), 2) + for line in lines: + self.assertEqual( + json.loads(line)["event"], "chv_sync_worker_fallback") + + def test_fallback_audit_failure_does_not_break_sync(self): + # 审计落盘失败只记 stderr,绝不中断同步(与 worker _audit + # 同款口径):同步本身的退出路径不变。 + self.env["CHV_GITEA_WORKER_SOCK"] = os.path.join( + self.tmp.name, "no-worker.sock") + self.env["CHV_SYNC_FALLBACK_LOG"] = os.path.join( + self.tmp.name, "no-such-dir", "audit.log") + proc = self._run_sync("status") + self.assertIn("回落审计落盘失败", proc.stderr) + self.assertNotIn("Traceback", proc.stderr) + self.assertNotEqual(proc.returncode, 0) # 假 .git 仓,git 失败 def test_worker_present_delegates(self): mod = _load_guest_worker() @@ -692,6 +731,8 @@ class ChvSyncDelegationTest(unittest.TestCase): self.assertEqual(proc.returncode, 0) self.assertIn("clean tree", proc.stdout) self.assertNotIn("worker 不在", proc.stderr) + # 走 worker 时不留回落审计(反向对照)。 + self.assertFalse(os.path.exists(self.audit)) finally: server.shutdown() server.server_close() diff --git a/docs/chv-muse-p6-gap.md b/docs/chv-muse-p6-gap.md index c493f42..791066b 100644 --- a/docs/chv-muse-p6-gap.md +++ b/docs/chv-muse-p6-gap.md @@ -143,10 +143,13 @@ Base64、零宽字符)、藏在图片/附件/二进制里的指令、需要理 (authd 放凭证前)+ 只配 helper 的 git + systemd 沙箱;eBPF cgroup 拦截 + LSM 污点传播是 P5 的范围。不得把本轨写成“出口已 全部经 Sentinel 强制”。 -4. **过渡兼容留了旧路径**:worker socket 不存在时 `chv-sync` - pull/push/status 仍走旧的直接路径(只多一句 stderr 警告),为 - 的是旧镜像不被一次改断。旧镜像退役后应删掉该回落,改为无 worker - 即 fail-closed;删除前不得声称“旧路径已关闭”。 +4. **过渡兼容留了旧路径(仍是 fail-open,round-12 修复轮第二轮 + 项 2 钉死)**:worker socket 不存在时 `chv-sync` pull/push/status + 仍走旧的直接路径,为的是旧镜像不被一次改断;但回落不再静默—— + 每次回落都在 stderr 警告(“worker 不在…privsep 未生效”)+ 追加 + 一行 JSON 审计(`~/.chv-sync-fallback-audit.log`)。旧镜像退役 + 后应删掉该回落,改为无 worker 即 fail-closed;删除前不得声称 + “旧路径已关闭”。 5. **无真机验证**:本轮全部是代码 + 本地单测(含 worker 真 socket 回路 + 假 git runner)。真机待验证清单见 §4。