Files
ISEP/hooks/leo21c-write-guard.sh
T
isep-hand 65120a1c65 雲端要拿得到票、主線與通知:白名單住 ISEP、主線檔隨 repo 走、leo21c 讀放寫擋(inkstone/ISEP#130)
- docs/permissions-allow.json + scripts/settings-allow-sync:四個 Gitea 正門工具的權限白名單一份,
  setup script 裝完 plugin 寫一次、SessionStart 每次再對一次(只加不減、冪等)
- hooks/lib/mainline.py/scripts/mainline:家目錄沒主線就讀 InkStoneCo/system-dev/mainline.json;
  set/adopt/clear 兩份一起寫,refresh 只寫家目錄
- hooks/leo21c-write-guard.sh:唯讀 -d(tr/cut/sort…)先剪掉再判、notify_leo trigger 放行
  (與 prod-write-guard 同一份白名單)、改法段改印 09-02 起的 youlin 子網域;補第一支測試(26 條)
- prod-write-guard/main-and-prod-push-guard/kbdb-live-exam:認得 youlin 新子網域 arcrun-yuga3bse
- scripts/ticket:收件 repo 寫 inkstone/ISEP 不再 404(org 寫錯當場講)
- scripts/isep-notify:有 TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID 先走 Bot API 直送(雲端唯一通的路)
- 測試:A31–A35 共 79 條;README/plugin.json/hooks-inventory 數字實數(61 支、85 條、53 支腳本)

假設(記在這裡等 review):權限規則的形狀沿用 leo 09-07 親手加、實測有效的那四條;
「分類器真的不擋」要雲端一趟 run 的 permission_denials 才驗得到,本 PR 驗不了。
版本:待總管定版(plugin.json 仍 0.22.0)。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTZ9QtvjY7MNxjfQbexAm7
2026-09-07 01:17:15 +00:00

127 lines
7.1 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# leo21c-write-guard.sh — 不准寫 leo 的個人帳號(leo 2026-08-20 立)
#
# leo 原話:
# 「youlin = stagegeek6688 = 測試 prod 出貨機,uncle6 = 中心服務,
# 這三個都可以拿來當 default,但你要實驗當然是放在 youlin。」
# 「**leo21c 就是我這個普通用戶,不應該讓你去操控,我只用公開的更新。**」
#
# 🔴 為什麼要機器守(2026-08-20 實錯,本閘的來由):
# 總管派 subagent 驗碎形目錄索引,它用了本機 `~/.arcrun/config.yaml` 的預設
# `cypher_executor_url: …leo21c…``api_key: bfezv28v`),
# 把 8 張測試卡寫進 leo 的真庫。
# 而當時 `system-dev/wiki/agent-memory.md` §2 白紙黑字寫著
# 「底層帳號=leo21c,**任何自動化只准落在它上面**」——那是 dogfood 時代的舊分工。
# ⇒ **文件教錯 + 機器預設也錯 ⇒ 沒指定的一律流進他的帳號。**
# ⇒ 規則改對了還不夠:`~/.arcrun/config.yaml` 至今仍指著 leo21c
# (它的 KV id 與 encryption_key 是該實例專屬,換不過去),
# 所以**真正擋得住的是這道閘**,不是那張表。
#
# 判準(封動作,不封措辭 —— 同 empty-handed-stop-guard 的哲學):
# 命中 leo21c 的座標 + 這是一個寫入動作 ⇒ 擋
# 只是讀(GET/查詢/grep 到那個字串) ⇒ 放行
# 發一則 notify_leo(通知型 trigger,什麼都沒改)⇒ 放行(同 prod-write-guard 的白名單)
set -uo pipefail
payload=$(cat)
cmd=$(printf '%s' "$payload" | python3 -c "
import json,sys
try: print((json.load(sys.stdin).get('tool_input') or {}).get('command',''))
except Exception: print('')
" 2>/dev/null)
[ -z "$cmd" ] && exit 0
# 🔴 這些工具碰不到 CF 帳號 ⇒ 整個放行(2026-08-20 上線當天就誤攔兩次,本段是修正)
# ① `git commit -m "…leo21c… acr update…"` 同時命中座標與寫入動詞而被擋
# ② 連「修這道閘本身」的指令都被擋(測試案例裡自然含觸發字)
# 但 git 與文字編輯根本寫不到那台實例,**訊息裡寫到什麼都不會造成寫入**。
# leo 2026-08-17:「紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎。」
# ⇒ 閘要問「這個指令能不能真的寫到那台」,不是「這段文字提到什麼」。
case "$cmd" in
git\ *|jj\ *|" git "*) exit 0 ;;
esac
if printf '%s' "$cmd" | grep -qE '^[[:space:]]*(git|jj)[[:space:]]'; then
exit 0
fi
# 修這道閘自己:命中的是本檔路徑就放行(否則永遠改不動它)
if printf '%s' "$cmd" | grep -q 'leo21c-write-guard'; then
exit 0
fi
# ── 判準:這個指令會不會真的把東西寫到那台?────────────────────────────────
# 🔴 2026-08-20 第三次誤攔後收斂(前兩次:git commit 訊息、修閘自己)。
# 舊判準是「文字裡有沒有出現座標」+「有沒有寫入動詞」,於是
# `curl -X PATCH .../api/v1/repos/inkstone/Arcrun/issues/145 -d '{"state":"closed"}'`
# **只因為票的內文提到那個帳號就被擋**——而那是打 Gitea,跟 CF 一點關係都沒有。
# ⇒ 只認**兩種真的會寫到那台的形狀**,其餘一律不管文字寫了什麼:
# ① 寫入請求打到 `*.leo21c.workers.dev`
# ② 用它的 CF 帳號 id 去跑 wrangleracr
# leo 2026-08-17:「紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎。」
hit=0
# ① 寫入請求打到那台 worker
if printf '%s' "$cmd" | grep -qiE 'https?://[^[:space:]"'"'"']*leo21c\.workers\.dev'; then
# 🔴 (a) 通知型 trigger 放行(inkstone/ISEP#130;判準與 prod-write-guard.sh 同一份):
# `…/webhooks/named/<ns>/notify_leo/trigger` 發一則 Telegram 給 leo**什麼都沒改**。
# wiki `agent-memory.md` 寫明它是找 leo 的正式通道;prod-write-guard 在 ISEP#63 就放行了它,
# 本閘卻因為路徑尾巴是 `/trigger` 照擋 ⇒ 兩支閘對同一條指令說不同的話,
# 而「發通知」被擋的結果是**雲端撞到人閘也叫不動 leo**inkstone/InkStoneCo#110)。
# 放行的範圍刻意只有一個名字:指令裡打到 leo21c 的**每一個**網址都得是它,
# 混進部署端點(`…/webhooks/named` 無名字)或別的工作流 ⇒ 照擋。
NOTIFY_ONLY=$(printf '%s' "$cmd" | python3 -c '
import re, sys
cmd = sys.stdin.read()
ALLOW = {"notify_leo"}
urls = [u for u in re.findall(r"https?://[^\s\"\x27<>]+", cmd) if "leo21c.workers.dev" in u.lower()]
PAT = re.compile(r"https?://[^/]+/webhooks/named/[^/]+/([A-Za-z0-9_-]+)/trigger/?$")
def notify(u):
m = PAT.match(u.rstrip(",;)"))
return bool(m) and m.group(1) in ALLOW
print("notify-only" if urls and all(notify(u) for u in urls) else "")
' 2>/dev/null || printf '')
if [ "$NOTIFY_ONLY" != "notify-only" ]; then
# 🔴 (b) 先剪掉唯讀工具的 `-d`inkstone/ISEP#13009-04 雲端實撞):
# `curl -s https://…leo21c…/x | tr -d '\r'`、`cut -d= -f2`、`sort -d`、`date -d` ……
# 這些 `-d` 沒有一個會寫到那台,舊判準 `-d[[:space:]]` 卻把整條純 GET 擋下
# ⇒ 「連讀都被擋」。prod-write-guard.sh 在 2026-08-12 就修過同一個洞,本閘漏了。
# 剪法與那支同一句 sed,剪完剩下的 `-d` 才是 curl 的 body。
cmd_w=$(printf '%s' "$cmd" | sed -E 's/(^|[|;&( ])(tr|cut|sort|uniq|date|xargs|paste|join|du|logger|split|comm)[[:space:]]+-d/\1\2 __READONLY_D__/g')
if printf '%s' "$cmd_w" | grep -qE -- '-X *(POST|PUT|PATCH|DELETE)|--request *(POST|PUT|PATCH|DELETE)|--data|--data-raw|-d[[:space:]]|/trigger'; then
hit=1
fi
fi
fi
# ② 拿它的 CF 帳號去部署
if printf '%s' "$cmd" | grep -qE 'CLOUDFLARE_ACCOUNT_ID=51a01bfa2665bd7bc3fd080dc40cf3e1'; then
if printf '%s' "$cmd" | grep -qE 'wrangler|acr |node .*index\.js'; then
hit=1
fi
fi
[ "$hit" = "1" ] || exit 0
cat >&2 <<'MSG'
🚫 不准寫 leo 的個人帳號 leo21cleo 2026-08-20 立)
leo 原話:「**leo21c 就是我這個普通用戶,不應該讓你去操控,我只用公開的更新。**」
youlin ← 🟢 你的 stage:做實驗、跑驗證,**沒指定就用這個**
geek6688 ← 測試 prod 出貨機
uncle6 ← 中心服務(安裝器/文件站/bundle),出貨線的目的地
leo21c ← 🔴 leo 本人在用的知識庫。**讀可以,寫不行。**
改法:把目標明寫成 youlin,不要吃 `~/.arcrun/config.yaml` 的預設——
那個檔至今仍指著 leo21cKV id 與 encryption_key 是該實例專屬,換不過去)。
cypher : https://arcrun-cypher-executor.arcrun-yuga3bse.workers.dev
(09-02 重裝後的子網域;舊名 youlin-hsieh-dev 的 DNS 已不存在,打它一律 000)
ns : yuga3bse
CF : 1129efd7df2e8899d537e9c8fbabb6cb
token : 頂層 .env 的 CLOUDFLARE_API_TOKEN_YOULIN_CC_USE
📌 2026-08-20 實錯:subagent 吃了那個預設,把 8 張測試卡寫進 leo 的真庫
library=demo-real-verify)。當時 wiki 還教「任何自動化只准落在 leo21c」。
MSG
exit 2