Skip to content

docs: rebuild README against the CLI variant template - #4

Merged
necco-c merged 1 commit into
mainfrom
readme/cli-template-rebuild
Aug 20, 2026
Merged

docs: rebuild README against the CLI variant template#4
necco-c merged 1 commit into
mainfrom
readme/cli-template-rebuild

Conversation

@necco-c

@necco-c necco-c commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

agent-lock README — reviewer notes

Rebuilt against the CLI variant template (the current one, same as hotspot). The live
README was written against the retired single template, which is why it was missing
Questions this tool answers, Contents, Navigation, Reading it without a TTY,
Testing across kernels and the demo section, and why it still had Common use cases,
Honest caveats and Community questions.

✅ Reviewer action items

  • Apply the About description (below). You have ADMIN on the repo, so gh repo edit will work.
  • Apply the topic tags (below). Current tags are mostly right; three adds, one removal.
  • Resolve flagged claims:
    • The "next thing on the list" roadmap promise is removed; the README now describes the workaround and the one-line wrapper fix instead of promising a date.
    • Validated-on claim (Linux 6.12 / arm64, Debian 13) carried over from the previous README. Still true?
    • The --audit + OMP= + -- invocations in the flag examples are assembled from reading the wrapper, not run. Worth one pass on a real box.
  • Land the one-line --comm derivation in scripts/agent-lock (see below). It removes a
    looks-like-success failure mode and lets three README passages get shorter.
  • scripts/agent-lock and src/main.jsx still call the tool agent-jail in their header
    comments, and main.jsx still describes the retired Landlock C launcher architecture.
    Not README copy, but it will mislead the next reader of the source.
  • package.json description still says "with Landlock". Stale since the LSM rewrite.

About description

Confine an AI coding agent to one directory with a BPF-LSM program, and watch every escape it tries.

107 characters. Leads with the category, agent-agnostic, no em-dash. (The current one is fine too;
this version drops the vendor list, which the repo description doesn't have room to keep current.)

Topic tags

ebpf
linux
bpf-lsm
lsm
security
sandbox
agent-security
ai-agents
llm-tools
yeet
showcase
enforcement

Changes from what's live: add enforcement (the category badge now claims it, and it is the axis
that separates this repo from the observe-only corpus). Drop oh-my-pi — it is vendor-specific and
works against the agent-agnostic framing you asked for. Keep the rest.

Flagged claims

claim grounding note
"the next thing on the list" Dropped. No issue or TODO backed the promise. Replaced with the actual workaround plus the one-line fix.
yeet run . -- --dir ... --comm claude works 1 Verified against main.jsx:33 (parses comm from yeet.args) and fileaccess.js:41 (configure(dir, comm, audit)). Read, not executed.
"Validated on Linux 6.12 / arm64 (Debian 13)" 1 Carried verbatim from the live README. Verify it still holds rather than letting it age.
"no loops over unbounded data and no map allocations on the hot path" (FAQ, overhead) 2 True by reading: every loop is #pragma unroll over a fixed width, and the only map write on the hot path is enrollment, which happens once per process. Stated qualitatively with no numbers, deliberately.
Flag-combination examples 2 Assembled from scripts/agent-lock argument parsing, not executed. sudo -E is included on the OMP= line because plain sudo drops the env var.
"1 / 12" style sample output, counts and paths 2 Hand-written per the template. Numbers are internally consistent (12 escape rows, 37 blocked, 11 sensitive) and paths match what demo-agent.sh actually reaches for.

Newly discovered limits (stated in the README, not previously documented)

Two of these are new. Per the rules skill they ship honest by default; flagging so you see what changed.

  1. The hardlink gap. A hardlink inside the jail pointing at a file outside it resolves to an
    in-bounds path, so the read is allowed. This is the documented limit of path-based enforcement.
    It was in commit 1a8e831's message and in adversary.sh comments but nowhere in the README.
    It is now in the primer, in What it can't see, in the self-test section, and in the
    "why an LSM hook" trade-off, each time with the agent-jail cross-link.
  2. Two agents at once is not supported. The jailed directory is a single .data knob and the
    comm match is a single name, so a second omp would be enrolled against the first jail's
    boundary. New FAQ entry; not previously stated anywhere.
  3. bpf in the active LSM list is a boot-time requirement. The old README mentioned
    CONFIG_BPF_LSM=y and the LSM list in passing; it is now the [!IMPORTANT] callout, because a
    kernel compiled with the option still cannot run this until lsm= includes bpf and the machine
    reboots. This is the single most likely "why won't it attach" report.
  4. No probe self-test entry point. src/probes/probe.js has no import.meta.main block, so
    unlike most yeet scripts there is no yeet run src/probes/probe.js one-shot. Stated plainly in
    Reading it without a TTY with a pointer to where it would go, per the template's rule that the
    section is more useful when the answer is bad.

The omp name requirement is a wrapper limit, not a tool limit

Worth being precise, because an early draft of these notes blurred it and it is the kind of thing
that turns into "agent-lock only works with oh-my-pi" if it is stated loosely.

Three separate layers:

layer agent-agnostic?
jail.bpf.c Yes. target_comm is a patchable .data knob. No compiled-in name.
src/main.jsx + fileaccess.js Yes. main.jsx:33 reads comm from yeet.args; configure(dir, comm, audit) patches whatever it is given. yeet run . -- --comm claude works today (verified by reading both call sites).
scripts/agent-lock No. Lines 63 and 65 pass a literal --comm omp, and OMP= only selects which binary to launch, not what name it is matched under.

So the only thing pinning omp is one hardcoded flag in the shell wrapper. The README now says
exactly that, adds a ### Launching a differently-named agent subsection with both routes (a
symlink plus sudo -E, or driving the script directly with --comm), and keeps the failure-mode
warning: a name that was never enrolled runs unconfined, and an empty dashboard looks identical
either way.

The fix is roughly one line. Derive the comm from the launched binary in scripts/agent-lock:

COMM=$(basename "$OMP")
... --comm "$COMM"

That would let the README drop the workaround entirely. Two things to decide while you are in there:
matching by name also confines any unrelated process sharing that name (matching the launched pid
instead would be tighter), and a bare --comm on a common name like node would be a wide net.

Also worth knowing

  • The ⊟ JAILED / ⚠ AUDIT · UNCONFINED masthead badges are ALL CAPS, which violates the
    no-ALL-CAPS rule (feedback_no_all_caps). That is UI code (src/components/header.jsx), not
    README copy, so I did not touch it; the README's sample output shows them in sentence case as
    jailed / audit · unconfined, which is where they should end up. Small header.jsx fix.
  • Discord link. The live README uses discord.gg/dYZu9PjKB; the corpus footer and every other
    repo use discord.gg/JxVseaAVAU. This draft uses JxVseaAVAU. Confirm that's the right invite.
  • The hero GIF is reused as-is (assets/agent-lock.gif, 1.7 MB) with a rewritten alt text. If
    the masthead caps get fixed, scripts/demo-record.sh regenerates it.
  • .github/workflows/kernel-matrix.yml is present, so no CI finding. There is no
    veristat-matrix make target though, despite the template describing one; the README documents
    only make veristat, which does exist.

Machine-readable handoff

repo: agent-lock
variant: cli
interaction_mode: full-tui
readme: ~/code/yeet-scripts-readmes/agent-lock/README.md
personas: [platform, devops]
category: eBPF filesystem jail for Linux
defers_to:
  agent-jail: "inode-based enforcement via Landlock, closes the hardlink gap"
  containers/gVisor/microVMs: "network, PID and device isolation; untrusted code"
  AppArmor/SELinux: "persistent system-wide policy across reboots"
  seccomp: "restricting which syscalls exist rather than which paths they touch"
flagged_claims: 4
uncovered_topics:
  - "how BPF LSM enforcement differs from Landlock, seccomp and AppArmor as mechanisms"
  - "why path-based confinement cannot see through a hardlink, and what inode-based checks cost"
  - "why an LSM program's return value must land in [-4095, 0] and how clang breaks it"
  - "the enrollment race: confining a process tree you did not fork yourself"
  - "what a coding agent actually reads outside its project directory, measured"

The previous README was written against the retired single template, so it
was missing the sections the current CLI variant requires and still carried
three retired headings.

Added: Questions this tool answers (8, weighted to platform/DevOps), a
Contents block, Navigation, Reading it without a TTY, Testing across
kernels, the demo section, a sample-output block, and a column-meaning
table.

Retired: Common use cases -> Questions this tool answers, Honest caveats ->
What it can't see, Community questions -> FAQ.

Reframed agent-agnostic throughout. The jail confines a process tree matched
by name, and the name is a runtime knob, so nothing about the mechanism is
tied to one vendor's agent. Claude Code, Codex, oh-my-pi and DeepSeek appear
only as examples in the compatibility question.

Documents three limits that were true but unstated:

- A hardlink planted inside the jail resolves to an in-bounds path and is
  allowed. This is the boundary of path-based enforcement; agent-jail uses
  Landlock and enforces on the inode instead. Cross-linked in four places.
- Confinement matches the process name and scripts/agent-lock hardcodes
  --comm omp, so an agent launched under its own name runs unconfined while
  the dashboard looks merely idle. Both workarounds are documented.
- bpf must be in the active LSM list, which is set at boot via lsm=, so a
  kernel built with CONFIG_BPF_LSM=y can still fail to attach.

Also drops a roadmap promise no issue backed, and documents that
src/probes/probe.js has no import.meta.main self-test, so --headless is the
only non-TTY path.
@necco-c
necco-c merged commit db4736f into main Aug 20, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant