identity: scope-as-set + scope narrowing on refresh (RFC 6749 §6, +6 tests)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s

Each access token now carries its own effective scope (<= the grant's max).
refresh/3 requests a narrower scope; the request must be a subset of the
grant scope, else {error, invalid_scope} and the refresh token is NOT
consumed (client may retry, §5.2). refresh/2 keeps full scope; scope stays
opaque (atom or list) for issue so all prior atom-scope tests are unchanged.
Also files a Blocker: PKCE S256 is blocked on erlang substrate bugs (binary
=:= always true; crypto:hash ignores binary content). token 24/24, 130/130.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 01:43:16 +00:00
parent 21673b6731
commit e951f23f14
5 changed files with 93 additions and 19 deletions

View File

@@ -19,7 +19,7 @@ through the event log, all authorization questions delegated to `acl-on-sx`.
## Status (rolling)
`bash lib/identity/conformance.sh`**124/124** (all four phases complete)
`bash lib/identity/conformance.sh`**130/130** (4 phases + ext: scope narrowing)
## Ground rules
@@ -78,9 +78,9 @@ lib/identity/api.sx ── (identity/login) (identity/grant?) (identity/revoke)
- [x] tests: audit completeness, cross-instance subject mapping
## Extensions (base roadmap complete; deepen the engine)
- [ ] PKCE S256 method (RFC 7636 §4.2) — SHA256 challenge derivation, not just `plain`
- [~] PKCE S256 method (RFC 7636 §4.2) — BLOCKED on erlang substrate (see Blockers)
- [ ] access-token TTL / `expires_in` — tokens expire as a grant timeout, introspect honours it
- [ ] scope as a set + scope narrowing on refresh (RFC 6749 §6)
- [x] scope as a set + scope narrowing on refresh (RFC 6749 §6)
- [ ] client registry: public vs confidential clients, client authentication (RFC 6749 §2)
- [ ] client-credentials grant (RFC 6749 §4.4) and device grant (RFC 8628)
- [ ] acl-on-sx delegation: wire `verify`/membership projection → an acl decision, integration test
@@ -88,6 +88,15 @@ lib/identity/api.sx ── (identity/login) (identity/grant?) (identity/revoke)
- [ ] unify `api.sx` over oauth + membership + audit (one facade, audited login/consent)
## Progress log
- 2026-06-07 — scope narrowing (ext): each access token now carries its own
EFFECTIVE scope (<= the grant's max). `refresh/3` requests a narrower scope;
the request must be a subset of the grant scope (RFC 6749 §6) else
`{error, invalid_scope}` and the refresh token is NOT consumed (client may
retry, §5.2). `refresh/2` keeps full scope; scope stays opaque (atom or list)
for issue, so all prior atom-scope tests pass unchanged. token 18→24, 130/130.
Also filed Blocker: PKCE S256 needs SHA256+binary compare, both broken in the
erlang substrate (binary `=:=` always true; crypto:hash ignores binary
content) — deferred, plain method stays.
- 2026-06-07 — `federation.sx`: trust-gated, advisory federated identity.
A peer assertion is accepted only from an explicitly trusted peer
(else `{error, untrusted}`) and is flagged `{peer_asserted, Peer}`, never
@@ -171,4 +180,18 @@ lib/identity/api.sx ── (identity/login) (identity/grant?) (identity/revoke)
`tests/session.sx`). 11/11.
## Blockers
(loop fills this in)
- 2026-06-07 — **PKCE S256 blocked: erlang binary bugs.** Two substrate bugs
in `lib/erlang` make a correct/secure S256 impossible (S256 needs
`BASE64URL(SHA256(verifier))` compared against the stored challenge):
1. **Binary `=:=` always true.** `<<"v1">> =:= <<"v2">>``true`;
`<<"abc">> =:= <<"abd">>``true`. So a hash comparison can't reject a
wrong verifier.
2. **`crypto:hash` ignores binary-literal content.**
`crypto:hash(sha256, <<"v1">>)` and `crypto:hash(sha256, <<"v2">>)` return
the *identical* 32-byte digest (`6e 34 0b 9c …`), which is also ≠ the
correct SX-level `(crypto-sha256 "abc")` (`ba 78 16 bf …`). The binary
payload isn't reaching the hash. (Atom input → badarg→nil, separate issue.)
Minimal repro (epoch protocol, after loading lib/erlang/runtime.sx):
`(erlang-eval-ast "case <<\"a\">> =:= <<\"b\">> of true -> bug; false -> ok end")`
`bug`. Not in scope to fix (lib/erlang is a substrate). PKCE `plain`
remains correct and in use; S256 deferred until the binary path is fixed.