fix(hooks): 讓四支閘認得出「指令位置」跟「heredoc/引號裡的文字」 #39

Merged
claude-code merged 1 commits from fix/group0-gates into main 2026-08-20 11:00:34 +00:00
Member

服務哪張票

inkstone/InkStoneCo#23inkstone/InkStoneCo#56(同屬 inkstone/ISEP#30 這個 hub:讓閘擋對東西)。

這條分支解掉什麼

同一個病的兩面:該擋的沒擋(假陰性)、不該擋的一直擋(假陽性)

  • heredoc body 是資料不是指令——新增共用輔助 hooks/lib/strip_heredoc.py
    四支閘(github-contact-guard.shmain-and-prod-push-guard.sh
    stage-before-prod-guard.shkbdb-api-wall-guard.sh 的 Bash 分支)呼叫前
    一律先拿掉 heredoc body 再比對關鍵字。解掉 #23 comment(2026-08-20 寫
    docs/TESTING.mdgithub-contact-guard.sh 誤擋)與 #56 comment
    (貼一則引用 #56 自己敘述的 Gitea 留言,被 main-and-prod-push-guard.sh 誤擋)。

  • main-and-prod-push-guard.sh 補回跟 release-tag-guard.sh 同款、已驗證過的洞
    舊版「命令開頭是讀取工具(echo/cat/grep…)就整條放行」——
    echo x && git push gitea HEAD:main 會被整條放走,真的推 main 完全不看。
    改成「危險關鍵字出現在任何位置都不吃開頭豁免」,git push 的偵測本身也從
    「整串找子字串」改成「指令位置」(^;&|( &&|| 之後才算數)。
    順手把 *main*|*master* 改成單字邊界比對——舊版連分支名裡的 "domain"
    (d-o-m-a-i-n)都會誤中。

  • github-contact-guard.sh 拿掉一個鬆散邊界gh CLI/git push 的判準原本
    \s(任意一個空白)當「指令位置」的替代品,等於「只要前面有個空格就算數」
    ——散文裡幾乎處處都有空格。改成只認真正的指令分隔符。

  • kbdb-api-wall-guard.sh 的 Bash 分支是個真的漏擋:原本引用
    hooks/kbdb_cmd_check.py,這個檔案在這個 repo 從未存在過(git log --all
    查證,origin/main 上也是同樣的斷引用)。python3 找不到檔案就非零結束,
    呼叫端寫成 ... || echo "OK",於是這個分支永遠印 "OK"——任何
    wrangler d1 execute 直打 arcrun-kbdb 都會被放行,完全沒有機會擋下。
    邏輯搬進新檔 hooks/lib/kbdb_cmd_check.py(shlex 分詞、quote-aware,
    heredoc-strip 前置),把 .sh 的參照路徑改過去,並補回 Bash 分支原本
    就該有、但從沒真正生效過的 kbdb-sql-ok 逃生口。

四支閘各補 InkStoneCo#40 §1 要求的三行中文檔頭(管什麼/為什麼/誤觸時怎麼關)。

沒動什麼

  • 沒新增任何 .sh 閘(InkStoneCo#40 §7:手寫 hook 只減不增);新檔都是
    hooks/lib/ 下的共用輔助程式(.py,不是閘)。
  • 沒放寬任何真違規的判準——四支測試都明確包含「真的推 main/真的打 tag/
    真的碰 prod/真的繞過 KBDB」的案例,全部仍然擋下。
  • 沒動 docs/labels.yaml.claude-plugin/

怎麼驗(雙向都要)

四支新測試,scripts/test-{github-contact,main-and-prod-push,stage-before-prod,kbdb-api-wall-guard-bash}.sh
共 69 條斷言全過(貼在票的留言裡有完整輸出)。另外兩支既有測試
test-release-tag-guard.shtest-ticket-api-bypass-guard.sh)跑過,
仍然 8/8、13/13——沒有因為這次改動而回歸。

誠實列出:還漏得過去的形狀

  1. 子行程繞過(#56 原案的假陰性那一半)——這四支都是 PreToolUse:Bash 掛勾,
    看得到的只有「Claude Code 的 Bash 工具呼叫本身的指令字串」。若某支程式
    (如 installer/scripts/ship.mjs)自己用 node:child_process 直接
    spawn('git', ['push', ...]),那個 push 是子行程,不會產生新的 Bash 工具
    呼叫,這四支從頭到尾看不到它——這是 github-contact-guard.sh 檔頭早就
    寫明的架構限制,不是這次能在 ISEP 這邊解掉的(github-contact-guard.sh
    檔頭已指向對應的 in-process 解法 installer/scripts/d20-guard.mjs
    那是 arcrun-rag 那個 repo 的事)。
  2. 雲端 session 的 cwd 誤判(#56 2026-08-19 那則留言)——github-contact-guard.sh
    用 hook payload 的 cwd 解 remote,雲端 session 的 cwd 是薄殼,會把任何
    repo 的 origin 都誤判成 GitHub。這次沒動這段(不是 heredoc/指令位置這兩個
    已驗證判準能覆蓋的問題,需要另外從指令本身的 git -C <path>cd 解析
    目標,範圍更大,留給下一輪或另開票處理)。
  3. git commit -m "..." 裡若真的出現「git」與「push」被同一段非 ;&| 文字
    隔開,且 push 後面剛好是空白
    (例如 git commit -m "mentions git push to origin"),目前的正則仍可能誤觸——因為判準是正則模式比對,不是完整的
    shell 語法解析,沒有真正的引號邊界概念。用 scripts/test-main-and-prod-push-guard.sh
    裡的 case 6/7 驗過幾種常見寫法(帶標點、加引號提及規則本身)目前都過得去,
    但沒有窮舉。kbdb-api-wall-guard.sh 的 Bash 分支用 shlex 分詞做到了真正
    quote-aware,是比較徹底的做法;其餘三支仍是正則比對,這是刻意的取捨
    (範圍小、風險低,避免大改一個歷史包袱很重的檔案)。

相關

closes(部分)inkstone/InkStoneCo#23inkstone/InkStoneCo#56;hub inkstone/ISEP#30

🤖 Generated with Claude Code

## 服務哪張票 `inkstone/InkStoneCo#23`、`inkstone/InkStoneCo#56`(同屬 `inkstone/ISEP#30` 這個 hub:讓閘擋對東西)。 ## 這條分支解掉什麼 同一個病的兩面:**該擋的沒擋(假陰性)、不該擋的一直擋(假陽性)**。 - **heredoc body 是資料不是指令**——新增共用輔助 `hooks/lib/strip_heredoc.py`, 四支閘(`github-contact-guard.sh`/`main-and-prod-push-guard.sh`/ `stage-before-prod-guard.sh`/`kbdb-api-wall-guard.sh` 的 Bash 分支)呼叫前 一律先拿掉 heredoc body 再比對關鍵字。解掉 #23 comment(2026-08-20 寫 `docs/TESTING.md` 被 `github-contact-guard.sh` 誤擋)與 #56 comment (貼一則引用 #56 自己敘述的 Gitea 留言,被 `main-and-prod-push-guard.sh` 誤擋)。 - **`main-and-prod-push-guard.sh` 補回跟 `release-tag-guard.sh` 同款、已驗證過的洞**: 舊版「命令開頭是讀取工具(echo/cat/grep…)就整條放行」—— `echo x && git push gitea HEAD:main` 會被整條放走,真的推 main 完全不看。 改成「危險關鍵字出現在任何位置都不吃開頭豁免」,`git push` 的偵測本身也從 「整串找子字串」改成「指令位置」(`^`/`;&|(` `&&`/`||` 之後才算數)。 順手把 `*main*|*master*` 改成單字邊界比對——舊版連分支名裡的 "domain" (d-o-**m-a-i-n**)都會誤中。 - **`github-contact-guard.sh` 拿掉一個鬆散邊界**:`gh` CLI/`git push` 的判準原本 用 `\s`(任意一個空白)當「指令位置」的替代品,等於「只要前面有個空格就算數」 ——散文裡幾乎處處都有空格。改成只認真正的指令分隔符。 - **`kbdb-api-wall-guard.sh` 的 Bash 分支是個真的漏擋**:原本引用 `hooks/kbdb_cmd_check.py`,這個檔案在這個 repo 從未存在過(`git log --all` 查證,origin/main 上也是同樣的斷引用)。`python3` 找不到檔案就非零結束, 呼叫端寫成 `... || echo "OK"`,於是這個分支**永遠印 "OK"**——任何 `wrangler d1 execute` 直打 `arcrun-kbdb` 都會被放行,完全沒有機會擋下。 邏輯搬進新檔 `hooks/lib/kbdb_cmd_check.py`(shlex 分詞、quote-aware, heredoc-strip 前置),把 `.sh` 的參照路徑改過去,並補回 Bash 分支原本 就該有、但從沒真正生效過的 `kbdb-sql-ok` 逃生口。 四支閘各補 `InkStoneCo#40` §1 要求的三行中文檔頭(管什麼/為什麼/誤觸時怎麼關)。 ## 沒動什麼 - 沒新增任何 `.sh` 閘(`InkStoneCo#40` §7:手寫 hook 只減不增);新檔都是 `hooks/lib/` 下的共用輔助程式(`.py`,不是閘)。 - 沒放寬任何真違規的判準——四支測試都明確包含「真的推 main/真的打 tag/ 真的碰 prod/真的繞過 KBDB」的案例,全部仍然擋下。 - 沒動 `docs/`、`labels.yaml`、`.claude-plugin/`。 ## 怎麼驗(雙向都要) 四支新測試,`scripts/test-{github-contact,main-and-prod-push,stage-before-prod,kbdb-api-wall-guard-bash}.sh`, 共 69 條斷言全過(貼在票的留言裡有完整輸出)。另外兩支既有測試 (`test-release-tag-guard.sh`/`test-ticket-api-bypass-guard.sh`)跑過, 仍然 8/8、13/13——沒有因為這次改動而回歸。 ## 誠實列出:還漏得過去的形狀 1. **子行程繞過(#56 原案的假陰性那一半)**——這四支都是 `PreToolUse:Bash` 掛勾, 看得到的只有「Claude Code 的 Bash 工具呼叫本身的指令字串」。若某支程式 (如 `installer/scripts/ship.mjs`)自己用 `node:child_process` 直接 `spawn('git', ['push', ...])`,那個 push 是子行程,不會產生新的 Bash 工具 呼叫,這四支從頭到尾看不到它——這是 `github-contact-guard.sh` 檔頭早就 寫明的架構限制,不是這次能在 ISEP 這邊解掉的(`github-contact-guard.sh` 檔頭已指向對應的 in-process 解法 `installer/scripts/d20-guard.mjs`, 那是 arcrun-rag 那個 repo 的事)。 2. **雲端 session 的 cwd 誤判(#56 2026-08-19 那則留言)**——`github-contact-guard.sh` 用 hook payload 的 `cwd` 解 remote,雲端 session 的 cwd 是薄殼,會把任何 repo 的 origin 都誤判成 GitHub。這次沒動這段(不是 heredoc/指令位置這兩個 已驗證判準能覆蓋的問題,需要另外從指令本身的 `git -C <path>`/`cd` 解析 目標,範圍更大,留給下一輪或另開票處理)。 3. **`git commit -m "..."` 裡若真的出現「git」與「push」被同一段非 `;&|` 文字 隔開,且 push 後面剛好是空白**(例如 `git commit -m "mentions git push to origin"`),目前的正則仍可能誤觸——因為判準是正則模式比對,不是完整的 shell 語法解析,沒有真正的引號邊界概念。用 `scripts/test-main-and-prod-push-guard.sh` 裡的 case 6/7 驗過幾種常見寫法(帶標點、加引號提及規則本身)目前都過得去, 但沒有窮舉。`kbdb-api-wall-guard.sh` 的 Bash 分支用 shlex 分詞做到了真正 quote-aware,是比較徹底的做法;其餘三支仍是正則比對,這是刻意的取捨 (範圍小、風險低,避免大改一個歷史包袱很重的檔案)。 ## 相關 closes(部分)`inkstone/InkStoneCo#23`、`inkstone/InkStoneCo#56`;hub `inkstone/ISEP#30`。 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-code added 1 commit 2026-08-20 10:53:11 +00:00
inkstone/InkStoneCo#23、#56 同一個病:閘對整條指令字串做關鍵字掃描,
把「檔案內容/留言引用裡剛好提到某個關鍵字」當成「真的在執行」,
同時放過包一層讀取指令、或藏在 heredoc body 裡的真動作。

- 新增共用輔助 hooks/lib/strip_heredoc.py:heredoc body 是資料不是指令,
  四支閘(github-contact / main-and-prod-push / stage-before-prod /
  kbdb-api-wall 的 Bash 分支)呼叫前一律先拿掉 body 再比對。
- main-and-prod-push-guard.sh:修掉跟 release-tag-guard.sh 同款的
  「開頭是讀取工具就整條放行」前綴繞過洞;git push 的偵測改成指令位置比對;
  main/master 目標改用單字邊界,不再誤中 "domain" 這種子字串。
- github-contact-guard.sh:拿掉 gh CLI/git push 判準裡「前面隨便一個空白
  就算數」的鬆散邊界,只認真正的指令分隔符。
- kbdb-api-wall-guard.sh:Bash 分支原本引用不存在的 kbdb_cmd_check.py,
  python3 找不到檔案就吃掉錯誤印 "OK",該分支形同虛設——任何
  `wrangler d1 execute` 直打 kbdb 都會被放行。邏輯搬進新檔
  hooks/lib/kbdb_cmd_check.py(shlex 分詞、quote-aware),把 .sh 的
  參照路徑改過去,補回 Bash 分支的 kbdb-sql-ok 逃生口。

四支各補 InkStoneCo#40 §1 要求的三行中文檔頭。

新增四支可重跑測試(scripts/test-*.sh),共 69 條斷言全過,
含 #23/#56 票上實撞的原始形狀(寫 docs/TESTING.md 的 heredoc、
貼引用 #56 敘述的留言、`grep git push`)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
claude-code merged commit a7ea0283ac into main 2026-08-20 11:00:34 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: inkstone/ISEP#39