| 2026-07-27 |

permission allowlist: 13 read-only entries; ssh/scp/virsh and creds-matrix DELIBERATELY excluded
...
Transcript scan of the 44 most-recent session JSONLs across both project dirs, counting Bash
command+subcommand pairs and splitting each candidate into LOCAL vs nested-inside-ssh (a
command that only ever runs inside `ssh host '...'` gains nothing from its own rule -- the
outer ssh is what the permission layer matches).
ADDED to permissions.allow only, 31 -> 44 entries. ask/deny/env/hooks untouched, nothing
removed or reordered, JSON re-validated and de-duplicated:
bash scripts/creds-audit.sh * ; bash scripts/dc-dc-ceph-disk-budget.sh * ;
bash scripts/site-baseleg.sh check* ; tofu validate* (+ -chdir variant) ;
tofu state list* (+ -chdir variant) ; tofu state show* ; tofu providers schema* ;
tofu version* ; ip route* ; systemctl is-active *
The substantive half of this change is what was NOT added:
- ssh (693 calls), scp (47), virsh (234) -- the three biggest prompt sources, left alone.
They are this cloud's MUTATION TRANSPORT; CLAUDE.md hard rule 3 makes the ask rules the
enforcement mechanism for per-mutation approval and says not to work around them, and the
`Bash(ssh *)` disposition is an OPEN operator item queued at the 2026-07-23 close.
Allowlisting any ssh shape would bypass the gate and pre-empt an unresolved decision.
THIS COMMIT DOES NOT DISCHARGE THAT QUEUED PRUNE -- it is still open.
- python3 scripts/creds-matrix.py (76 local runs, the largest genuine miss) -- skipped
because `Bash(*creds-matrix.py *--privileged*)` is an ask rule added 2026-07-26 right after
the auto-mode classifier incident, and every prefix pattern wide enough to cover the
observed `... 2>&1 | tail` forms would ALSO match --privileged. Allow-vs-ask precedence was
not verified, and a rule that might silently downgrade an operator-installed gate is the
class of change hard rule 3 forbids. Needs the precedence answer, then exact forms.
- tofu plan (54) -- read-only in effect, but CURRENT-STATE section 10 says in terms "Do NOT
re-run `tofu plan` casually against live state". The prompt is deliberate friction.
- python3 -c/heredocs (324) and shells: arbitrary code execution. curl (114): mutation vector
via -X/-d. tofu fmt (26): rewrites files. tofu init (10): mutates .terraform/. nft list
(10): normally needs sudo, which correctly stays ask.
Flagged not fixed: six existing allow entries are redundant with Claude Code's built-in
auto-allow (git status/log, grep, ls, jq, cat scripts/*), and `Bash(git pull*)` sits in allow
though it is a fetch-and-merge. Removing entries was out of scope.
Folded into today's session changelog as item 6 (GA-R2/D1, ONE changelog per session) with a
per-item revert. repo-lint 0-fail; no executable surface changed, so the gauntlet result from
this session's earlier commit (ALL GREEN, 81) still stands.
Revert: drop the 13 appended lines from permissions.allow -- a contiguous deletion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
| 2026-07-26 |
permissions: close the ssh 'virsh quoting gap (twin of the sudo gap)
...
Bash(ssh * virsh *) had the same defect as the sudo rule: it needs a literal
space before `virsh`, so a quoted remote command ssh <host> 'virsh ...'
matched no rule and fell through to the classifier. Added the single- and
double-quoted variants.
Verified against real command shapes: quoted, double-quoted and bare forms
all resolve to an ask rule; ssh <host> 'sudo virsh ...' resolves via the sudo
rule; an unrelated ssh <host> 'ls -la' still matches nothing, so the widening
did not become a catch-all. All rules are `ask`, never `allow`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|

D-137 tier 2: privileged sweep completed; permission gap fixed; 6 inferred filenames corrected
...
The block was a PATTERN GAP, not a policy conflict. Bash(ssh * sudo *) was
already in the project ask list, but it needs a literal space before `sudo`
and the command was ssh <host> 'sudo ...' -- the quote meant no rule matched,
so it fell through to the classifier. Added the quoted variants plus a
targeted rule for the privileged invocation; all `ask`, never `allow`.
FLAGGED NOT FIXED (hard rule 1): Bash(ssh * virsh *) has the identical gap.
MEASURED (docs/audit/d137-tier2-privileged-20260726.txt): the region secrets
dir holds exactly admin.apikey/admin.pass/db.pass/lxd-trust.pass at 0600 --
precisely the SEC-020(i) carve-out list; netbox holds admin.pass/api.token/
secret_key at 0600; the SEC-022 shadow stores are confirmed and now audited.
ZERO undeclared files at any declared location: SEC-020 and SEC-022 are
accounted for.
OWNED: the first matrix draft carried SIX filenames I authored plausibly
instead of measuring -- a hard-rule-2 miss. The checker's undeclared-detection
caught every one on first contact with real hosts. Corrected to measured
values, real locations declared, and five colocated non-secret artifacts
declared so the check stays meaningful. Matrix 72 -> 77 rows.
9 findings remain, all true: SEC-021 (3 declaration + 2 on-disk), the ruling-5
conflation, and 3 S5 asymmetries -- dc0's snap power key is the generic
id_ed25519 while dc1's is the dedicated id_dc1_power, the SEC-012/SEC-016
per-DC-isolation divergence made visible.
Harness 35/35, gauntlet ALL GREEN (80), repo-lint 0-fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvCyrwvYTTcDYnRErfMsNf
|
| 2026-07-25 |
settings: allowlist read-only whole-host budget calculator
...
From the /fewer-permission-prompts pass: adds Bash(python3
scripts/dc-dc-whole-host-budget.py*) -- a read-only capacity calculator, sibling of
the already-allowed provider-bundle-check.py. Edit was operator-approved earlier this
session; committing now for a clean tree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
| 2026-07-24 |

caveman: disable on live host + SEC-017 + fail-closed compress guardrails
...
Council-reviewed follow-through on the caveman plugin (operator accepted the
recommendations). Three gated deliverables:
1. DISABLED on the live host: enabledPlugins."caveman@caveman"=false at user
(~/.claude, uncommitted) + committed repo scope; env CAVEMAN_DEFAULT_MODE=off
retained; marketplace kept for deliberate re-enable. Stops the per-turn
third-party hook execution (which ran regardless of mode=off) at next
session start.
2. SEC-017 opened (docs/security-ledger.md): supply-chain re-verify obligation
pinning SHA 0d95a81 + self-remeasured baseline hashes; register = OPS (no
D-number per GA-R3; precedent D-129). Open SEC 12->13, reconciled at Stage 4
close (not an L10 trigger; CURRENT-STATE untouched).
3. Guardrails: repo-lint L11 FAILs on any *.original.md (caveman-compress
residue), opt-out marker + harness T42-T44 (45/45 PASS); .claude/settings.json
deny on Bash(*python3 -m scripts*)+Bash(*caveman-compress*); CLAUDE.md
"Tooling guardrails" prohibiting the compressor against any repo-tracked
prose. Rejected the proposed plugin-hash-baseline-in-repo-lint (layering
violation; drift belongs in the SEC-017 re-verify duty).
Harness-config + governance only; no cloud mutation. repo-lint 0 fail;
gauntlet ALL GREEN (77). Changelog: docs/changelog-20260724-caveman-disable.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
| 2026-07-23 |
caveman plugin: off-by-default opt-in guard (repo + user CAVEMAN_DEFAULT_MODE=off)
...
Installed the third-party caveman Claude Code plugin (user scope) per operator
direction, then guarded it off-by-default so it never auto-arms while operating
the cloud. Hooks inspected read-only first: purely behavioral, no network /
credential access. Repo .claude/settings.json carries env CAVEMAN_DEFAULT_MODE=off
(durable, shared); the same guard was added to ~/.claude/settings.json (out of
repo, not committed) for off-by-default across all repos. /caveman still
activates on demand for future hardened-workflow deployment testing.
Harness-config only: no cloud mutation, no status change (CURRENT-STATE
untouched), repo-lint 0 fail. Changelog: docs/changelog-20260723-caveman-optin-guard.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
Session permissions: allow ledger-scan + opentofu-validate (read-only; gates untouched)
...
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUjEb7onHpdqFHUnio6iVw
|
| 2026-07-20 |
Permissions: targeted ask rules for scp / pipe-to-ssh / ssh-virsh (classifier walls -> gates, per 2026-07-19 ruling)
...
Deploy-window surfaces that hard-denied and stalled work: scp staging to
the dc0 rack, cat|ssh file staging, nested virsh over ssh. Per the ruled
convention these become ASK (approval prompts, Remote-Control friendly),
never allow -- hard rule 3 mutation gating is unchanged, and read-only
ssh probes stay frictionless.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KiUu1oqt76tWvV4vEC3NAr
|
| 2026-07-19 |
Permission posture: extend ask rules (Remote Control approve flow, no walls)
...
Operator-directed: git mutations (merge/revert/reset/rebase/branch -D),
script mutation verbs (bash scripts/* apply|install), ssh-sudo, and
Skill(*) added to ask so they PROMPT instead of classifier hard-deny.
Nothing auto-allowed; deny + guard hook unchanged. Skill(*) syntax
verified empirically (pre-change call hard-denied, post-change call
prompted + ran). Extends the 2026-07-18 ask-preempts-classifier posture.
Revert: git revert this commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZbUeRE8weySLSGY1odrCi
|
| 2026-07-18 |
Permission posture: ask-list mutation surfaces so they PROMPT (not classifier-deny)
...
Root cause (claude-code-guide, doc-cited): in auto mode, unmatched mutations
fall to the classifier and get HARD-DENIED (no approval prompt). ask/deny rules
are evaluated BEFORE the classifier, so ask-listing the mutation surfaces forces
an approve/deny/adjust prompt -- the intended gating.
.claude/settings.json (operator-applied; editing the permission file is itself
gated): + ask for *opnsense-api.sh POST, *opnsense-plugins.sh apply, *tofu
apply/destroy/import/state rm, *--commit, Edit(.claude/settings*.json); + allow
for the edge GET + opnsense-plugins.sh list. deny + guard hook unchanged.
Changelog + ledger note + carry-forward (verify prompts post-reload) added.
Revert: git revert this commit, or cp .claude/settings.json.bak over it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUBUyinU4sBAT4HV1b9vSz
|
| 2026-07-03 |
|