Skip to content

finding: add-member was not the only one — 6 of 17 ObjectStack auth mounts are undocumented and 9 of 17 are in no ledger, and nothing catches a mount that lands without either #10534

Description

@os-zhuang

#10050 asked whether POST /organization/add-member being mounted-but-undocumented was a one-off. It was not. Census below, measured on origin/main at 4544be260, with positive controls on both legs.

Population

auth-plugin.ts mounts routes directly on the raw Hono app, ahead of the better-auth catch-all. Those mounts are the ObjectStack-owned auth surface — the catch-all never sees them, so the vendor's own route table cannot account for them.

Enumerated by matching rawApp.<verb>(\${basePath}/…`)inpackages/plugins/plugin-auth/src/auth-plugin.ts(excluding therawApp.allcatch-all and the.well-known/*mounts, which are not underbasePath`): 17 routes.

Leg 1 — documented?

Test: does the literal wire path /api/v1/auth<path> appear anywhere in the hand-written docs corpus (392 .md/.mdx files under content/docs, content/docs/releases/** excluded as release-owned)?

6 of 17 undocumented, before this card's PR:

RouteIn a ledger?
POST /api/v1/auth/admin/sso/request-domain-verificationno
POST /api/v1/auth/admin/sso/verify-domainno
POST /api/v1/auth/admin/unban-uservendor inventory only
GET /api/v1/auth/bootstrap-statusyes — objectstack / sdk
POST /api/v1/auth/organization/add-memberyes — objectstack / server-only
POST /api/v1/auth/set-initial-passwordno

#10050's PR documents the last-but-one, leaving 5 of 17.

⚠️ Two refinements the raw number hides. set-initial-password is named once in prose (authentication.mdx, "offers set-initial-password") without its URL — named, not documented. And add-member was named three times in tenancy-modes.mdx with no URL anywhere, which is how it stayed invisible while looking discussed.

Positive control (leg 1): the same expression, run before the docs were written, returned organization/add-member — the known-present member of the set. So a zero would have been a real zero. Control output: POSITIVE CONTROL organization/add-member present in undocumented set: true.

Leg 2 — ledgered?

auth-route-ledger.ts has two halves: the reviewed AUTH_ROUTE_LEDGER rows and the machine-maintained BETTER_AUTH_MOUNTED_SURFACE inventory. A route in neither is unaccounted for.

9 of 17 appear in neither half:

/admin/import-users · /admin/oauth2/toggle-disabled · /admin/sso/register · /admin/sso/register-saml · /admin/sso/request-domain-verification · /admin/sso/verify-domain · /admin/unlock-user · /set-initial-password · /sys-oauth-application/register

Only 3 of the 17 carry a reviewed source: 'objectstack' row: /config, /bootstrap-status, /organization/add-member. The rest are either attributed to the vendor (correct where the vendor also declares the path — the ObjectStack mount shadows it) or absent entirely.

Verified two ways that agree: a regex parse of the ledger source, and a direct grep "auth<path>'" per route. Positive control (leg 2): the same grep returns 1 for /organization/add-member and 1 for /config, so an absence is an absence and not a broken pattern.

Leg 3 — why nothing caught it

Both of #10050's asks land on the same hole, and it is upstream of either ledger row.

scripts/docs-audit/affected-docs.mjs --bridge-coverage on this tree:

 client-bound ledger rows ... 221
reachable ................ 45
UNREACHABLE .............. 176
55 of 55 unreachable packages/plugins/plugin-auth/src/auth-route-ledger.ts

Every client-bound row in the auth ledger is unreachable by the sdk doc-nomination bridge. The bridge finds route registrars by filename convention — REGISTRAR_FILE_RE = /(?:^|\/)(?:[\w.-]*route[\w.-]*|[\w.-]*-server)\.ts$/ — and the file that actually registers this surface is auth-plugin.ts, which matches neither alternative. No registrar tail is produced for the auth surface, so no auth ledger row can be selected, so the sdk anchor never nominates a doc for an auth route.

add-member is then invisible a second time on top of that: its row is disposition: 'server-only' with no client, so it is not even among the 55 client-bound rows the bridge fails to reach.

The 221→45 shape is already recorded in #9572 (closed). What is new here is the auth slice being 55 of 55 and the concrete cause being one filename convention — plus the demonstration that this is what let a route mount and its documentation gap ship separately with nothing objecting.

⛔ Deliberately not acted on: scripts/docs-audit/affected-docs.mjs is being edited by open PR #10501, which is fixing a different silent-parse defect in the same file. Any remedy here belongs after that lands.

Suggested follow-ups

  1. Document the 5 remaining undocumented mounts (or record a deliberate "server-only, not user-facing" verdict for each — bootstrap-status may well be one).
  2. Ledger the 9 unledgered mounts as source: 'objectstack' rows, which is what the ledger's own header says objectstack mounts are for.
  3. Decide whether the sdk bridge should reach the auth surface at all — either by teaching REGISTRAR_FILE_RE about auth-plugin.ts, or by declaring registrars explicitly rather than by filename convention. After fix(docs-audit): a PARTIAL ledger read is a verdict, not a smaller number #10501.
  4. Consider a gate: a rawApp mount under basePath with no ledger row is exactly the state that produced [finding] sys_member "Add Member" action targets POST /organization/add-member, which better-auth 1.7.0-rc.2 never mounts (server-only) — on multi-org there is NO remaining UI path to attach an existing user to an org #9941 and docs: the newly mounted POST /organization/add-member is undocumented — and it is the ONLY multi-org path to attach an existing user #10050, and it is mechanically detectable.

Refs

#10050 (the card this sweep came from) · #9941 / PR #10049 (the mount) · #9572 (the 221→45 bridge measurement, closed) · #10501 (open, owns affected-docs.mjs) · #9969 / #9652 (adjacent: vendor /admin/ routes and their admit sets)

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions