diff --git a/.claude/settings.json b/.claude/settings.json index e3b13d4..6b2dd48 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -14,7 +14,9 @@ "Bash(bash scripts/preflight.sh*)", "Bash(python3 scripts/repo_lint.py*)", "Bash(python3 scripts/provider-bundle-check.py*)", - "Bash(bash tests/*)" + "Bash(bash tests/*)", + "Bash(OPNSENSE_API_HOST=* bash scripts/opnsense-api.sh GET *)", + "Bash(bash scripts/opnsense-plugins.sh list*)" ], "ask": [ "Bash(juju destroy-model *)", "Bash(juju remove-machine *)", @@ -28,7 +30,13 @@ "Bash(openstack * set*)", "Bash(openstack * unset*)", "Bash(* --apply*)", "Bash(git commit*)", "Bash(git push*)", - "Bash(sudo *)", "Bash(virsh *)", "Bash(rm *)" + "Bash(sudo *)", "Bash(virsh *)", "Bash(rm *)", + "Bash(*opnsense-api.sh POST *)", + "Bash(*opnsense-plugins.sh apply*)", + "Bash(*tofu apply*)", "Bash(*tofu destroy*)", + "Bash(*tofu import*)", "Bash(*tofu state rm*)", + "Bash(*--commit*)", + "Edit(.claude/settings.json)", "Edit(.claude/settings.local.json)" ], "deny": [ "Bash(vault operator init*)", "Bash(vault operator rekey*)", diff --git a/docs/changelog-20260718-permission-posture-ask-preempts-classifier.md b/docs/changelog-20260718-permission-posture-ask-preempts-classifier.md new file mode 100644 index 0000000..8953cd2 --- /dev/null +++ b/docs/changelog-20260718-permission-posture-ask-preempts-classifier.md @@ -0,0 +1,40 @@ +# Changelog 2026-07-18 -- permission posture: mutations PROMPT, not classifier-deny + +## Problem +The session runs in Claude Code `auto` permission mode. Mutations that matched +NO `permissions` rule (e.g. `bash scripts/opnsense-api.sh POST ...`, editing +`.claude/settings*.json`, invoking a Skill) fell through to the auto-mode +classifier, which **hard-denied** them -- no approval prompt, so the operator +could not approve/adjust and had to run them by hand. That is a wall, not a gate. + +## Finding (claude-code-guide, doc-cited) +`deny` and explicit `ask` rules are evaluated **before** the auto-mode classifier +(Claude Code `auto-mode-config` docs). An explicit `ask` rule therefore FORCES a +prompt even in auto mode. So the fix is to `ask`-list the mutation surfaces; they +become approve/deny/adjust prompts instead of classifier hard-denials. + +## Change (`.claude/settings.json`, operator-applied) +Added to `ask` (mutations now PROMPT): `Bash(*opnsense-api.sh POST *)`, +`Bash(*opnsense-plugins.sh apply*)`, `Bash(*tofu apply*)`, `Bash(*tofu destroy*)`, +`Bash(*tofu import*)`, `Bash(*tofu state rm*)`, `Bash(*--commit*)`, +`Edit(.claude/settings.json)`, `Edit(.claude/settings.local.json)`. +Added to `allow` (read-only edge queries stay frictionless): +`Bash(OPNSENSE_API_HOST=* bash scripts/opnsense-api.sh GET *)`, +`Bash(bash scripts/opnsense-plugins.sh list*)`. +`deny` + the PreToolUse guard hook UNCHANGED. Broad `*`-patterns catch the verb +(POST/apply/--commit/tofu-mutations) to reduce future whack-a-mole. + +The `Edit(.claude/settings*.json)` ask rules bootstrap self-service: from now on +the agent CAN propose settings edits -- they arrive as prompts the operator +approves, instead of being hard-denied (the chicken-and-egg that required this +first edit to be operator-applied by hand). + +## Rationale for staying in `auto` + `ask` (not flipping to `default` mode) +`default` mode would prompt on ALL non-allow-listed commands including read-only +probes (`ssh`, `journalctl`, `ping`, `pkg info`) -- very noisy for this +read-heavy audit work. `auto` + comprehensive `ask` keeps reads frictionless and +makes mutations prompt: the exact requested behavior. + +## Revert +`git revert` this commit (restores the prior `.claude/settings.json`), or +`cp .claude/settings.json.bak .claude/settings.json` (operator's local backup). diff --git a/docs/session-ledger.md b/docs/session-ledger.md index ecdc096..bb8ded0 100644 --- a/docs/session-ledger.md +++ b/docs/session-ledger.md @@ -778,6 +778,22 @@ gated), qga functional enablement (channel+enable+restart), git-backup wiring, the 4 D-129 sub-decisions. See `docs/changelog-20260718-opnsense-plugins-installer.md`. +**PERMISSION POSTURE UPDATED 2026-07-18 (mutations PROMPT, not classifier-deny).** The `auto`-mode +classifier was HARD-DENYING mutations that matched no rule (edge API POST, settings edits, Skill invokes) -- +no approval prompt, so the operator had to run them by hand. claude-code-guide confirmed (doc-cited) that +`ask`/`deny` rules are evaluated BEFORE the classifier, so `ask`-listing the mutation surfaces forces a +prompt. `.claude/settings.json` updated (operator-applied by hand -- editing the permission file is itself +gated; chicken-and-egg): added `ask` for `*opnsense-api.sh POST *`, `*opnsense-plugins.sh apply*`, `*tofu +apply/destroy/import/state rm*`, `*--commit*`, `Edit(.claude/settings*.json)`; added `allow` for the edge +GET + `opnsense-plugins.sh list`. The `Edit(.claude/settings*.json)` ask rules mean the agent can now +PROPOSE settings edits (prompted), no longer hard-blocked. Stayed on `auto`+`ask` (not `default` mode) to +keep read-heavy audits frictionless. See `docs/changelog-20260718-permission-posture-ask-preempts-classifier.md`. +**CARRY-FORWARD (verify after the operator's reload):** the new rules take effect only after a Claude Code +restart. Post-reload, CONFIRM a mutation now PROMPTS (approve/deny/adjust) instead of hard-denying -- the +`os-iperf` install (`OPNSENSE_API_HOST=10.10.0.1 bash scripts/opnsense-plugins.sh apply vr1-edge`, or the raw +`opnsense-api.sh POST core/firmware/install/os-iperf`) is the ready test. If it still hard-denies, the rule +pattern needs adjusting (check the exact command string vs the glob). + ## OPEN WORK -- VR0 / DC0 (the running testcloud) 1. **d011 batch-3 live-validation queue** -- `scripts/checks/d011-04-octavia-lb.sh` and