Skip to content
C.W.K.
Stream
Lesson 03 of 05 · published

Put the Guard in Front of the Tool Call

~13 min · guardrails, hooks, implementation, design

Level 0Wet Clay
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

The Only Place Left

If the forbidden action leaves no trace, the check cannot run after it. It cannot run during it either, because there is no during — a shell command either happens or does not. That leaves exactly one position: between the decision to run a command and the command running. A pre-execution hook is not a clever choice here; it is the only remaining seat.

Which means the guard inherits a hard constraint from where it sits: it runs on every command, including the hundreds that have nothing to do with the rule. So it must be fast, it must never make a network call, and it must never fail closed on its own bugs, because a guard that adds latency or flakiness to every action in the system will be switched off within a week and the rule goes back to being prose.

What the Guard Actually Knows

It reads a small local marker file that the session wrote when it claimed the work: which target, which pipeline, which stages restrict inputs, and which stages have been logged so far. That is enough to answer "is the seal armed right now" without asking anything over the wire. The marker is a cache of a fact the engine already owns, and it is deleted when the claim ends — by landing, by abandoning, or by an explicit release.

That last part sounds like housekeeping and is not. A guard that keeps firing after the work is finished is the kind that gets disabled. One early run ended with its claim released and its marker still live, and the session spent the next stretch fighting refusals for commands it was fully entitled to run. Over-sealing fails safe, but only if it also ends.

The Rule That Surprises Everyone: Judge Each Segment

The guard parses a command into pipe segments and judges each one separately, and this is where every intuition about it breaks. A directory change earlier in the chain still counts. A pipe into a pager does not narrow anything. A comment that scopes the intent is invisible, because comments are not arguments.

It also refuses things that feel unfair the first time. A run once tried to write an honest note saying it had modified none of the source-language files — and got blocked, because the note contained the forbidden pattern. The right answer was to write the sentence in words rather than in filenames, and the two seconds that cost is the price of a guard that has no model of intent.

Design the guard to fail in the direction that announces itself. Refusing too much is loud, annoying, and fixed within the hour. Refusing too little is silent and gets discovered by an audit weeks later. When you cannot make a guard exact — and you cannot — choose which error you would rather have, then make the block message say precisely how to proceed legitimately.

Code

The guard, reduced to its decision·python
import shlex

FORBIDDEN = ".source.json"          # the pattern that must not print
PRINTERS = {"cat", "head", "tail", "less", "grep", "rg", "ag",
            "jq", "python", "python3", "awk", "sed"}

# NOT here, and this is the honest gap: `ls`, `find`, `tree`.
# The previous lesson called a bare directory listing a breach,
# and this guard does not close it. Naming the gap in the code
# is worth more than a set that claims coverage nobody has.


def breaches(command: str, target_dir: str) -> bool:
    """True if any PIPE SEGMENT could print forbidden content.

    Each segment is judged alone. A `cd` in segment 1 does not make
    segment 2 safe, and `| head` narrows nothing - it is a second
    command that inherits the first one's output.
    """
    cwd = ""                               # a `cd` in an earlier
    for segment in split_pipeline(command):  # segment still counts
        try:
            argv = shlex.split(segment, comments=True)   # comments are
        except ValueError:                               # NOT scope
            return True                                  # unparseable
        if not argv:                                     # -> refuse
            continue
        tool = argv[0].rsplit("/", 1)[-1]
        if tool == "cd" and len(argv) > 1:
            cwd = argv[1]                    # carried into later segments
            continue
        if tool not in PRINTERS:
            continue
        args = argv[1:]
        if any(FORBIDDEN in a for a in args):
            return True
        # An unscoped sweep at the target directory is the real shape
        # every incident took. A narrowing glob is the only exemption.
        touches_target = (target_dir in cwd
                          or any(target_dir in a for a in args))
        narrowed = any(a.startswith("--glob") or a.endswith(".target.json")
                       for a in args)
        if touches_target and not narrowed:
            return True
    return False


# The block message matters as much as the block. It must name the
# legitimate way through, or the next person disables the hook:
#
#   "blocked: unscoped read at the sealed directory.
#    Add --glob '*.target.json', or read the engine log instead."

External links

Exercise

Write a pre-execution guard for one traceless rule in your own environment, and give it three test cases before you give it any logic: one command that must be blocked, one that must be allowed, and one borderline case you are genuinely unsure about. Then write the block message first and the detection second. If the block message cannot name a legitimate alternative, the guard is not ready to ship.
Hint
The borderline case is the design document. Whichever way you resolve it, write the reason down next to the test — six months later the guard will be tightened by somebody who does not know why the exemption exists, and the note is what stops them from removing it.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.