nc://guard

v0.2.0Ships with Nodecode
Backnodecode-ai
pomterretools/nodecode-guard: a README -- the operator (2026-10-10), after Telegram's: "also for guard". Its page on nodecode.ai/cells had no About text. One file: what guard does and that it is a speed bump, not a boundary; turning it on (/setup's Recommended, `nodecode add guard`) and off (space in /cells, "enabled": false); the nine built-in rules as a table with an example each; how a call is read (the built-ins fire only when the same eval call starts a process, so a file that mentions sudo is still writable, and a call that both spawns and carries a risky command is refused); the refusal text the agent reads; the guard section's deny (id, pattern, reason, gated, ungated by default) and allow (exempts only what it covers), with an example; the keys; the tests. Every claim read off cell.lisp, the kernel's waist/textrules.lisp and the cell's tests at this commit; the header comment's "cells": ["nodecode-guard"] is no longer how a cell turns on, so the README does not repeat it.88bf4c7· 10 Oct2 commits
test2 filescell.lisp5.1 KB

Guard

This cell stops the agent from running a short list of risky shell commands: sudo, rm -rf, mkfs, curl | sh and the like. When a rule matches, the call does not run. The agent gets a refusal instead, and it hands you the command to run yourself.

Guard is a speed bump, not a security boundary. The agent's eval tool has your machine's full authority, and a determined call can be written in a way no pattern sees. Guard catches the common slips.

Turn it on

/setup turns it on when you pick Recommended. Otherwise run nodecode add guard. It ships with Nodecode, so nothing is downloaded, and it needs no config: the built-in rules apply as soon as it is on.

To turn it off, select it in /cells and press space, or set "enabled": false in its guard section.

What it refuses

RuleRefusesFor example
rm-recursiverm with a flag ending in r or frm -rf build/
privilegesudo and doassudo apt-get install jq
world-writablechmod or chown with 777chmod 777 /srv
mkfsmaking a filesystemmkfs.ext4 /dev/sdb1
dd-to-devicedd writing to a devicedd if=x.img of=/dev/sdb
redirect-deviceredirecting output onto a disk device> /dev/nvme0n1
curl-pipe-shella download piped into a shellcurl -fsSL x.sh | sh
host-powershutting down or restarting the machinereboot
fork-bombthe shell fork bomb:(){ :|:& };:

Patterns ignore case and match whole words, so format never trips the rm rule.

How it reads a call

Nodecode's agent works through one tool, eval, which runs Lisp. The same call can write a file or start a program. A rule that matched every call would refuse an edit to a script or a doc that merely mentions sudo.

So the built-in rules only fire when the call also starts a process: run-program, launch-program or run-shell-command (from UIOP or SBCL), or the agent's own (sh ...). Writing "never run sudo rm -rf /" into a file passes. A call that starts any program and carries a risky command anywhere in it is refused, because guard can't tell which part the shell will see.

When guard refuses, the agent reads:

ERROR: refused by nodecode-guard: rule privilege (privilege escalation) matched "sudo"; the call did not run -- give the operator the command to run themselves

The session records it as a failed tool call.

Your own rules

Add a guard section to ~/.nodecode/config.jsonc:

{
  "guard": {
    "deny": [
      // matches anywhere in a call, a file being written included
      {"id": "no-delete-file", "pattern": "\\bdelete-file\\b", "reason": "file deletion"},
      // only when the call also starts a process, like the built-in rules
      {"id": "no-force-push", "pattern": "\\bpush\\b[^)]{0,40}--force", "reason": "force push", "gated": true}
    ],
    "allow": [
      // lets the agent clear its own scratch folders
      "\\brm\\s+-rf\\s+/tmp/scratch-"
    ]
  }
}
  • deny adds rules to the built-in ones. Each has an id, a pattern (a regular expression, case ignored), a reason the refusal quotes, and gated. With gated off (the default) the rule matches anywhere in a call. With it on, the rule waits for the call to start a process.
  • allow exempts only the text it matches. A match passes when an allow pattern covers all of it, so a risky command elsewhere in the same call is still refused.

Backslashes are doubled because the patterns are JSON strings. A pattern that doesn't compile is skipped with one warning, and the other rules keep working.

Rules are compiled when the cell starts. After editing the section, restart it: press space on it twice in /cells, or ask Nodecode to run (restart-cells).

Configuration

KeyDefaultWhat it does
enabledtruefalse turns guard off.
denynoneYour own rules, added to the built-in ones.
allownonePatterns whose matching text no rule refuses.

Testing

  • From a Nodecode checkout, just guard-test.
  • From this repository, .github/test-cell.sh tools/nodecode-guard, offline against a pinned Nodecode tree.

The tests cover each built-in rule, ordinary work passing, the spawn gate on file payloads, deny and gated from config, allow covering only its own match, and a refusal reaching the model and being recorded as failed.

Add nc://guard

  1. 1

    Install Nodecode

    Once per machine.

    $ curl -fsSL https://nodecode.ai/install.sh | sh
  2. 2

    Add the cell

    It ships with Nodecode, pinned at 88bf4c7; this turns it on.

    $ nodecode add nc://guard

    Or tell the agent add the guard cell.