Skip to content

release: v0.7.0 - #142

Merged
y1o1 merged 4 commits into
developfrom
release/v0.7.0
Sep 24, 2026
Merged

y1o1 merged 4 commits into
developfrom
release/v0.7.0

Conversation

@y1o1

@y1o1 y1o1 commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Writes ## [0.7.0] — 2026-09-24 from git log v0.6.0..f42f042 (release-policy R2; em dash, as the file already uses), inserted directly above ## [0.6.0]. Each entry is written from the PR description that says what an operator will notice, from #95's comments and from #140's correction comment, and each claim was checked against the code at f42f042 (a few measured on Node 26: what fetch replays across a redirect, fetch on a data: URL, JSON.stringify of a fetch TypeError). Three commits: the section; then, each separate so it can be dropped on its own, the README sentences the R6 audit found describing auth.provider as it was before v0.12.0 and v0.14.0, and a code comment that misstated what fetch does with a cross-origin redirect (both below).

Two commit messages in the range say things that later commits in the same range made untrue, and the section does not repeat them. 8f84104 (F7) says "every other introspection failure is still 500", which F42 (3a6bdf4) replaced. 2702944 (F8) says validation "still sets no redirect option — filed as F43", and F43 (2923655) fixed that.

What 0.7.0 contains

Changed: ten BREAKING entries, each with what an operator sees and what to do:

F30 and #139 carry the BREAKING CHANGE: footer without ! in the subject, which is why the list uses both signals.

Changed: operator-visible but not breaking.

  • F48: validation's failure lines carry a structured error.
  • F40: an empty Authorization is stripped when stripping is on.
  • injection: inject overwrites an empty Authorization header without the authorization_override log #133: an empty Authorization that gets overwritten now logs injection.authorization_override.
  • 921bd6c: an empty Authorization goes upstream in canonical casing. The commit is typed docs(router): but it changes behaviour.
  • F31: action: "forward_stripped" is logged, and the cookie_rejected message changed.
  • F34/F35/F39: provider bodies are read at a bound, and a 200 refusal is reworded.
  • zod 4.6.2 → 4.6.5.
  • F47's slow-body cost (a read that can take up to INJECTION_TIMEOUT_MS) is part of the F47 entry.

Added.

  • The deps seam's properties that 0.6.0 could not reach: F32/F41 (a supplied client's failures logged by error code, an undeclared code logged) and F33 (the session cache keyed by the grant context).
  • F45: VALIDATION_REALM / auth.validation.realm, and a 400 challenge shaped by what was sent.
  • F6: validation single-flight, independent of the TTL.
  • F1–F5: injectable deps on both routers and a per-router introspection cache. This is described as a library seam, not operator configuration: the package is private and ships as the image.
  • F10: the cancellation contract. It changes no behaviour, and is under Added because it is a newly stated contract on the client interfaces.

Security.

F30 and #140 are also BREAKING under Changed. The two places refer to each other rather than repeating each other.

Fixed.

  • F28/F37: unread provider bodies are released.
  • bb3c1f9: a single-flight whose fetcher throws synchronously no longer stays pinned.

Removed. jsonwebtoken and @types/jsonwebtoken (F24).

Dev-only bumps (#129) get no entry.

Why 0.7.0: ten breaking changes → minor, while the major is 0.

Two places where the source disagrees with the brief the section was written from

  • logger: URL credentials survive redaction when the configured introspection URL does not parse #140's password-in-the-log exposure was not in 0.5.0 or 0.6.0. In 0.6.0, validation logged its errors as { "x-request-id", error: e }. Pino serialises only err, so a fetch TypeError came out as "error":{}, with no message (F48 measured this, and JSON.stringify of the refusal gives {}). The message first reached the log in cee6959 (F48, this range), with a redaction that missed a space or an @. 2bbb83e and cdcc8cd closed that before any tag. So the Security entries say what 0.6.0 actually exposed: a data: URL admitted every token, and a userinfo URL failed every request. They say that 0.5.0 and 0.6.0 never wrote such a password. The allowlist and redaction are described as properties of the new error logging, not as a fix of a released leak.
  • "Every line carries an event" is too broad. The startup and shutdown lines have none. The entry says "every request, decision and failure line".

Judged not breaking

  • F31 (c1318c2) changes an action value, and only where stripInboundAuthorization is on, plus one message string. Its commit carries no breaking marker; it is under Changed with an action, so a dashboard keyed on action: "forward" is told to add "forward_stripped".
  • F45 (59e5a7c) adds a realm key and, for a deployment that sets none, one header: a malformed Bearer now gets WWW-Authenticate: Bearer error="invalid_request" on its 400. That is the 400 counterpart of F29, which is marked breaking; F45's commit is not. The entry under Added says so, so a reader can hold it to F29's standard.

Range

git log v0.6.0..f42f042 --oneline (51 commits) and where each commit is recorded:

f42f042 docs(test): the err-allowlist test's comment reads as one sentence        -> no entry (test comment)
3b4b604 docs: a failure while reading a 200 is an invalid response, not ...       -> no entry (docs correction; behaviour unchanged since 0.6.0)
60651ee docs: one timeout is logged at info, and says which                       -> no entry (docs; the fact is in the F47 entry, Changed)
2a80f9a docs: the log vocabulary is shared, the `error` field's shape is not      -> no entry (docs; the fact is in the F48 entry, Changed)
bb3c1f9 fix: a flight whose fetcher throws synchronously is cleared, not pinned   -> Fixed
18b0646 fix(logger): the error allowlist covers `err` as well as `error`          -> Security
2bbb83e fix(logger): redact userinfo quoted as it was given ... (#140)            -> Security
cdcc8cd fix(config)!: the introspection URL must be http(s) without userinfo ...  -> Changed (BREAKING), Security
2ba68c0 fix: log requestId and event in both modes, ... (#134) (#139)             -> Changed (BREAKING)
28d0eda docs: source-directory READMEs describe the directory; ... (#138)         -> no entry (docs and comments only)
71a41fc docs: qualify the pass-through to the exchange-disabled path              -> no entry (docs, #137)
900d6c0 docs: a refused cookie forwards without a minted Authorization, ...       -> no entry (docs, #137)
921bd6c docs(router): keep the upstream decorator for header-name casing (#132) (#136) -> Changed (empty Authorization casing)
1493d8f fix(injection): inject checks presence like the other two paths (#133) (#135) -> Changed
1a8c8b2 docs: cite the issues that track the known problems (#132, #133, #134)    -> no entry (docs, #131)
54c116a docs: every README states its responsibility, carries a date, ...         -> no entry (docs, #131)
87aa4bd chore(deps-dev): bump the dev-dependencies group ... (#129)               -> no entry (dev-only)
10fa63e chore(deps): bump zod (#94)                                               -> Changed (runtime dependency)
cee6959 fix(logger): validation's failure lines carry their error (#95 F48) (#128) -> Changed, Security
59e5a7c feat(validation): the 400 challenges by what was sent, ... (#95 F45) (#127) -> Added
fa8bbb3 test(injection): coalescing tests wait for the join, ... (#95 F46) (#125) -> no entry (tests)
3a6bdf4 fix(validation)!: a provider failure is 502, ... (#95 F42) (#126)         -> Changed (BREAKING)
bb376d3 fix(injection)!: a session grant succeeds on a 200, ... (#95 F38) (#124)  -> Changed (BREAKING)
dfae2da fix(injection): strip an empty inbound Authorization ... (#95 F40) (#122) -> Changed
2923655 fix(validation)!: refuse an introspection redirect ... (#95 F43) (#123)   -> Changed (BREAKING), Security
99fb2e4 fix(injection): an exchange refusal with an undeclared code ... (#121)    -> Added (F41: a property of the new `deps` seam — 0.6.0 took no supplied client, so it could not reach this)
0a68a72 fix(injection)!: a session 401 invalid_client is a config error, ... (#120) -> Changed (BREAKING, F47)
21f3506 fix(validation): read a 200 introspection body at a bound, ... (#95 F39) (#119) -> Changed
02dc8fa fix(injection): release every provider body ... (#95 F37) (#118)          -> Fixed
b5146cc feat(proxy): state the cancellation contract, and pin it (#95 F10) (#117) -> Added
6d0e291 feat(validation): concurrent misses on one token share ... (#95 F6) (#116) -> Added
f2214af feat(validation)!: the 401 carries the RFC 6750 challenge ... (#95 F29) (#114) -> Changed (BREAKING)
d4e77c2 fix(injection): redact a credential at any length, ... (#95 F30) (#115)   -> Changed (BREAKING, footer only), Security
afdfda2 fix(injection): key the session cache by the grant context, ... (#95 F33) (#113) -> Added (F33: the cross-router cache sharing it fixes only became possible with F4, in this range)
2702944 fix(injection)!: neither token client follows a redirect (#95 F8) (#112)  -> Changed (BREAKING), Security
8f84104 feat(validation)!: a provider 401 that refused the proxy is 502, ... (#95 F7) (#111) -> Changed (BREAKING)
10702bb fix(injection): bound the token response body ... (#95 F35) (#107)        -> Changed
fdb8ff7 fix(injection): one discriminator for the whole session-error branch (#95 F32) (#109) -> Added (F32: likewise a property of the `deps` seam)
b48fbe7 refactor(express): extractBearerToken answers the token, ... (#95 F36) (#110) -> no entry (no behaviour change; private type)
c1318c2 fix(injection): report the action a forward without injection ... (#95 F31) (#108) -> Changed
157a71b fix(injection): parseJsonBody answers null for the array ... (#95 F34) (#106) -> Changed
2970d48 fix(validation): cancel the introspection body ... (#95 F28)               -> Fixed (#105)
a542fde refactor: drop the header field nothing read and the dependency ... (#95 F15, F24) (#104) -> Removed (F24); F15 no entry (dead field)
aa208cc refactor(injection): the token-endpoint helpers into a module ... (#95 F19) (#103) -> no entry (pure move)
db11e23 refactor(validation): the introspection client and the cache ... (#95 F5, F23) (#102) -> Added (deps seam, per-router cache)
a6ea95e refactor(validation): the decision out of the handler, ... (#95 F3) (#101) -> Added (deps seam)
89ec9dc refactor(injection): the exchange decision out of the handler, ... (#95 F2) (#100) -> Added (deps seam)
f5472c0 refactor(injection): the session decision out of the middleware, ... (#95 F1, F4) (#99) -> Added (deps seam)
c7b0354 docs: README responsibility maps for the directories ... (#98)          -> no entry (docs)
3bd21d0 refactor(router): one upstream proxy stage for both modes (#97)          -> no entry (pure move, nothing on the wire)
253000e test(validation): pin the router's own mappings ... (#96)                -> no entry (tests)

Reviews

Reviewed before opening by a Claude reviewer (completeness, and every claim against the code at f42f042 and v0.6.0, with measurements on Node 24 and 26) and by an Opus reviewer standing in for Codex (security and breaking-change pass). Codex review not yet run: it is at its usage limit until 2026-09-30; codex review --base f42f042 will be run then, and any finding filed. Their findings — three statements about 0.6.0 that were wrong, an alert claim that was wrong, an F8 action that could not fix the one case that breaks, and wording points — are folded into the section, and the three entries describing states 0.6.0 could not reach now sit under Added as properties of the new seam.

R6 audit

  1. git grep -i -E "(removed|deprecated|planned).*(1\.0 GA|next major|next release|in v[0-9]+\.[0-9]+)": every match is in docs/release-policy.md, where it is an example or the rule. There is nothing to resolve.
  2. git grep -n '"this release' -- src config: no match. git grep -n -i unreleased -- README.md README.ja.md config docs src (excluding the policy and [Unreleased]): no match.
  3. The new section was grepped for 1.0 GA, next major/minor/release, this release, future release, upcoming, unreleased, will be, removed in, until it lands and version literals. One this release was rewritten to 0.7.0. The remaining version literals are 0.6.0 (released), zod 4.6.2 → 4.6.5, and RFC section numbers. No auth.provider version is named in the section.
  4. JSDoc, code, config comments and schema strings contain no forward-version references. The new operator-facing strings (the realm and introspect.url boot errors, Bad Gateway, introspect endpoint redirected, and provider rejected the proxy's client (client_id)) contain no versions.
  5. The PR title is release: v0.7.0.
  6. No pre-existing label is retired in this cut, so the release notes need no retirement note. They do need the breaking section, because generate_release_notes lists PR titles and would not show F30 or fix: log requestId and event in both modes, and a caller-caused 401 at info (#134) #139 as breaking. That section is prepared separately and gets prepended after gh release create.

Provider claims in README.md / README.ja.md, checked against auth.provider tags (latest release v0.15.0). They are fixed in this PR's second commit, separate so it can be dropped on its own, as #92 did with 9db4ce8.

  • Stale — fixed in the second commit: README.md:108 / README.ja.md:103 ("A companion change in auth.provider widens this pin to allowedAudiences ∪ {clientId} … Until it lands, only a token whose aud is exactly the caller's client_id …" / 「それが入るまでは」). This landed in auth.provider v0.12.0: #506, f0bb9ef7. git tag --contains gives v0.12.0 as the first tag, and the commit says "Introspection's audience pin is the caller's allowedAudiences ∪ {clientId}". The "until it lands" sentence is false for every provider release since v0.12.0. release: v0.6.0 #92 already flagged the JA sentence as a follow-up.
  • Stale, same cause — fixed in the second commit: README.md:97 / README.ja.md:92 say the provider pins the audience "to the calling client's own identity" / 「呼び出し元クライアント自身の識別子に固定する」. Since v0.12.0 the pin is allowedAudiences ∪ {clientId}. The bullet two lines below ("register that audience in the client's allowedAudiences") already assumes the wider pin.
  • Names a key that released providers deprecate — fixed in the second commit: README.md:152 / README.ja.md:147 use oauth.accessToken.expiresIn. auth.provider #591 (e354ac36, first in v0.14.0) makes that key a deprecated alias of oauth.accessToken.defaultExpiresIn. It still works, and the standalone composition warns at boot when only the old key carries a non-default value. release: v0.6.0 #92 kept expiresIn because #591 was unreleased at the time. Now that it is released, the README should name defaultExpiresIn and mention the alias for providers before v0.14.0.
  • Accurate, could name the release: README.md:267 / README.ja.md:249 ("A provider that includes auth.provider#588 also caps a jwt-bearer token's lifetime …"). #588 (f40df5f8) is in v0.14.0 and v0.15.0. The sentence is true as a conditional. It could now say "auth.provider v0.14.0 and later" (R1 allows released versions).
  • Accurate (shipped by v0.12.0, still true in v0.15.0): README.md:146–150 / README.ja.md:141–143, the UserSession tracking section. The session grant requires a live tracked session whose sub matches and returns 400 invalid_grant otherwise. It stamps sid, issues no refresh token, and answers an unauthenticated session 401 unauthorized. The logout invalidation and the operator runbook exist (packages/oauth/src/grants/session.mts, packages/session/src/module.mts, docs/operator-runbook.md at v0.15.0). F47's claim that invalid_client comes only from the client check holds at v0.15.0.
  • Accurate: README.md:233: the jwt-bearer grant reads scope, and reads resource under oauth.resourceIndicator.enabled. Also accurate: the rate-limit section's <endpoint>:ip:<ip> key, the 60/60s default, and the memoryRateLimiter.limits / redisRateLimiter.limits / defaultLimit keys (packages/core/config/reference.conf, packages/core/src/ratelimit/guard.mts at v0.15.0).

One code comment was inaccurate — fixed in the third commit (comment only). src/modes/validation/introspection-client.mts (the comment above redirect: "manual") says that across origins fetch "strips" the request's credential. fetch strips the Authorization header, but a 307/308 re-sends the body, and the body is token=<the caller's token>. I measured this on Node 26. The Security entry states it correctly.

Verification

  • Three commits: release: v0.7.0 (CHANGELOG.md), docs: the provider behaviour the README describes, as released (README.md, README.ja.md), and docs(validation): a cross-origin redirect drops the header, not the token (a comment in src/modes/validation/introspection-client.mts).
  • Each of the 51 commits in the range is recorded above.
  • Each of the ten breaking changes has an entry with an Action.
  • Gates on this branch: lint / typecheck / build / test all exit 0, 693 tests.

Release order

  1. Merge this PR.
  2. o3co/auth pins PROXY_REV to the merge commit and runs the umbrella E2E.
  3. Once that is green, tag v0.7.0 at that commit.
  4. After the release workflow creates the GitHub Release, prepend the "Breaking changes in 0.7.0" section to its generated notes.

🤖 Generated with Claude Code

y1o1 and others added 3 commits September 24, 2026 15:02
Writes `## [0.7.0] — 2026-09-24` from `git log v0.6.0..HEAD`, per
docs/release-policy.md R2 (em dash, as the file already uses). The range and
where each commit is recorded are in the pull request's description.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The R6 audit of the v0.7.0 cut checks README claims about o3co/auth.provider
against its releases (latest v0.15.0). Three were stale:

- Under client authentication, the README said the provider pins the
  introspected token's audience to the calling client's own identity, and a
  note said a companion change would widen it to
  `allowedAudiences ∪ {clientId}` — "until it lands". It landed in
  auth.provider v0.12.0 (f0bb9ef7). The paragraph now states the pin as
  released, with what earlier releases did, and the note is gone.
- The access-token lifetime key is `oauth.accessToken.defaultExpiresIn`;
  `expiresIn` is what older providers used and newer ones keep as a
  deprecated alias. The 0.6.0 cut kept `expiresIn` because the rename was
  not yet released; it is now.

English and Japanese alike. A separate commit so it can be dropped on its
own, as in the 0.6.0 cut.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…oken

The comment above the introspection client's `redirect: "manual"` said a
cross-origin redirect "strips" the request's credential. `fetch` drops the
`Authorization` and `Cookie` headers when a redirect crosses origins; a 307
or 308 still re-sends the body, and the introspection body is
`token=<the caller's token>`. So a followed cross-origin 307/308 carried the
caller's token to another origin — one more reason F43 refuses a redirect,
and the reason the v0.7.0 Security entry gives. Comment only.

Found while writing the v0.7.0 section.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Several changelog and README statements remain inconsistent with the documented audience and redirect behavior.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 Low severity

Open (2)
What changed in this PR

Documents the v0.7.0 release, updates provider guidance, and corrects redirect behavior documentation.

Changes:

  • Adds categorized v0.7.0 changelog entries.
  • Updates English and Japanese README guidance.
  • Clarifies cross-origin introspection redirects.
File Summary
CHANGELOG.md Adds v0.7.0 release notes and migration guidance.
README.md Updates provider audience and token-lifetime guidance.
README.ja.md Mirrors the provider guidance updates in Japanese.
src/​modes/​validation/​introspection-client.mts Corrects redirect behavior documentation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread README.ja.md
Comment thread README.md
Copilot on #142: the previous commit restated the introspection audience
pin as auth.provider has released it since v0.12.0 — the client's own
`client_id` and its `allowedAudiences` — but two sentences around it still
assumed the older pin. The challenge section said a token whose `aud` "does
not name this proxy's client" is refused however fresh it is; the resource
example said an "unrelated" `client_id` refuses every resource-audience
token. Both now name the condition that actually refuses: an `aud` that is
neither the client nor an audience registered in its `allowedAudiences`.
English and Japanese alike.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@y1o1
y1o1 merged commit dee5a79 into develop Sep 24, 2026
1 check passed
@y1o1
y1o1 deleted the release/v0.7.0 branch September 24, 2026 06:19
y1o1 added a commit to o3co/auth that referenced this pull request Sep 24, 2026
…tagging (#39)

PROXY_REV and VERIFIER_REV move to the merge commits of o3co/auth.proxy#142 and o3co/auth.policy-verifier#268; PROVIDER_REV stays at v0.15.0. E2E green on run 35963975620.
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.

2 participants