問題:`requireOwner()` 看到 sleeper 旗標就直接 return,把驗證整個外包給
PreToolUse hook;而 hook 認得出這支 CLI 靠的是檔名正則。把 scripts/ 複製出去、
CLI 改個名字,hook 全程回 pass,任何程序都能用那個旗標對**任意人格**
remember/recall/emotion --apply——等於完整讀寫權。
修法:CLI 自己驗,證據取自 hook 唯一寫得下、程序偽造不了的東西——
session 檔(`.runtime/sessions/<id>.json`)裡的 sleeper pin。pin 由 hook 依
`event.agent_type` 寫入,沒經過 hook 的程序拿不到。
- `pinAgent()` 多記角色(`{ persona, role, pinned_at }`),`pinOf()` 相容舊的純字串格式;
舊格式沒有角色,一律不算 sleeper 授權,hook 下次 first-touch 時會補上。
- 新增 `sleeperPins(sessionId, slug)`:本 session 中 pin 在該人格上的 sleeper。
- `requireSleeperPin()` 進 `sleeperAccess()`(requireOwner/requireMember 都會經過):
沒有對應的 sleeper pin 就 die,連 sleeper 租約都不會留下。
有帶 `--agent-id` 時額外要求它就是那個被 pin 的 agent。
- 連帶更新讀 pins 的三處:resolveScope、`leave` 的 guest pin 清理、SubagentStop hook。
persona-sleeper sub agent 的正常流程不受影響:hook 的 first-touch pinning 在它
第一個指令就寫好 pin,之後每個收尾指令都驗得過。
測試:新增 10 項(實際把 scripts/ 複製成別的檔名重現攻擊)——改名後 hook 確實
不表態、無 pin 時 remember/recall/emotion 都被 CLI 擋下且不留租約、pin 在別的
人格上不能跨過去、guest pin 與舊格式 pin 都不算授權、真有 pin 時照常放行。
345 → 355 項全過。
33 lines
1.2 KiB
JavaScript
33 lines
1.2 KiB
JavaScript
#!/usr/bin/env node
|
||
// SubagentStop:guest sub agent 結束時解除它的 pin(下次啟動要重新 first-touch),並續租。
|
||
|
||
import { readEvent, respond } from "./_hook.mjs";
|
||
import * as pl from "../scripts/persona-lib.mjs";
|
||
|
||
const event = readEvent();
|
||
if (!event) process.exit(0);
|
||
|
||
const sessionId = event.session_id || "unknown";
|
||
const agentId = event.agent_id;
|
||
const agentType = String(event.agent_type || "");
|
||
const isGuest = agentType.includes("persona-guest");
|
||
const isSleeper = agentType.includes("persona-sleeper");
|
||
if (!agentId || (!isGuest && !isSleeper)) process.exit(0);
|
||
|
||
const data = pl.loadSession(sessionId);
|
||
const pins = data.pins || {};
|
||
const slug = pl.pinOf(data, agentId)?.persona;
|
||
if (slug !== undefined) {
|
||
delete pins[agentId];
|
||
data.pins = pins;
|
||
pl.saveSession(sessionId, data);
|
||
if (isSleeper) {
|
||
// 睡眠 sub agent 收工:把短期寫入權還掉(就算它中途掛掉,租約 5 分鐘也會自己過期)
|
||
if (pl.personaExists(slug)) pl.dropSleepLease(slug, sessionId, agentId);
|
||
} else {
|
||
const info = (data.guests || {})[slug];
|
||
if (info?.room) pl.addGuestLease(slug, sessionId, info.room, data.host || "");
|
||
}
|
||
}
|
||
respond({ suppressOutput: true });
|