You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
How do in-process guards coexist with the command-hook guards? #201
When the mod is loaded, how do the in-process guards coexist with the command-hook guards (commit, secret, install, search, body, secret-read, mockup-link, injection scanner)?
A. Mod replaces: the command hooks detect the mod (env var, marker, or a check the mod answers) and exit early, so each check runs once.
B. Both run (defence in depth): simplest and safe, but keeps today's per-call node spawns and so loses the main speed win.
C. Mod replaces per guard, only for the guards ported and tested, the rest stay on command hooks.
Also settle the failure posture: a mod guard that throws is skipped unless its .catch refuses, so each ported guard needs a fail-closed .catch; and whether the search-guard may rewrite a cd X && read command instead of denying it.
Resolution. Per-guard replacement (option C), gated by a freshness-stamped env signal.
Coexistence: per guard. A guard moves to the mod only once ported and tested; every other guard stays on its command hook. Codex and builds without mods stay entirely on the command hooks (guard of record per Can the core plugin ship a mod alongside its command hooks? #199).
Signal: stamped env, refreshed every call. The mod's tool.call sets TRAILHEAD_MOD_GUARDS=<guard,...>@<epoch-ms> via $.env.set before next(e). A command hook skips a guard only when it is listed and the stamp is fresh (a few seconds, tuned at porting). Rationale: the API has no unload event to clear an env var, so a static flag would leave a guard running nowhere after a mid-session unload; with the stamp, a missing or stalled mod falls back to the command hooks on its own. Short-circuiting classic.PreToolUse was ruled out: it would also skip other plugins' and the user's hooks.
Failure posture: fail-closed. The API skips a hook that throws, so every ported guard carries .catch(($, e, next) => next.called ? next(e) : { deny }). The injection scanner (annotates, does not block) reports a warning on error instead of passing silently.
Search-guard rewrites only safe shapes.cd <abs> && cat|head|tail|wc|grep|ls <simple relative paths> (no pipes, substitutions, globs, heredocs) is rewritten path-explicit via next({...e, command}); everything else is denied as today; the result tells the model which command actually ran.
Activity
ToRvaLDz commentedon Oct 10, 2026
Resolution. Per-guard replacement (option C), gated by a freshness-stamped env signal.
tool.callsetsTRAILHEAD_MOD_GUARDS=<guard,...>@<epoch-ms>via$.env.setbeforenext(e). A command hook skips a guard only when it is listed and the stamp is fresh (a few seconds, tuned at porting). Rationale: the API has no unload event to clear an env var, so a static flag would leave a guard running nowhere after a mid-session unload; with the stamp, a missing or stalled mod falls back to the command hooks on its own. Short-circuitingclassic.PreToolUsewas ruled out: it would also skip other plugins' and the user's hooks..catch(($, e, next) => next.called ? next(e) : { deny }). The injection scanner (annotates, does not block) reports a warning on error instead of passing silently.cd <abs> && cat|head|tail|wc|grep|ls <simple relative paths>(no pipes, substitutions, globs, heredocs) is rewritten path-explicit vianext({...e, command}); everything else is denied as today; the result tells the model which command actually ran.nodeprocess (7 -> 3 spawns per Bash call, every host). Opened as Merge the Bash guards into one hook process (Merge the Bash guards into one hook process #207); the guard porting (fog 11) builds on it, since the merged hook is whereTRAILHEAD_MOD_GUARDSis read.