docs(server): correct overstated WORM/config-edit comment; pin two-step demotion

The decider comment claimed standing config-edit "only ever grants VerbA, so it
can never write/delete/create WORM records." True for a single decision, but it
overstated the guarantee: a config-editor who administers a WORM zone can edit
that zone's .zddc (inherit:false drops the embedded worm:), after which ordinary
writes are no longer clamped. That two-step demotion is intended — owning a
subtree's policy includes its worm: marker, and the edit is access-logged — so
WORM is tamper-EVIDENT to its policy owner, not tamper-PROOF.

Rewrite the comment to say so (and note where to gate worm: relaxation behind
elevation if a deployment needs tamper-proof markers), and add
TestStandingConfigEdit_WormDemotionIsTwoStep pinning the boundary (direct WORM
write denied unelevated), the lever (config-edit allowed), and the consequence
(post-demotion write allowed). Surfaced by the deferred-findings triage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-06-09 19:57:13 -05:00
parent d14516a74d
commit 88ef2dd921
2 changed files with 59 additions and 5 deletions

View file

@ -243,11 +243,21 @@ func (d *InternalDecider) Allow(_ context.Context, input AllowInput) (bool, erro
// is a STANDING permission: a subtree admin (admins: cascade) or a
// holder of the `a` verb may edit the config of subtrees they
// administer WITHOUT elevating. This sits ABOVE the WORM clamp because
// config is not WORM-protected data — and it only ever grants VerbA,
// so it can never write/delete/create WORM *records* (those need
// W/C/D, which stay clamped and behind the elevated bypass above).
// Elevation is thus purely additive: it adds the WORM/destructive
// overrides, never gating config-edit you already have authority for.
// config is not WORM-protected data.
//
// Scope: this grants VerbA only, so no SINGLE decision here authorizes a
// WORM *record* write/delete/create (those need W/C/D, which stay clamped
// below, behind the elevated bypass above). It does NOT, however, make a
// WORM zone tamper-proof against its own policy owner: a config-editor who
// administers a WORM directory may edit that directory's .zddc — including
// relaxing or (via inherit:false) dropping its `worm:` marker — after
// which ordinary writes to that subtree are no longer clamped. That
// two-step demotion is intended (owning a subtree's policy includes its
// worm: declaration) and is access-logged/transparent: WORM is therefore
// tamper-EVIDENT to its config owner, not tamper-PROOF. A deployment that
// needs the marker immutable except under elevation should gate worm:
// relaxation behind IsActiveAdmin. The edit-then-write composition is
// pinned in standing_config_test.go (TestStandingConfigEdit_WormDemotionIsTwoStep).
if verb == zddc.VerbA && zddc.IsConfigEditor(chain, email) {
return true, nil
}

View file

@ -72,3 +72,47 @@ func TestStandingConfigEdit(t *testing.T) {
}
}
}
// TestStandingConfigEdit_WormDemotionIsTwoStep documents the (intended)
// composition that a single-action view of the decider hides: a config-editor
// who administers a WORM zone cannot write a WORM record directly, but CAN
// demote the zone by editing its own .zddc, after which an ordinary write is
// no longer clamped. WORM is thus tamper-evident to its policy owner, not
// tamper-proof. Pinned so the behavior is an explicit, tested decision — if a
// deployment ever needs WORM markers immutable except under elevation, this is
// the test that must change alongside gating worm: relaxation behind
// IsActiveAdmin in policy.InternalDecider.Allow.
func TestStandingConfigEdit_WormDemotionIsTwoStep(t *testing.T) {
d := &InternalDecider{}
dec := func(chain zddc.PolicyChain, p zddc.Principal, action string) bool {
ok, _ := AllowActionFromChainP(context.Background(), d, chain, p, "/proj/probe", action)
return ok
}
alice := zddc.Principal{Email: "alice@x", Elevated: false} // config-editor, NOT elevated
// Before — alice administers a WORM zone (admins: + a non-nil worm list).
worm := zddc.PolicyChain{
Levels: []zddc.ZddcFile{{Admins: []string{"alice@x"}, Worm: []string{}}},
HasAnyFile: true,
}
// The boundary holds: a direct WORM record write is denied unelevated...
if dec(worm, alice, ActionWrite) {
t.Error("unelevated config-editor must NOT directly write a WORM record")
}
// ...but she CAN edit the zone's policy (VerbA) — the lever for demotion.
if !dec(worm, alice, ActionAdmin) {
t.Error("config-editor should be able to edit the WORM zone's .zddc unelevated")
}
// After — alice has rewritten that .zddc: inherit:false dropped the
// embedded worm: and her acl now grants rwcd (the post-edit cascade the
// file API persists). The subtree is no longer WORM, so her write lands —
// still unelevated. This is step two of the intended demotion.
demoted := zddc.PolicyChain{
Levels: []zddc.ZddcFile{{ACL: zddc.ACLRules{Permissions: map[string]string{"alice@x": "rwcd"}}}},
HasAnyFile: true,
}
if !dec(demoted, alice, ActionWrite) {
t.Error("after the config-editor demotes the zone, the ordinary write should be allowed")
}
}