fix(security): 人格檔案不能逸出注入區塊、room 台詞不能偽造成系統訊息(S6)

注入到上下文的東西夾在 persona-runtime / persona-context / persona-ops 三種區塊
中間,而夾進去的內容有**不可信來源**:persona-anime 從 Fandom 抓設定寫進
IDENTITY/AGENTS、`sync pull` 從另一台機器拉、`import` 吃外部 bundle、
guest 的 room 台詞是別的人格寫的。原本這些地方**沒有任何跳脫**:

- AGENTS.md 裡放一行結束標記 → opsBrief 的區塊提早關閉,後面的內容跑到區塊外,
  連外層的 runtime 區塊都能一起關掉。
- IDENTITY.md 的 `Vibe:` 欄位值同理,經 identityBrief 進 turnContext。
- room 台詞塞換行 → roomScript 是一行一句「emoji 名字(情緒):內容」,
  於是可以偽造成別人的台詞或系統訊息。

修法:

- 新增 `stripInjectionMarkers()`:把 `<persona-…` 的 `<` 換成全形。內容還讀得懂
  (人格自己寫的說明不會被吃掉),但它不再是一個標籤。
- 新增 `injectSafeLine()`:中和標記 + 換行壓成空白(比照短期記憶的作法)。
- 一個收口勝過十幾個防點:`turnContext()` 與 SessionStart 的 runtime 區塊都改成
  **組完之後對整個內文**做一次,再補上真正的標記;只有 turnContext/opsBrief
  這種自己已處理過、帶合法巢狀標記的整塊原樣保留。
- 讀出來就中和的:`opsBrief()` 的 AGENTS.md 全文(先截斷再中和,長度上限才算得準)、
  `identityFields()` 的欄位值(identityBrief/roomScript/roomDisplayName 全吃這一份)、
  `relationsBrief()` 的人名與備註。
- room:`roomPost()` 在**寫入端**就把 text/emotion/barge_in 壓成一行,
  `roomScript()` 與 `room read` 在**顯示端**再壓一次(舊逐字稿是原文寫進去的)。

測試:新增 14 項——AGENTS.md 與 IDENTITY 欄位的逸出、turnContext 與 SessionStart
的區塊只被關閉一次、短期記憶與關係圖人名走同一個收口、room 台詞的換行偽造與
標記逸出、舊逐字稿的顯示端防線、顯示名不夾帶標記。
反向驗證:把兩個中和函式改成 identity,這 14 項全數失敗。
368 → 383 項全過。
This commit is contained in:
2026-07-31 09:31:41 +00:00
parent 7549c0b69f
commit 186de10179
5 changed files with 189 additions and 20 deletions
+17
View File
@@ -500,6 +500,23 @@ node scripts/persona.mjs sleep --session <PERSONA_SESSION> --release # 收工
誤攔正常指令的風險。真的需要對抗性隔離,要靠作業系統層的手段(獨立使用者、容器、
檔案權限),不是靠 hook。
### 注入的區塊不會被人格檔案關掉
注入到上下文的東西夾在 `<persona-runtime>` / `<persona-context>` / `<persona-ops>` 中間,
而夾進去的內容有**不可信來源**`persona-anime` 從 Fandom 抓設定寫進 IDENTITYAGENTS、
`sync pull` 從另一台機器拉、`import` 吃外部 bundle、guest 的 room 台詞是別的人格寫的。
內容裡只要出現一行 `</persona-ops>`,區塊就提早關閉——後面的文字跑到區塊外,
讀起來變成「系統在說話」,連外層的 `</persona-runtime>` 都能一起關掉。
所以注入前一律經過 `stripInjectionMarkers()`:把 `<persona-…` 的 `<` 換成全形 ``。
內容還讀得懂(人格自己寫的說明不會被吃掉),但它不再是一個標籤。
`turnContext()` 與 SessionStart 的 `<persona-runtime>` 都在**組完之後對整個內文**做一次,
不是在十幾個 push 點各自防;AGENTS.md 全文與 IDENTITY 欄位值則在讀出來的當下就中和。
room 台詞另外比照短期記憶把**換行壓成空白**(`roomPost()` 寫入時、`room read``room script`
顯示時各一道):`roomScript()` 是一行一句 `emoji 名字(情緒):內容`,台詞裡塞換行就能
偽造成別人的台詞或系統訊息。
---
## Skills 目錄