Skip to content

How do in-process guards coexist with the command-hook guards? #201

Closed

Description

@ToRvaLDz

Parent: 🗺 trailhead mod

Question

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.

Blocked by

Activity

  1. self-assigned this
    on Oct 10, 2026
  2. ToRvaLDz commented on Oct 10, 2026

    @ToRvaLDz
    OwnerAuthor

    Resolution. Per-guard replacement (option C), gated by a freshness-stamped env signal.

    1. 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).
    2. 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.
    3. 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.
    4. 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.
    5. Speed win independent of the mod: merge the five Bash guards into one node process (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 where TRAILHEAD_MOD_GUARDS is read.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions