助理巡檢改用字面絕對路徑,排程根目錄由 cron 條目帶入,避免無人值守輪次被權限層擋下 #14

Merged
admin merged 3 commits from fix/literal-absolute-paths-for-unattended-patrol into develop 2026-09-03 02:52:01 +00:00
Member

摘要

  • 需求描述:無人看管的排程輪次在第一支腳本就被權限層擋下,助理心跳寫不出來,斷了兩個多小時沒有人發現。實測確認權限層比對的是指令尚未展開的字面字串,因此把巡檢流程全面改成字面絕對路徑,並讓排程安裝時就把解好的根目錄寫進 cron 條目,由條目把路徑帶進每一輪。
  • 計畫名稱:無
  • 計畫頁:無
  • 分析頁:無

變更內容

檔案 為什麼改
skills/assistant/SKILL.md 新增路徑守則與 Step 0。守則寫明權限層比對未展開的字面字串,帶變數或波浪號的路徑一律要核准,並附七列實測佐證表。Step 0 從「自己跑 readlink 解路徑」改成「從叫用文字的『工具根目錄=』取字面絕對路徑」,排程那一輪一個解析指令都不跑;人在現場叫用才用 readlink 解一次,那一次有人可以按同意。取不到根目錄就停下回報,收尾狀態取 aborted。全篇 46 處腳本呼叫改成字面絕對路徑。
tools/schedule.sh 安裝排程時把解好的字面根目錄寫進 cron 條目的提示文字,並印成 patrol_root=,讓條目與允許規則共用同一個值。允許規則的提示改成完整字面路徑,不寫變數、波浪號與萬用字元。JSC_HOME 解不出絕對路徑時回結束碼 6,不讓相對路徑寫進條目。自訂巡檢指令沒帶那一段只警告、不中止。
references/behaviors.md 行為契約四列跟上新的路徑取得方式與中止條件;觸發時機未動。
README.md 路徑家族與 schedule.sh 用途兩處敘述更新。
plugin.json 版本從 0.1.5 升到 0.1.6,讓安裝端認得出要更新。
.claude-plugin/plugin.json 同上,由 manifest 同步工具一併升版。
.codex-plugin/plugin.json 同上,由 manifest 同步工具一併升版。

設計重點

  • 兩條判準來自實測,不是推論。一、無人值守時只有允許清單上的完整字面指令跑得動,沒有「預設安全的唯讀指令」這回事,連 readlink 與 ls 都要有自己的規則。二、路徑中段的萬用字元不匹配,規則與指令都必須是完整字面,所以帶版本號的快取路徑放不進允許清單。
  • 「先解路徑再用」因此不成立。解路徑的指令自己就過不了,而路徑既然能寫成字面,就不需要解。所以根目錄改由上游帶進來:安裝排程時解一次,寫死在 cron 條目裡,往後每一輪直接取用。
  • 條目與允許規則共用同一個值。兩邊各算各的就會差開,而差開的那一輪是被靜靜擋掉,沒有錯誤訊息,只有心跳消失。
  • 人在現場與無人值守分兩條路。有人時允許用 readlink 解一次,因為有人可以按同意;沒人時一個解析指令都不跑。取不到根目錄就停下回報,不猜也不退回帶變數的路徑。
  • 版號與主變更分成兩筆提交。三份 manifest 只帶版號、不帶行為,混進主變更會讓那一筆的 diff 夾雜非行為變更,分開之後也看得清楚哪一版對應哪一次變更。

測試結果

  • 2026-09-02 到 09-03 用非互動模式比照排程環境實測七種寫法,結果如下:

    寫法 結果
    允許清單上的完整字面路徑 執行了
    $JSC_HOME/... 開頭 擋下
    ~/... 開頭 擋下
    readlink -f "$JSC_HOME/current" 擋下
    ls -d "$JSC_HOME/current" 擋下
    沒有規則的字面 ls -d 擋下
    規則版本號寫成萬用字元的快取路徑 擋下
  • 待審閱者留意:這張 PR 是必要條件,不是充分條件。機器端還要把允許清單補齊到涵蓋巡檢會呼叫的七支腳本,本機已完成;其中 link-check.sh、wiki-contents.sh、report-status.sh 三支的已安裝版本目前還沒有,要等部署才補得上。

前置 Push Request

  • 無
## 摘要 - 需求描述:無人看管的排程輪次在第一支腳本就被權限層擋下,助理心跳寫不出來,斷了兩個多小時沒有人發現。實測確認權限層比對的是指令尚未展開的字面字串,因此把巡檢流程全面改成字面絕對路徑,並讓排程安裝時就把解好的根目錄寫進 cron 條目,由條目把路徑帶進每一輪。 - 計畫名稱:無 - 計畫頁:無 - 分析頁:無 ## 變更內容 | 檔案 | 為什麼改 | | --- | --- | | `skills/assistant/SKILL.md` | 新增路徑守則與 Step 0。守則寫明權限層比對未展開的字面字串,帶變數或波浪號的路徑一律要核准,並附七列實測佐證表。Step 0 從「自己跑 readlink 解路徑」改成「從叫用文字的『工具根目錄=』取字面絕對路徑」,排程那一輪一個解析指令都不跑;人在現場叫用才用 readlink 解一次,那一次有人可以按同意。取不到根目錄就停下回報,收尾狀態取 aborted。全篇 46 處腳本呼叫改成字面絕對路徑。 | | `tools/schedule.sh` | 安裝排程時把解好的字面根目錄寫進 cron 條目的提示文字,並印成 `patrol_root=`,讓條目與允許規則共用同一個值。允許規則的提示改成完整字面路徑,不寫變數、波浪號與萬用字元。`JSC_HOME` 解不出絕對路徑時回結束碼 6,不讓相對路徑寫進條目。自訂巡檢指令沒帶那一段只警告、不中止。 | | `references/behaviors.md` | 行為契約四列跟上新的路徑取得方式與中止條件;觸發時機未動。 | | `README.md` | 路徑家族與 `schedule.sh` 用途兩處敘述更新。 | | `plugin.json` | 版本從 0.1.5 升到 0.1.6,讓安裝端認得出要更新。 | | `.claude-plugin/plugin.json` | 同上,由 manifest 同步工具一併升版。 | | `.codex-plugin/plugin.json` | 同上,由 manifest 同步工具一併升版。 | ## 設計重點 - 兩條判準來自實測,不是推論。一、無人值守時只有允許清單上的完整字面指令跑得動,沒有「預設安全的唯讀指令」這回事,連 `readlink` 與 `ls` 都要有自己的規則。二、路徑中段的萬用字元不匹配,規則與指令都必須是完整字面,所以帶版本號的快取路徑放不進允許清單。 - 「先解路徑再用」因此不成立。解路徑的指令自己就過不了,而路徑既然能寫成字面,就不需要解。所以根目錄改由上游帶進來:安裝排程時解一次,寫死在 cron 條目裡,往後每一輪直接取用。 - 條目與允許規則共用同一個值。兩邊各算各的就會差開,而差開的那一輪是被靜靜擋掉,沒有錯誤訊息,只有心跳消失。 - 人在現場與無人值守分兩條路。有人時允許用 `readlink` 解一次,因為有人可以按同意;沒人時一個解析指令都不跑。取不到根目錄就停下回報,不猜也不退回帶變數的路徑。 - 版號與主變更分成兩筆提交。三份 manifest 只帶版號、不帶行為,混進主變更會讓那一筆的 diff 夾雜非行為變更,分開之後也看得清楚哪一版對應哪一次變更。 ## 測試結果 - 2026-09-02 到 09-03 用非互動模式比照排程環境實測七種寫法,結果如下: | 寫法 | 結果 | | --- | --- | | 允許清單上的完整字面路徑 | 執行了 | | `$JSC_HOME/...` 開頭 | 擋下 | | `~/...` 開頭 | 擋下 | | `readlink -f "$JSC_HOME/current"` | 擋下 | | `ls -d "$JSC_HOME/current"` | 擋下 | | 沒有規則的字面 `ls -d` | 擋下 | | 規則版本號寫成萬用字元的快取路徑 | 擋下 | - 待審閱者留意:這張 PR 是必要條件,不是充分條件。機器端還要把允許清單補齊到涵蓋巡檢會呼叫的七支腳本,本機已完成;其中 `link-check.sh`、`wiki-contents.sh`、`report-status.sh` 三支的已安裝版本目前還沒有,要等部署才補得上。 ## 前置 Push Request - 無
jiantw83 added 2 commits 2026-09-03 02:17:31 +00:00
無人看管的排程輪次在第一支腳本就被權限層擋下。心跳因此寫不出來,助理斷了兩個多小時,沒有人發現。

2026-09-02 到 09-03 用非互動模式比照排程環境實測七種寫法,歸納出兩條判準。一、無人值守時只有允許清單上的完整字面指令跑得動,沒有「預設安全的唯讀指令」這回事,連 readlink 與 ls 都要有自己的規則。二、路徑中段的萬用字元不匹配,規則與指令都必須是完整字面,所以帶版本號的快取路徑放不進允許清單。「先解路徑再用」因此不成立:解路徑的指令自己就過不了,而路徑能寫成字面就不必解。

助理技能新增路徑守則與 Step 0。守則寫明權限層比對的是指令還沒展開的字面字串,帶未展開變數或波浪號的路徑一律要核准,並附上七列實測佐證表。Step 0 從「自己跑 readlink 解路徑」改成「從叫用文字的『工具根目錄=』取字面絕對路徑」,排程那一輪一個解析指令都不跑;人在現場叫用才用 readlink 解一次,那一次有人可以按同意。取不到根目錄就停下回報,收尾狀態取 aborted,不猜也不退回帶變數的路徑。全篇 46 處腳本呼叫改成字面絕對路徑。

排程工具在安裝時把解好的字面根目錄寫進條目的提示文字,並印成 patrol_root=。條目與允許規則共用同一個值,兩邊各算各的就會差開,而差開的那一輪是被靜靜擋掉。允許規則的提示改成完整字面路徑,不寫變數、波浪號與萬用字元。JSC_HOME 解不出絕對路徑時回結束碼 6,不讓相對路徑寫進條目。自訂巡檢指令沒帶那一段只警告、不中止。

行為契約四列與說明文件兩處敘述一併跟上。
三份 manifest 的版本從 0.1.5 升到 0.1.6,由 sync-skill-manifest.sh 同步。

助理技能的路徑守則與排程條目的內容都改過,安裝端要靠版號才認得出該更新,不升版的話舊條目會一直留在機器上。

版號與主變更分開成一筆,是因為這三個檔案只帶版號、不帶行為,混進主變更會讓那一筆的 diff 夾雜非行為變更;分開之後回溯哪一版對應哪一次變更也看得清楚。
jiantw83 added 1 commit 2026-09-03 02:31:07 +00:00
人在現場那一條路解出根目錄之後,守門只看「非空、結束碼 0、是絕對路徑」三項,攔不住 JSC_HOME 沒設的情況。

實測:JSC_HOME 沒設時,readlink -f "$JSC_HOME/current" 印出 /current、結束碼 0。三項全部符合,直接通關,而 /current 並不存在。之後每一條由它組出來的字面路徑都指向不存在的地方,錯誤要到第一支腳本才浮出來,而且訊息看不出根因。

守門加上第四項:印出來的路徑必須是存在的目錄,用 [ -d ] 在同一個已核准的步驟裡查。檢查只掛在人在現場那一條,排程那一輪照舊一個指令都不跑——它的根目錄是安裝排程的人寫進條目的,錯了就由第一支腳本呼叫失敗來反映。

行為契約的關鍵步驟與完成條件兩列跟著改。
admin merged commit 56fe144c38 into develop 2026-09-03 02:52:01 +00:00
admin deleted branch fix/literal-absolute-paths-for-unattended-patrol 2026-09-03 02:52:01 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: plugins/assist#14