Skip to content

guard: what it cannot see and what the shell can still remove; alarm: a transient failure that keeps happening - #27

Merged
itswl merged 3 commits into
mainfrom
guard-and-recurring-failures
Oct 27, 2026
Merged

itswl merged 3 commits into
mainfrom
guard-and-recurring-failures

Conversation

@itswl

@itswl itswl commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Three lessons from a second deployment running copies of these files, by Claude (claude-opus-5-5) at the repository owner's request. Each was measured against this repository's own gate or failure path before it was changed, and each fix has a test that fails without it.

1. A script the guard never sees is refused, under every posture. A pipe into a shell (curl … | sh, | sudo bash, |bash) and a fetched script handed to one (bash -c "$(curl …)", sh <(curl …), source <(…), eval "$(…)") passed in both postures: the guard read sh, and every verb in the script stayed out of reach of every rule. The work stack's egress allowlist includes raw.githubusercontent.com, where anyone can host a script. Refused now before the mode branch, with the way through said: fetch it, read it, run what it holds. Pipes into sha256sum, jq, python3 -m json.tool or shellcheck are untouched.

2. Removing or relinking a protected path from the shell is a write. rm results/x.json, rm -f CLAUDE.md, find results -delete and ln -sf /tmp/x .claude/skills/x passed while cp and mv into the same paths were refused: the case files the next run reads could be deleted, and a skill planted whose content lives wherever the run can write. rm, rmdir, unlink, shred, ln, touch, and find with -delete or an -exec that removes or writes now name their targets the way cp does, in both postures.

3. A transient failure that keeps happening pages. Rate limits and 5xx are retried and, failing again, filed as one investigation's bad luck; none is a node-wide shape, so none paged. The other deployment lost three days of rounds to a rate limit that way. The third consecutive failure of one transient kind now pages once per kind per six hours, and the next run that lands says so. Node-wide failures are not counted twice; one investigation's own failure is not counted.

Also: aws logs start-query (and CloudTrail Lake's) is a read in the readonly posture, named exactly so Athena's start-query-execution, which writes to a bucket, stays refused.

Not ported: an emergency-stop file, and the other deployment's deny list for its writing agent — this repository's writing posture allows writes by design and its readonly guard already refuses those verbs.

Decision note: .agents/notes/implemented/2026-10-27-what-the-guard-cannot-see-and-a-failure-that-keeps-happening.md.

Gate: STACK GATE GREEN locally; CI read back before merging.

itswl added 3 commits October 27, 2026 17:32
…not removed or relinked from the shell, and Logs Insights is a read

Measured against this gate with the commands a second deployment running copies of these files now denies its unattended agent. A pipe into a shell (curl ... | sh, | sudo bash, |bash) and a fetched script handed to one (bash -c "$(curl ...)", sh <(curl ...), source/. <(...), eval "$(...)") passed in both postures: the guard read sh, and every verb in the script stayed out of reach. They are refused under every posture now, before the mode branch, with the way through said: fetch it, read it, run what it holds.

rm results/x.json, rm -f CLAUDE.md, find results -delete and ln -sf /tmp/x .claude/skills/x passed while cp and mv into the same paths were refused: the case files the next run reads could be deleted, and a skill planted whose content lives wherever the run can write. rm, rmdir, unlink, shred, ln, touch and find with -delete or an -exec that removes or writes now name their targets like cp does. aws logs start-query (and CloudTrail Lake's) is a read in the readonly posture, named exactly so Athena's start-query-execution stays refused. Each rule's test fails with the rule removed.
…per six hours, and a landing says so

Rate limits and 5xx are retried and, when they fail again, filed as one investigation's bad luck; none of them is a node-wide shape, so none paged. A second deployment running copies of these files lost three days of rounds to a rate limit exactly that way. The third consecutive failure of one transient kind now pages through the alarm channel, at most once per kind per six hours, and the next run the engine finishes says the node is landing again. Kinds are named (a 429 and 'rate limit exceeded' are one kind), node-wide failures are not counted twice, and one investigation's own failure is not counted at all. The streak lives in memory. The alarm setting's comment, and so the generated reference, now lists everything that pages.
…can no longer do; the decision note

The containment row now lists removing and linking among the shell's refused writes, the unseen-code rule under every posture, and a script saved to a file and then run among what it does not stop. The README's guard item says the same. The decision note records what was measured and the consequences: an installer's curl | sh is refused even on a writing node, ln -s to a protected file over-blocks, the streak restarts with the process, and nothing persisted changes shape.
@itswl
itswl merged commit 626b93d into main Oct 27, 2026
8 checks passed
@itswl
itswl deleted the guard-and-recurring-failures branch October 27, 2026 09:36
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