fix(kvb-org-organize): no hook runs a binary planted on the inherited PATH

Hooks run outside the sandbox but inherit a PATH whose directories
sandboxed agents of the same uid can write (a virtualenv's bin activated
first by the shell profile), so a file planted there under the name of
docker, git or notify-send ran unsandboxed. hookpath.py keeps only the
PATH directories that, with every parent, belong to another uid and that
neither group nor others can write, as real paths; inside the sandbox's
user namespace root shows as the overflow uid and still counts as sealed.
watch.py, radio.py and council.py set that PATH before they start
anything, and the cleanup child runs with -I. Both reminder hooks pin
PATH=/usr/bin:/bin, and the install registers them with /usr/bin/bash and
moves older bare bash and python3 entries to the absolute paths.

🎯 Fix: 90 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 86
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-06 13:27:00 +00:00
1 parent 94542e7b92
commit 454670a77e
13 files changed
+193 -5

No files matched your search

+5 -2
View File
@@ -61,6 +61,7 @@ for (const [plugin, marketplace] of Object.entries(RETIRED_PLUGINS)) {
data.env = data.env || {};
if (!data.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW) data.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = "500000";
const PYTHON = "/usr/bin/python3 -I";
const ABSOLUTE = { python3: PYTHON, bash: "/usr/bin/bash" };
const COUNCIL_CONFIG_DENY = "Edit(~/.claude/agents-council/**)";
data.permissions = data.permissions || {};
data.permissions.deny = [...new Set([...(data.permissions.deny || []), COUNCIL_CONFIG_DENY])];
@@ -91,7 +92,9 @@ for (const entries of Object.values(data.hooks)) {
for (const [from, to] of Object.entries(RENAMED)) {
hook.command = hook.command.replace(`/skills/${from}`, `/skills/${to}`);
}
if (hook.command.includes("/skills/kvb-")) hook.command = hook.command.replace(/(^|&& )python3 /, `$1${PYTHON} `);
if (hook.command.includes("/skills/kvb-")) {
hook.command = hook.command.replace(/(^|&& )(python3|bash) /, (_, lead, binary) => `${lead}${ABSOLUTE[binary]} `);
}
}
}
}
@@ -101,7 +104,7 @@ for (const skill of REMINDERS) {
if (alreadyRegistered.includes(`${skill}/hooks/reminder.sh`)) continue;
const reminder = path.join(path.dirname(settingsPath), "skills", skill, "hooks", "reminder.sh");
data.hooks.UserPromptSubmit.push({
hooks: [{ type: "command", command: `bash ${JSON.stringify(reminder)}` }],
hooks: [{ type: "command", command: `${ABSOLUTE.bash} ${JSON.stringify(reminder)}` }],
});
}