feat(policy): format 1.1.0 with Deny, prefix wildcards and named permissions #69

Merged
james.maes merged 14 commits from feat/policy-1-1-deny-prefix into develop 2026-11-09 03:01:05 +00:00
14 Commits
Author SHA1 Message Date
james.maesandClaude Sonnet 5.5 2c5baa336d ci: re-run (the previous run hit a runner Docker port-binding error in Testcontainers)
maven / build (push) Successful in 11m49s
build / maven (push) Successful in 11m49s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 20:48:19 -06:00
james.maesandClaude Sonnet 5.5 e2a3cd3021 test(policy): skip the python blueprint run when the CI image has no python
maven / build (push) Failing after 1m43s
build / maven (push) Failing after 1m43s
The shared CI runner image has no python3, so the 'fail without python' rule made
every build red at the unit stage. The python run now skips unless
BLUEPRINT_PYTHON_REQUIRED=true; a pure-Java test keeps the blueprint's safety
shape checked everywhere.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 20:38:27 -06:00
james.maesandClaude Sonnet 5.5 49905f8e86 fix(policy): break-glass operators keep identity permissions; absent groups claim is unavailable
maven / build (push) Failing after 1m29s
build / maven (push) Failing after 1m29s
Bootstrap identity permissions are added to an operator after the Deny filter, so
a void or denying group cannot lock them out of repairing policies. A token with
no groups claim and no policy claim is unavailable (an empty array stays known).
The mirror restores a policy attribute verbatim only when it parses. Docs mark the
resolve/resolveResult/apply helpers as not for sign-in assembly. More mapper tests
(void claim alone, bootstrap under void, guard error at sign-in, specific
QAuthenticationException on refused mirror snapshots).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 20:32:00 -06:00
james.maesandClaude Sonnet 5.5 a76843d622 docs(policy): break-glass operators, absent groups claim, claim-only mapper
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 20:31:59 -06:00
james.maesandClaude Sonnet 5.5 cd1ebc0f73 fix(policy): one shared session-permission rule; Deny beats the mirror; void/unavailable
maven / build (push) Failing after 1m29s
build / maven (push) Failing after 1m29s
SessionPermissionRules.compute serves sign-in, automated sessions and the
refreshing mapper: void leaves break-glass only even while the guard blocks;
unavailable leaves only configured outage fallbacks; known applies every Deny to
policy grants, mirror permissions, static and host floors and earlier session
permissions. The refreshing mapper reads Deny rules from the mirrored group rows
on the same connection (checkpoint-consistent, bounded). Automated sessions void
on an unknown group pk and no longer keep earlier permissions when unresolved.
The guard keeps only a real last state (never OFF, original time, 15 min bound).
The mirror stores the policy document unredacted. Blueprint rejects extra
top-level keys and a missing Statement.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 19:56:28 -06:00
james.maesandClaude Sonnet 5.5 81fa5b53bf docs(policy): one shared session rule, void/unavailable, blueprint shape
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 19:56:26 -06:00
james.maesandClaude Sonnet 5.5 bad06d806a fix(policy): guard-blocked and floor Deny, unavailable state, strict documents
maven / build (push) Failing after 1m32s
build / maven (push) Failing after 1m32s
The cut-over guard's blocked branch applies valid Deny rules (claim and graph) to
floor, host source and existing permissions; a guard error keeps the last known
state. Static and host floors are subject to Deny everywhere (bootstrap exempt),
including the refreshing mapper, which now reads Deny rules from the mirror
without an API call. PolicyResult gains UNAVAILABLE; a failed graph read with no
claim voids earlier permissions; resolveResult reports the full rules. Missing
parents void. Documents with unknown top-level keys or no Statement are
rejected. The group fingerprint covers the configured policy attribute. Stronger
tests for release, grandparent Deny, and the blueprint (fails without python).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 19:24:01 -06:00
james.maesandClaude Sonnet 5.5 4a375848f2 docs(policy): vectors and spec for document shape, blocked-guard and floors
Documents with an unknown top-level key or no Statement are rejected (new
rawDocuments vectors); a Deny subtracts static and host floors, also while the
guard blocks; unreadable and void states; fingerprint covers the policy attribute.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 19:23:59 -06:00
james.maesandClaude Sonnet 5.5 a6ac960f3a fix(policy): close fail-open edges found in review
maven / build (push) Failing after 1m26s
build / maven (push) Failing after 1m25s
Operator credit never includes a name their own policies deny; the configured
policy attribute is reserved against attribute edits; un-voiding an invalid
policy is a release; a deliberate Deny no longer trips the cut-over guard and
void is shown; a group missing from the graph voids; PolicyResult typed API
with apply(); Deny of a prefix covers dotted host names; empty arrays and bad
host names reject; Save Policy requires expectedFingerprint; the catalog cache
key covers all rule sources and the runtime shares the cached catalog.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 18:54:02 -06:00
james.maesandClaude Sonnet 5.5 86aa863c7e docs(policy): vectors, spec and blueprint for review fixes
Deny of a prefix or named resource covers its dotted host names; empty
Resource/Action/Permission arrays reject; the blueprint voids on any document
under the attribute that is not exactly our spec (skipping other specs is an
opt-in) and on duplicate keys. Spec: what a Deny subtracts, void is not empty,
operators, host customizer API, blueprint known differences.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 18:54:02 -06:00
james.maesandClaude Sonnet 5.5 33e75b6d45 test(policy): golden vectors, Deny properties, consumer tests; blueprint, changelog, upgrade notes
maven / build (push) Failing after 1m43s
build / maven (push) Failing after 1m43s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 18:09:24 -06:00
james.maesandClaude Sonnet 5.5 1ca28ecce6 feat(policy): format 1.1.0 typed rules (Deny, prefix resources, named permissions)
PolicyParser returns typed PolicyRules (Allow and Deny terms, or void) instead
of permission names, so a Deny or prefix cannot be read as a grant. Result =
union(Allow) minus union(Deny) across all of a user's groups; one invalid
policy voids every group of the user. Allow expands against one live universe;
Deny is a predicate. Canonicalization never drops a Deny. Editor: save-time
zero-match checks, effective-permission subset rule, release of Deny on
removal, last-admin check. Host names register without an Authentik runtime;
PolicyCatalog is cached per QInstance. Default PolicyVersion is 1.1.0.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 18:09:24 -06:00
james.maesandClaude Sonnet 5.5 9c76ff389e docs(policy): vectors and spec for hyphenated names, one universe, host names, claim shape
Permission names are dot-separated segments of [A-Za-z0-9_-] (bare names
allowed); Resource stays [A-Za-z0-9_]+ and utils_* prefixes. Every Allow
expands against one universe, host names are not features, a standard action
a resource lacks is dropped, and the claim is one merged document.
New vectors: hyphenated and bare Permission names, invalid names, hyphenated
Resource, host name as Action, dropped action, not-in-universe name.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 18:01:26 -06:00
james.maesandClaude Sonnet 5.5 6f9ec41f35 docs(policy): specify format 1.1.0 (Deny, prefix wildcards, named permissions)
maven / build (push) Failing after 45s
build / maven (push) Failing after 45s
Spec and shared golden vectors only; no parser change yet.

- Effect Allow|Deny; result = union(Allow) minus union(Deny) across all of
  a user's groups, Deny wins, no ordering, expanded against the live
  instance so a Deny also covers future resources.
- Prefix resources (utils_*): at least two characters, literal part ends in
  an underscore, wildcard last. Deny with Action * means every action
  including features; Allow with Action * stays the five standard actions.
- Permission[] key for exact dotted/host permission names under Allow and
  Deny, so Deny can remove what full access includes.
- Zero-match prefix, feature or named permission is rejected at save for
  both effects. Unknown statement keys (Condition) reject the document.
- One invalid document among a user's groups voids all of them: dropping a
  bad group could drop a Deny and escalate access.
- Canonicalization never drops a Deny.
- policy-vectors-1.1.0.json: 27 vectors over a 16-name universe, to be run
  by this parser, the authentik-cd claim validator and Forge's tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 17:28:33 -06:00