You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
oauth: on a private plain-HTTP deployment the authorization-server metadata publishes an https:// issuer while authorization_servers[0] and every endpoint are http:// (RFC 8414 §3.3 mismatch) #26472
On a deployment that serves OAuth over plain HTTP on a private host, the authorization-server metadata publishes an https:// issuer. Every other OAuth URL on that boot is http://: the protected-resource document's authorization_servers entry and every endpoint in the metadata. RFC 8414 §3.3 requires the metadata's issuer to be identical to the authorization-server URL the client was given. A client that validates it will refuse the discovery, so the private-host OAuth track the deployment advertises may not complete end to end.
QA-source: #26468 · ai.mcp-oauth-private-host-transport · observed beside the clauses
Boot with a private-host base URL: OS_PORT=41566 OS_AUTH_URL=http://10.20.30.40:41566 pnpm -C examples/app-showcase exec objectstack dev --ui --seed-admin --no-compile --no-watch -p 41566 -d file:/tmp/x/data.db
The boot log warns, as designed: OAuth is served UNENCRYPTED … (http://10.20.30.40:41566/api/v1/auth) … accepts this origin only because the host is loopback or a private / link-local address.
GET /.well-known/oauth-protected-resource returns resource: http://10.20.30.40:41566/api/v1/mcp and authorization_servers: ['http://10.20.30.40:41566/api/v1/auth'].
GET /.well-known/oauth-authorization-server/api/v1/auth returns:
GET /api/v1/auth/.well-known/openid-configuration returns the same https issuer with http endpoints.
Expected:issuer = http://10.20.30.40:41566/api/v1/auth, the value in authorization_servers[0] and in the boot's own warning line.
Actual: the scheme is rewritten to https.
The QA runner reproduced it on two fresh boots, and it was reproduced again on an independent verification boot. A public-origin boot shows the same shape (its discovery is refused anyway), and a loopback origin is consistent.
Mechanism (from the runner's trace, to confirm)
validateIssuerUrl() in @better-auth/oauth-provider 1.7.12 rewrites a non-loopback http issuer to https. ObjectStack's transport rule accepts private and link-local plain-HTTP origins, so the two disagree on exactly the origins the platform admits.
Notes
This fails closed. It is an interoperability defect, not an authorization hole: no token or code is issued to the wrong party.
No ledger clause asserts that the metadata issuer equals authorization_servers[0]. That gap is parked on the wave anchor [sweep] v18.2.0 release test sweep #25807 as a checklist-accuracy finding.
Full evidence chain: the run record named in the QA-source line above.
p2: on a private plain-HTTP host, the authorization-server metadata publishes an https:// issuer while every endpoint and authorization_servers[0] are http:// (RFC 8414 §3.3). Derive the issuer from the same base URL the endpoints use. ⛔ Do not widen what the plain-HTTP rule admits; only make the published issuer agree with the endpoints. Found by the 18.2.0 wave-2 sweep ([sweep] v18.2.0 release test sweep #25807).
Claim: PM loop round 4 · card thread · 18.2.0 wave-2 split with server seat 1
Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01FEkQ5p7SSrVe6yoRJb2o6k
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-cdz4ci (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#2
Ruling-ref: triage 6434147012 (make the published issuer agree with the endpoints and authorization_servers[0]; ⛔ no widening of what the plain-HTTP rule admits)
File surface: the auth host's OAuth provider configuration and the two OAuth metadata routes (packages/plugins/plugin-auth/** or wherever the issuer is configured, found by symbol), their tests, and a changeset on every package the diff publishes from. ⛔ No packages/spec/**, ⛔ no node_modules patch or library fork (stop as needs_decision instead), ⛔ no change to which origins get OAuth
Container & model: M, mode:thread, default model and effort (set at thread open)
Clause-②: no (the dev declares the measured arm if a published contract line moves)
Metadata-protocol: none
Responsibility: the auth host's issuer configuration (plugin-auth), against RFC 8414 §3.3
Thread-read: 6434147012
Premise: holds (read 2026-10-27T05:43Z at origin/main45de8c6cba: card open, unassigned, pm:queue with its triage line; the split with server seat 1 gives this card to seat 2); the dev re-measures it first, and a refutation is a premise_still_valid: false hand-back
Resume-from: none — fresh from origin/main
Serial constraints cleared: yes — the dev re-measures open PRs on the file surface before the first edit and before pr_create
State: pm:queue → pm:dispatched, assigned to os-project-manager, in this act.
os-dev-report
{
"issue": 26472,
"status": "needs_decision",
"branch": "claude/project-thread-cdz4ci",
"pr": null,
"session": "session_01FEkQ5p7SSrVe6yoRJb2o6k",
"premise_still_valid": true,
"summary": "Premise holds and is wider than the card says. Measured on a REAL better-auth 1.7.12 + @better-auth/oauth-provider 1.7.12 authorization server (memory adapter, jwt plugin, basePath /api/v1/auth) at origin/main 45de8c6, private-host boot http://10.20.30.40:41566: AS metadata issuer https://10.20.30.40:41566/api/v1/auth while token_endpoint is http://; openid-configuration issuer (root call AND the in-basePath /api/v1/auth/.well-known/openid-configuration) https://; RFC 9207 iss parameter on the authorization-code redirect https://; the minted ID token's iss claim http://10.20.30.40:41566/api/v1/auth. So the issued tokens' iss ALREADY differs from the published issuer today (the order asked to say so). Controls: http://localhost:41566 and https://acme.example.com publish one issuer everywhere (metadata, iss param, ID token all equal). Mechanism confirmed by reading the installed source: validateIssuerUrl() (oauth-provider dist/authorize-D_jy3s-1.mjs:5503) sets url.protocol to https whenever the host is not isLoopbackHost (localhost, 127/8, ::1, *.localhost) and also strips a trailing slash; it is applied UNCONDITIONALLY to the metadata issuer (line 679, even an explicit jwt.issuer goes through it) and to getIssuer() (line 5517), which stamps the RFC 9207 iss parameter (line 5898, and every error redirect via formatErrorURL) and is the comparand for the logout id_token_hint (line 537) and client-assertion audience (line 1555); token iss claims use the RAW baseURL (better-auth dist/plugins/jwt/sign.mjs:86, oauth-provider line 314). Unchanged in 1.7.14 (latest) and 1.8.0-beta.3 (read from the packed tarballs). No supported configuration moves it: jwt.issuer is validated too, and provider extensions are first-wins and cannot override a core key (utils-BfpTo7XY.mjs:198). A pnpm patch is not a route either: plugin-auth's tsup keeps better-auth external and patchedDependencies applies only to this workspace's install, so a patch never reaches an npm-installed deployment. The host-side override (PM hypothesis 2) does satisfy the ruled acceptance literally, but it splits the library's issuer surfaces a new way: metadata issuer would become http while the RFC 9207 iss parameter stays https, and the metadata advertises authorization_response_iss_parameter_supported: true, so a strict client (oauth4webapi / openid-client) moves from failing at discovery to failing at the authorization response. That trade is outside the rulings, so no code was written and no PR opened; decision below.",
"tests": "No change, so no suite and no ablation. Reading: a one-off vitest probe (not committed) booting the real AS three times (private / loopback / https) and driving DCR (native client) then sign-up then authorize then consent then token, printing every issuer surface. Output verbatim: /mnt/project-files/pm-orders/server/evidence/26472/probe-output.txt; probe source alongside (zz-probe-26472.test.ts). Private row: as_issuer https://10.20.30.40:41566/api/v1/auth, as_token_endpoint http://10.20.30.40:41566/api/v1/auth/oauth2/token, oidc_issuer https://..., inbase_oidc_issuer https://..., redirect_iss_param https://..., id_iss http://10.20.30.40:41566/api/v1/auth. Loopback and https rows: every value equal to the origin's own scheme. Build closure: os-verify-lock VERDICT command-exit 0 (pnpm --filter '@objectstack/plugin-auth^...' build). Probe file removed after the read; git diff HEAD empty, git status clean.",
"gates": "None run: empty diff (git diff HEAD empty at 45de8c6), so dispatch-gates --commands would derive nothing and there is no PR for CI to run.",
"dogfood_battery": "n/a - no PR",
"mcp_calls": "0 - all GitHub reads went through gh api REST (card, comments, claim, open-PR file lists); no MCP tool called",
"api_writes": "1 - POST /repos//issues/26472/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). git push 0, pr_create 0.",
"open_questions": [
{
"question": "The library publishes https on a private plain-HTTP host in the metadata issuer AND in the RFC 9207 iss parameter, while its tokens carry http. No supported configuration moves either, and a pnpm patch does not ship to npm consumers. Which repair does the platform take?",
"status": "needs_decision",
"options": [
"A - host overrides the published issuer: plugin-auth rewrites issuer to getAuthIssuer() in both metadata documents (the root /.well-known routes auth-plugin.ts mounts, and the in-basePath documents better-auth serves through the catch-all). Meets the ruled acceptance byte for byte (metadata issuer == authorization_servers[0] == token iss on private hosts; loopback and https unchanged because the value is already equal there). Leaves the RFC 9207 iss parameter at https, so metadata and iss parameter newly disagree.",
"B - A plus a host rewrite of the RFC 9207 iss parameter on every provider redirect (authorize, consent, continue, and every error redirect), e.g. a better-auth after-hook rewriting the Location / JSON redirect_uri. Every externally visible issuer then agrees; the provider's internal comparisons (logout id_token_hint at line 537, client-assertion audience at line 1555) stay on the https spelling.",
"C - no host override: the defect is in validateIssuerUrl (it rewrites an operator-configured issuer and disagrees with the library's own token iss); carry it upstream to better-auth (keep the configured scheme, or honour private/link-local http the way the platform's transport rule does), park this card Blocked-by that, and bump when released. Filing upstream is an external write, so it needs the maintainer's go."
],
"four_axes": {
"real_business_need": "Measured: the private-host OAuth track's main consumer, the MCP TypeScript SDK (1.32.1, client/auth.js:260), deliberately does not read metadata.issuer and has no iss-parameter check, so MCP clients complete today under every option. The clients the card describes are strict OAuth/OIDC libraries (oauth4webapi, openid-client): they fail today at discovery (RFC 8414 3.3); under A they still fail an authorization-code flow, one step later, at the RFC 9207 check (only client_credentials and ID-token iss validation get fixed); under B and C they complete. No named deployment runs a strict client against a private-host issuer; the card came from the QA RFC sweep (#26468), not a user report.",
"long_term_soundness": "C repairs the one producer that is wrong and leaves one source for the issuer (better-auth's baseURL, which getAuthIssuer() already mirrors). A and B are host rewrites of a vendor's output (Prime Directive 5: no workarounds); each needs a removal step once upstream changes, and B grows into every redirect path the provider adds.",
"ai_error_resistance": "A/B add an override layer that silently diverges when the provider changes its document or redirect shape (a new redirect path under B is a new unrewritten iss); C adds no local surface. A also publishes metadata that sends a strict client into a later failure, which reads as working OAuth while it is not.",
"startup_focus": "C costs no local code and no new gate; A is small (one override, two document families, tests); B is the largest and is speculative capability for strict clients nobody has named."
},
"recommendation": "C, because the defect is the vendor rewriting an issuer the operator configured, and the platform's main consumer of this track (MCP clients) is unaffected while strict clients would still fail under A. If the maintainer wants the RFC 8414 3.3 clause green before upstream moves, take A as a stated partial (Part of, not Fixes), knowing code-flow strict clients still fail at RFC 9207; B only on a named strict-client deployment."
}
],
"out_of_scope_findings": [
"class: a · reach: same root cause as this card (no separate public door measured) · the provider compares a logout id_token_hint's iss (raw http) to getIssuer() (https) at oauth-provider authorize-D_jy3s-1.mjs:537, so on a private plain-HTTP host the hint never matches and is ignored · carrier: #26472 (same validateIssuerUrl root; fixed by option C, not by A or B) · dedupe words: id_token_hint, end-session, issuer scheme, private host",
"class: a · reach: same root · validateIssuerUrl also strips a trailing slash, so a configured basePath with a trailing slash publishes a metadata issuer without it while tokens and authorization_servers[0] (getAuthIssuer, which deliberately keeps it) carry it - read from source, not booted · carrier: #26472 · dedupe words: trailing slash, basePath, issuer, RFC 8414"
]
}
The card thread's report (6434887020, status: needs_decision) measured that the enforce fix is not reachable inside this repository alone. @better-auth/oauth-provider's validateIssuerUrl() rewrites the metadata issuer and the RFC 9207 iss parameter to https on any non-loopback host, with no option to turn that off. This holds in 1.7.12, 1.7.14 and 1.8.0-beta.3. A pnpm patch would not reach npm consumers. No branch work was pushed; branch claude/project-thread-cdz4ci is untouched at 45de8c6c.
The seat put the choice to the maintainer:
A: the host overrides the metadata issuer. This is a partial fix; the iss parameter stays https.
B: A, plus rewriting iss on every authorization redirect.
C: file the defect upstream at better-auth and hold this card until a release carries the fix.
C + A: file upstream, and land A now as a Part of partial.
The seat recommends C. Filing upstream is an external write, so it needs the maintainer's word.
Maintainer-action: pick A, B, C or C + A for #26472 (and, for C, approve filing the issuer defect at better-auth) — done when the seat posts a comment on this card quoting the maintainer's choice verbatim
State: pm:dispatched → pm:awaiting-maintainer, assignee released. The card thread is released. The seat re-claims with an order matching the chosen option.
Ruled: C. The maintainer, verbatim, in the coordinator session on 2026-11-07: 「C」. The question put to them was option A, B, C or C + A, with the seat recommending C.
What C means, per the seat's 2026-10-27 07:10Z comment:
File the issuer defect upstream at better-auth. This answer is the maintainer's approval for that external write. The filing should state what the card thread measured on 1.7.12, 1.7.14 and 1.8.0-beta.3:
validateIssuerUrl() rewrites a non-loopback http:// issuer to https:// on the metadata issuer and on the RFC 9207 iss parameter;
the ID token's iss keeps the raw http:// base URL, so the published issuer and the tokens' iss already disagree;
no supported option turns the rewrite off.
Hold this card until a better-auth release carries the fix. Then bump and verify against the acceptance on this card. Neither A nor B lands here, and no pnpm patch.
State: pm:awaiting-maintainer → pm:queue, so the domain:server lane re-claims with an order matching C: file upstream, link it here, then park this card on the upstream issue.
State:pm:queue → pm:on-hold. Neither A nor B lands here, and no pnpm patch (the ruling).
Restart-when: a published @better-auth/oauth-provider release keeps a configured non-loopback http:// issuer in its metadata and RFC 9207 iss (read the release that closes better-auth/better-auth#11740), then bump and verify against this card's acceptance
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
bug·priority:p2·domain:server·area:identity·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T04:59Z⛔ Not a claim, ⛔ not a dispatch.
https://issuer while every endpoint andauthorization_servers[0]arehttp://(RFC 8414 §3.3). Derive the issuer from the same base URL the endpoints use. ⛔ Do not widen what the plain-HTTP rule admits; only make the published issuer agree with the endpoints. Found by the 18.2.0 wave-2 sweep ([sweep] v18.2.0 release test sweep #25807).Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 4 · card thread · 18.2.0 wave-2 split with server seat 1
Session:
session_0186vfjHHMsRXC9EUT9hTFMo(the seat); the card thread issession_01FEkQ5p7SSrVe6yoRJb2o6kAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-cdz4ci(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: triage
6434147012(make the publishedissueragree with the endpoints andauthorization_servers[0]; ⛔ no widening of what the plain-HTTP rule admits)File surface: the auth host's OAuth provider configuration and the two OAuth metadata routes (
packages/plugins/plugin-auth/**or wherever the issuer is configured, found by symbol), their tests, and a changeset on every package the diff publishes from. ⛔ Nopackages/spec/**, ⛔ nonode_modulespatch or library fork (stop asneeds_decisioninstead), ⛔ no change to which origins get OAuthContainer & model:
M,mode:thread, default model and effort (set at thread open)Clause-②: no (the dev declares the measured arm if a published contract line moves)
Metadata-protocol: none
Responsibility: the auth host's issuer configuration (
plugin-auth), against RFC 8414 §3.3Thread-read: 6434147012
Premise: holds (read 2026-10-27T05:43Z at
origin/main45de8c6cba: card open, unassigned,pm:queuewith its triage line; the split with server seat 1 gives this card to seat 2); the dev re-measures it first, and a refutation is apremise_still_valid: falsehand-backResume-from: none — fresh from
origin/mainSerial constraints cleared: yes — the dev re-measures open PRs on the file surface before the first edit and before
pr_createState:
pm:queue→pm:dispatched, assigned toos-project-manager, in this act.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{
"issue": 26472,
"status": "needs_decision",
"branch": "claude/project-thread-cdz4ci",
"pr": null,
"session": "session_01FEkQ5p7SSrVe6yoRJb2o6k",
"premise_still_valid": true,
"summary": "Premise holds and is wider than the card says. Measured on a REAL better-auth 1.7.12 + @better-auth/oauth-provider 1.7.12 authorization server (memory adapter, jwt plugin, basePath /api/v1/auth) at origin/main 45de8c6, private-host boot http://10.20.30.40:41566: AS metadata issuer https://10.20.30.40:41566/api/v1/auth while token_endpoint is http://; openid-configuration issuer (root call AND the in-basePath /api/v1/auth/.well-known/openid-configuration) https://; RFC 9207 iss parameter on the authorization-code redirect https://; the minted ID token's iss claim http://10.20.30.40:41566/api/v1/auth. So the issued tokens' iss ALREADY differs from the published issuer today (the order asked to say so). Controls: http://localhost:41566 and https://acme.example.com publish one issuer everywhere (metadata, iss param, ID token all equal). Mechanism confirmed by reading the installed source: validateIssuerUrl() (oauth-provider dist/authorize-D_jy3s-1.mjs:5503) sets url.protocol to https whenever the host is not isLoopbackHost (localhost, 127/8, ::1, *.localhost) and also strips a trailing slash; it is applied UNCONDITIONALLY to the metadata issuer (line 679, even an explicit jwt.issuer goes through it) and to getIssuer() (line 5517), which stamps the RFC 9207 iss parameter (line 5898, and every error redirect via formatErrorURL) and is the comparand for the logout id_token_hint (line 537) and client-assertion audience (line 1555); token iss claims use the RAW baseURL (better-auth dist/plugins/jwt/sign.mjs:86, oauth-provider line 314). Unchanged in 1.7.14 (latest) and 1.8.0-beta.3 (read from the packed tarballs). No supported configuration moves it: jwt.issuer is validated too, and provider extensions are first-wins and cannot override a core key (utils-BfpTo7XY.mjs:198). A pnpm patch is not a route either: plugin-auth's tsup keeps better-auth external and patchedDependencies applies only to this workspace's install, so a patch never reaches an npm-installed deployment. The host-side override (PM hypothesis 2) does satisfy the ruled acceptance literally, but it splits the library's issuer surfaces a new way: metadata issuer would become http while the RFC 9207 iss parameter stays https, and the metadata advertises authorization_response_iss_parameter_supported: true, so a strict client (oauth4webapi / openid-client) moves from failing at discovery to failing at the authorization response. That trade is outside the rulings, so no code was written and no PR opened; decision below.",
"tests": "No change, so no suite and no ablation. Reading: a one-off vitest probe (not committed) booting the real AS three times (private / loopback / https) and driving DCR (native client) then sign-up then authorize then consent then token, printing every issuer surface. Output verbatim: /mnt/project-files/pm-orders/server/evidence/26472/probe-output.txt; probe source alongside (zz-probe-26472.test.ts). Private row: as_issuer https://10.20.30.40:41566/api/v1/auth, as_token_endpoint http://10.20.30.40:41566/api/v1/auth/oauth2/token, oidc_issuer https://..., inbase_oidc_issuer https://..., redirect_iss_param https://..., id_iss http://10.20.30.40:41566/api/v1/auth. Loopback and https rows: every value equal to the origin's own scheme. Build closure: os-verify-lock VERDICT command-exit 0 (pnpm --filter '@objectstack/plugin-auth^...' build). Probe file removed after the read; git diff HEAD empty, git status clean.",
"gates": "None run: empty diff (git diff HEAD empty at 45de8c6), so dispatch-gates --commands would derive nothing and there is no PR for CI to run.",
"dogfood_battery": "n/a - no PR",
"mcp_calls": "0 - all GitHub reads went through gh api REST (card, comments, claim, open-PR file lists); no MCP tool called",
"api_writes": "1 - POST /repos//issues/26472/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). git push 0, pr_create 0.",
"open_questions": [
{
"question": "The library publishes https on a private plain-HTTP host in the metadata issuer AND in the RFC 9207 iss parameter, while its tokens carry http. No supported configuration moves either, and a pnpm patch does not ship to npm consumers. Which repair does the platform take?",
"status": "needs_decision",
"options": [
"A - host overrides the published issuer: plugin-auth rewrites issuer to getAuthIssuer() in both metadata documents (the root /.well-known routes auth-plugin.ts mounts, and the in-basePath documents better-auth serves through the catch-all). Meets the ruled acceptance byte for byte (metadata issuer == authorization_servers[0] == token iss on private hosts; loopback and https unchanged because the value is already equal there). Leaves the RFC 9207 iss parameter at https, so metadata and iss parameter newly disagree.",
"B - A plus a host rewrite of the RFC 9207 iss parameter on every provider redirect (authorize, consent, continue, and every error redirect), e.g. a better-auth after-hook rewriting the Location / JSON redirect_uri. Every externally visible issuer then agrees; the provider's internal comparisons (logout id_token_hint at line 537, client-assertion audience at line 1555) stay on the https spelling.",
"C - no host override: the defect is in validateIssuerUrl (it rewrites an operator-configured issuer and disagrees with the library's own token iss); carry it upstream to better-auth (keep the configured scheme, or honour private/link-local http the way the platform's transport rule does), park this card Blocked-by that, and bump when released. Filing upstream is an external write, so it needs the maintainer's go."
],
"four_axes": {
"real_business_need": "Measured: the private-host OAuth track's main consumer, the MCP TypeScript SDK (1.32.1, client/auth.js:260), deliberately does not read metadata.issuer and has no iss-parameter check, so MCP clients complete today under every option. The clients the card describes are strict OAuth/OIDC libraries (oauth4webapi, openid-client): they fail today at discovery (RFC 8414 3.3); under A they still fail an authorization-code flow, one step later, at the RFC 9207 check (only client_credentials and ID-token iss validation get fixed); under B and C they complete. No named deployment runs a strict client against a private-host issuer; the card came from the QA RFC sweep (#26468), not a user report.",
"long_term_soundness": "C repairs the one producer that is wrong and leaves one source for the issuer (better-auth's baseURL, which getAuthIssuer() already mirrors). A and B are host rewrites of a vendor's output (Prime Directive 5: no workarounds); each needs a removal step once upstream changes, and B grows into every redirect path the provider adds.",
"ai_error_resistance": "A/B add an override layer that silently diverges when the provider changes its document or redirect shape (a new redirect path under B is a new unrewritten iss); C adds no local surface. A also publishes metadata that sends a strict client into a later failure, which reads as working OAuth while it is not.",
"startup_focus": "C costs no local code and no new gate; A is small (one override, two document families, tests); B is the largest and is speculative capability for strict clients nobody has named."
},
"recommendation": "C, because the defect is the vendor rewriting an issuer the operator configured, and the platform's main consumer of this track (MCP clients) is unaffected while strict clients would still fail under A. If the maintainer wants the RFC 8414 3.3 clause green before upstream moves, take A as a stated partial (Part of, not Fixes), knowing code-flow strict clients still fail at RFC 9207; B only on a named strict-client deployment."
}
],
"out_of_scope_findings": [
"class: a · reach: same root cause as this card (no separate public door measured) · the provider compares a logout id_token_hint's iss (raw http) to getIssuer() (https) at oauth-provider authorize-D_jy3s-1.mjs:537, so on a private plain-HTTP host the hint never matches and is ignored · carrier: #26472 (same validateIssuerUrl root; fixed by option C, not by A or B) · dedupe words: id_token_hint, end-session, issuer scheme, private host",
"class: a · reach: same root · validateIssuerUrl also strips a trailing slash, so a configured basePath with a trailing slash publishes a metadata issuer without it while tokens and authorization_servers[0] (getAuthIssuer, which deliberately keeps it) carry it - read from source, not booted · carrier: #26472 · dedupe words: trailing slash, basePath, issuer, RFC 8414"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Awaiting the maintainer
2026-10-27T07:10Z ·
domain:serverseat 2 ·os-project-manager· sessionsession_0186vfjHHMsRXC9EUT9hTFMoThe card thread's report (
6434887020,status: needs_decision) measured that the enforce fix is not reachable inside this repository alone.@better-auth/oauth-provider'svalidateIssuerUrl()rewrites the metadata issuer and the RFC 9207issparameter tohttpson any non-loopback host, with no option to turn that off. This holds in 1.7.12, 1.7.14 and 1.8.0-beta.3. A pnpm patch would not reach npm consumers. No branch work was pushed; branchclaude/project-thread-cdz4ciis untouched at45de8c6c.The seat put the choice to the maintainer:
issparameter stayshttps.isson every authorization redirect.Part ofpartial.The seat recommends C. Filing upstream is an external write, so it needs the maintainer's word.
Maintainer-action: pick A, B, C or C + A for #26472 (and, for C, approve filing the issuer defect at better-auth) — done when the seat posts a comment on this card quoting the maintainer's choice verbatim
State:
pm:dispatched→pm:awaiting-maintainer, assignee released. The card thread is released. The seat re-claims with an order matching the chosen option.Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
coordinator#1 · hotlong · session_6eb9ec2e0e3f410b91239dab7a337b9e
2026-11-06T23:53Z
Ruled: C. The maintainer, verbatim, in the coordinator session on 2026-11-07: 「C」. The question put to them was option A, B, C or C + A, with the seat recommending C.
What C means, per the seat's 2026-10-27 07:10Z comment:
validateIssuerUrl()rewrites a non-loopbackhttp://issuer tohttps://on the metadata issuer and on the RFC 9207issparameter;isskeeps the rawhttp://base URL, so the published issuer and the tokens'issalready disagree;State:
pm:awaiting-maintainer→pm:queue, so thedomain:serverlane re-claims with an order matching C: file upstream, link it here, then park this card on the upstream issue.🤖 Generated with Claude Code
objectstack-fleet commentedon Nov 7, 2026
domain:server#3 · marchtian · session_017ihL2dFb2QdZEHEJ83cR2B
Ruling C executed: upstream link, card parked · 2026-11-07T13:10Z
Executing the maintainer's 「C」 (recorded 6698997586): file upstream, link it here, park this card on the upstream issue.
issparameter are rewritten tohttps://on a non-loopback host, the ID tokenisskeeps the configuredhttp://, and nothing reports the rewrite. Found byrepo:better-auth/better-auth is:issue validateIssuerUrl→ 2 hits; the other, oauth-provider: no opt-out for HTTPS-only redirect URIs breaks self-hosted HTTP deployments better-auth/better-auth#11277, is the sibling HTTPS-only redirect-URI refusal, not this defect. Filing a second issue would be a duplicate, so this card links docs: threevisibleWhenbinding claims are now wrong in the other direction — objectui boundcurrent_useron form section/field predicates (objectui#6010) #11740 instead. If the maintainer still wants our own measurements added upstream, that is a comment on docs: threevisibleWhenbinding claims are now wrong in the other direction — objectui boundcurrent_useron form section/field predicates (objectui#6010) #11740 by a person, since the fleet has no write path to that repository.pm:queue→pm:on-hold. Neither A nor B lands here, and no pnpm patch (the ruling).Restart-when: a published
@better-auth/oauth-providerrelease keeps a configured non-loopbackhttp://issuer in its metadata and RFC 9207iss(read the release that closes better-auth/better-auth#11740), then bump and verify against this card's acceptance