fix(aosguard): make kubectl name its cluster, closing a boundary that never bound #1349
No reviewers
Labels
No labels
burndown-2026-06
burndown-2026-08
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/advocate
role/director
role/exec
role/frontend
role/gamedev
role/human
role/platform
role/qa
role/science
role/sysadmin
state
ambient
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/agentic-os!1349
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/mt75-kubectl-context"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #1348. Kai's call on the shape, made after two rounds of corrected premises.
The boundary did not bind
The kubectl guardfile header claimed "the exec target is fixed at parse, so the caller can never substitute it." False as deployed.
execfixes the binary, not the cluster, and nothing constrained--context:Different node, different server version. aosguard did no flag filtering on this wrap at all:
--definitely-not-a-real-flagcame back aserror: unknown flagfrom kubectl, not from the guard.Scoped honestly, per Vera's refinement: a typo already failed closed, because the reachable set is exactly the kubeconfig's contexts. The real exposure was a valid but wrong context accepted on
apply,scale, androlloutwhile the comment told a reader that was impossible - which made a stray--contextlook inert when it was not.Why wrap-level, and why mandatory
One
only pass when context is kai-server ser8at wrap level.execverb.go:300checks wrap-levelWhenson every leaf, so a grant added later cannot forget it. Twenty per-grant copies would each be a place to omit it.Mandatory rather than checked-when-present is forced by the dialect, not a preference.
resolveSelectorreturns no values for an absent flag,firstMatchthen fails, and a non-denywhenrefuses fail-closed. Confirmed on an unrelated wrap:So an optional allowlist is not expressible in umbra today. Two shapes were rejected for concrete reasons:
allow-flagis a strict allowlist (docs/execverb.md), so addingallow-flag --contexttoapplywould deny-funless every legitimate flag were enumerated per grant; andargv-prefixpins leading argv but caller args land after it, where a later--contextwins.Verified against the built binary
Behavior, not dialect-reading - this file's last claim was wrong precisely because nobody ran it:
--context--context ser8--context kai-server--context prod-secretapplywith no--contextThe unknown-context deny moving from kubectl into the guard is the point: the refusal now happens in the control.
Known cost
config get-contextsnow needs a context, which reads circular. The deny message names both valid values, so the error is self-documenting. Flagged rather than carved out, since an exemption in a security control is not mine to add quietly.Blast radius
forgejo_storage_measure.pycalls thekubectlbinary through its own wrap, not throughaosguard ops kubectl, so it is unaffected. It does run against the active context with no--context, which is the same ambiguity one layer over - noted, not touched here.The composed Kubernetes skill routed agents to this wrap without the flag, so it would have taught a call that now denies, and it still described the estate as single-cluster. Both corrected.
Verification
just aosguard-buildmaterializes cleanly,uv run pytest753 passed,pre-commit run --all-filesgreen.typossplits the alpha run inser8, so the allowlist key has to beser;ser8 = "ser8"silently never matches. Noted in.typos.tomlso it is not "fixed" back.Worth a sweep
If one guardfile documented an invariant it did not enforce, others may too. Vera has that in #1348's acceptance criteria. Not attempted here.