Skip to content

fix: RARSet without HTML escaping; typed-nil trust fields; RAR hook docs - #766

Merged
osanderson merged 7 commits into
mainfrom
fix/review-core
Nov 1, 2026
Merged

osanderson merged 7 commits into
mainfrom
fix/review-core

Conversation

@osanderson

Copy link
Copy Markdown
Collaborator

What changed

extension.RARSet no longer HTML-escapes (fix). RARSet re-encoded detail objects with json.Marshal, which turns each <, > and & into a six-byte <-style escape. The documented consent pattern (RARGet, show, RARSet, GrantedAuthorization.AuthorizationDetails) could therefore grow a request validated within MaxBytesPerObject and the registry's total bound past them, so CompleteAuthorization refused the grant after the user had consented. When the grant did fit, the token carried escaped bytes instead of the validated ones. RARSet now uses strictjson.MarshalUnescaped for the value, the type member and the assembled object, the same encoder v0.67.2 introduced for granted and stored details.

Other extension encode paths checked and left unchanged:

  • extension.Set: it size-checks its own output, and the client sends that output as-is; the client's request encoding re-escapes it anyway, so switching Set alone would let it pass values the server then refuses.
  • withoutTypeMember and compare's canonical encoding: their output is only decoded or compared, never size-checked or issued.
  • Values/RARValues.MarshalJSON (interaction storage): restored objects are only read back through Get/RARGet.

Typed-nil trust fields (fix). X5CAttesterChain.validate called Revocation's own validate before its typed-nil check, so server.New panicked on (*ClientCertificateCRLs)(nil). TrustedClientCAs.validate made the same call and had no typed-nil check. Both now refuse a typed nil in an interface field first, naming the field. RegisteredAttesterKeys refuses a typed-nil Keys the same way, instead of failing on the first attestation.

Docs. The RARDefinition doc comments and docs/guides/rar.md now spell out four pitfalls:

  • A nil ValidateNarrowing falls back to ValidateGrant, which fails open at the token endpoint for server-minted members. Such definitions must set ValidateNarrowing.
  • Typed hooks can't tell an absent or null member from its zero value, and they compare floats after rounding. Use Validate to require the member, or a pointer, json.Number or json.RawMessage field.
  • Greedy matching depends on the order the client sends objects in. It can refuse an equivalent narrowing sent in a different order, but it never accepts more than the grant allows.
  • An embedder that forwards a client's authorization_details to IssueAccessToken/IssueRefreshToken should run RARRegistry.ParseRequest on them itself.

Tests

  • TestAuthorizationDetailsKeepTheirBytesThroughRARSet (server): a request full of <>& near tight bounds is accepted at PAR and granted through RARGet then RARSet. The access token claim must equal the validated bytes. Before the fix, CompleteAuthorization returned a local error.
  • TestRARSetDoesNotEscapeHTML (extension).
  • TestNewRefusesTypedNilTrustFields: covers typed nils in X5CAttesterChain.Revocation (by value and by pointer), RegisteredAttesterKeys.Keys and TrustedClientCAs.Revocation. Each must be refused with an error naming the field. Before the fix, three of them panicked and one was accepted.

🤖 Generated with Claude Code

osanderson and others added 3 commits November 1, 2026 08:43
RARSet re-encoded details with json.Marshal, which escapes '<', '>'
and '&' as six-byte <-style sequences. A consent page that reads
requested details with RARGet and grants them re-encoded with RARSet,
as the RAR guide shows, could therefore grow a request that was
validated within MaxBytesPerObject and the registry's total bound past
them, and CompleteAuthorization then refused the grant after the user
had consented. RARSet now encodes with the same unescaped encoder the
server uses for granted and stored details, so the granted bytes and
the token's claim match what was validated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
X5CAttesterChain.validate called Revocation's own validate before its
typed-nil check, so New panicked on a nil *ClientCertificateCRLs
instead of returning an error. TrustedClientCAs.validate had the same
call with no typed-nil check at all. Both now refuse a typed nil in an
interface field first, naming the field, and RegisteredAttesterKeys
refuses a typed-nil Keys the same way rather than failing on the first
attestation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spell out that a nil ValidateNarrowing falls back to ValidateGrant and
fails open at the token endpoint for server-minted members; that typed
hooks can't tell an absent or null member from its zero value and
compare floats after rounding; that greedy matching depends on the
order a client sends objects in; and that an embedder forwarding a
client's authorization_details to IssueAccessToken or IssueRefreshToken
should run RARRegistry.ParseRequest on them itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@sonarqubecloud

sonarqubecloud Bot commented Nov 1, 2026

Copy link
Copy Markdown

@osanderson
osanderson merged commit 5dec6c6 into main Nov 1, 2026
27 checks passed
@osanderson
osanderson deleted the fix/review-core branch November 1, 2026 04:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant