Repository navigation
security: the CORS doors #24738 does not reach keep the old default (@objectstack/hono's createHonoApp, loopback beside a configured list, all-host patterns) — family closure, detail withheld under the disclosure ruling #24809
Description
Filing gate ①: a defect with named loci and a measured reading. Filed by domain:cli seat 2 (#22648) · os-justin · session session_01LmjU2x1faLZ39DVTFyVca6, from the out_of_scope_findings of #24738's dev report (6285567675, PR #24806). ⛔ Not a claim.
Disclosure: this is an access-control finding. Under the maintainer's ruling on #9387 (5323310017), this card records only the defect's existence, where it lives, and the governing text. No reproduction or exploit path is published.
Reader (who acts): the domain:cli seat. packages/adapters/* and plugin-hono-server are both domain:cli in the lane table. Triage owns the grade.
The family, and why this is its closure card
#24738 (in flight, PR #24806) narrows the CORS default of HonoServerPlugin, the server os serve / os dev / os start mount. This card is the same family's closure card: one card for every remaining location, with an enumeration pin. #24738 is in flight, so nothing is merged into it.
Locations (read on origin/main 46bb5e3e5; PR #24806's head f7cb52d15 for the third)
@objectstack/hono'screateHonoApp(packages/adapters/hono/src/index.ts, its CORS block, about:607–:690). With no origin list configured, its default is the same combination security: the HTTP server's default CORS answer admits every origin for credentialed requests (detail withheld under the disclosure ruling) #24738 removes fromHonoServerPlugin. Measured by security: the HTTP server's default CORS answer admits every origin for credentialed requests (detail withheld under the disclosure ruling) #24738's dev on PR fix(plugin-hono-server)!: the CORS default admits no cross-origin caller, and '*' with credentials is refused at boot #24806's tree: the twohttp-conformancedoor-parity pins (hono-approvals-act,hono-webhooks-redeliver) went red. The hosted door still answered the old default whereHonoServerPluginnow answers none. That PR moved those pins tocors: false.- Loopback beside a configured list (
packages/plugins/plugin-hono-server/src/pattern-matcher.ts,matchOriginPattern/createOriginMatcher). A configured origin list also admits loopback origins in every mode, production included. security: the HTTP server's default CORS answer admits every origin for credentialed requests (detail withheld under the disclosure ruling) #24738 scoped "localhost in dev only" to the no-list default and left the matcher unchanged. The matcher is also imported by the cloud's serverless preflight short-circuit, which is a cross-repo consumer and NOT MEASURED here. - The boot refusal covers a literal
*only. PR fix(plugin-hono-server)!: the CORS default admits no cross-origin caller, and '*' with credentials is refused at boot #24806 refuses'*'with credentials at boot. A pattern whose host part is a bare wildcard is not refused.
Governing text
docs/HARDENING.md§ CORS: "Do not useorigin: '*'withcredentials: true", and the go-live checklist's "CORS origin list is explicit, not*".- security: the HTTP server's default CORS answer admits every origin for credentialed requests (detail withheld under the disclosure ruling) #24738's Expected: "Localhost stays admitted in dev only, if at all."
- AGENTS.md, "one operation, one implementation": two doors that mount the same CORS policy answer the same configuration differently today.
Expected (proposed; triage owns it)
- One CORS admission rule, shared by every door that mounts CORS (
HonoServerPlugin,createHonoApp), so the same configuration gets the same answer.createHonoAppalready imports the matcher and the header defaults fromplugin-hono-server. - Loopback is admitted on a development boot only, beside a list too, unless the maintainer rules an exception.
- An all-host pattern with credentials gets the same boot refusal as
*. - Enumeration pin: one test that lists every door in this repo that mounts CORS and asserts the same answers for the same configurations, so a new door cannot ship the old default.
Release and order
- security: the HTTP server's default CORS answer admits every origin for credentialed requests (detail withheld under the disclosure ruling) #24738 is ruled into 18.0 (the maintainer's word
6284880389; triage6285370999). Whether this closure also enters 18.0 is the maintainer's call, and it is put to them with this filing. - Blocked-by: security: the HTTP server's default CORS answer admits every origin for credentialed requests (detail withheld under the disclosure ruling) #24738. This card shares the rule and the matcher that PR fix(plugin-hono-server)!: the CORS default admits no cross-origin caller, and '*' with credentials is refused at boot #24806 changes, so it starts after that PR lands.
Dedupe
Semantic issue search on objectstack-ai/objectstack, closed included:
- "hono adapter createHonoApp CORS default origin credentials localhost admitted": 4 hits. security: the HTTP server's default CORS answer admits every origin for credentialed requests (detail withheld under the disclosure ruling) #24738 is the parent. hono adapter:
createHonoApp's catch-all renders an error thrown out ofdispatch()fromstatusCodealone, so a declaredstatus/code(a missing record's404 RECORD_NOT_FOUND, an outage's503) answers500 INTERNAL_ERROR#23365, hono adapter:createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a@objectstack/honohost #23214 andcreateHonoApp's defaultprefix: '/api'does not compose with AuthPlugin's defaultbasePath: '/api/v1/auth'— the documented embed answers200 {}to sign-in #16025 are closed and are othercreateHonoAppdefects. - "CORS origin pattern matcher loopback allowed in production wildcard star credentials": 1 hit, security: the HTTP server's default CORS answer admits every origin for credentialed requests (detail withheld under the disclosure ruling) #24738.
None names these locations.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 20, 2026
Location 4 ·
domain:cliseat 2 (#22648) ·os-justin· sessionsession_01LmjU2x1faLZ39DVTFyVca6· 2026-10-20T10:10Z. ⛔ Not a claim. Under the disclosure ruling (#93875323310017), this records existence, location and governing text only.PR #24806's second review of record (
6288540922, ③ new flag 1) names one more location of this family:*entry among other origins reaches auth's trusted set (packages/plugins/plugin-auth/src/auth-manager.ts, thetrustedOriginsblock, about:3011–:3015onmain). The block tests the wholeOS_CORS_ORIGINvalue against*and splits it afterwards. So a list that holds*beside real origins, booted with credentials off (the only configuration the transport now admits with*), puts the*entry into better-auth's trusted origins. Governing text:docs/HARDENING.md§ CORS and § CSRF. It is pre-existing and was not moved by PR fix(plugin-hono-server)!: the CORS default admits no cross-origin caller, and '*' with credentials is refused at boot #24806.The enumeration pin this card asks for covers it too: the same configuration gets the same answer at every door that reads
OS_CORS_ORIGIN, auth's trusted-origin merge included. The file ispackages/plugins/plugin-auth, which the lane table places indomain:services. So the card's claimant either takes this location through the cross-domain path or hands it to that lane, and says which in the claim.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 9 ·
domain:cliseat 2 · p1 ·security(the CORS family's closure card, filed by this seat;Blocked-by: #24738cleared, since #24738 landed asf7546a4de) · 2026-10-20T10:51ZSession:
session_01LmjU2x1faLZ39DVTFyVca6Account:
os-justin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24809-cors-family-closureWorktree:
objectstack-issue-24809Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)Disclosure: under the maintainer's ruling on #9387 (
5323310017), this claim, the PR and every comment state the defaults, their locations and the governing text only. Tests that pin the refusals are the fix's evidence, and they stay.File surface (read on
origin/mainf7546a4de, which carries PR #24806; line numbers move; stop on a breach and explain it in the report):packages/adapters/hono/src/index.ts,createHonoApp's CORS block (about:607–:690). With no list it still defaults to'*'with credentials on and reflects the request origin. It takes the same ruleHonoServerPluginnow has, through one shared rule inplugin-hono-server(which the adapter already imports from), not a copy.packages/plugins/plugin-hono-server/src/pattern-matcher.ts,matchOriginPattern(about:47–:49), which admits every loopback origin ahead of any configured list, in every mode. Loopback is admitted on a development boot only, beside a list too, unless the dev measures a consumer this breaks; then they stop and report it.'*'with credentials (hono-plugin.ts, and the adapter's equivalent) also covers a pattern whose host part matches every host.packages/plugins/plugin-auth/src/auth-manager.ts, thetrustedOriginsblock (about:3011–:3015). It tests the wholeOS_CORS_ORIGINvalue against'*'and splits afterwards, so a'*'entry beside real origins enters better-auth's trusted set. Each entry is judged, not the whole string. This file isdomain:services' (lane table). This seat follows its own card across under a cross-lane declaration on [PM seat] domain:services — ⏳ vacant #6021 in this act; it does not take that lane's other work.OS_CORS_ORIGIN/ mounts CORS (HonoServerPlugin,createHonoApp, auth's trusted-origin merge) and asserts the same answer for the same configuration.docs/HARDENING.md§ CORS (itscreateHonoAppparagraph and the loopback row) and the two packages' READMEs.packages/spec/**(CorsConfigSchema);content/docs/deployment/cli.mdx(its "(CORS)" line, still held for whichever PR lands second); better-auth's other trusted-origin sources (OS_TRUSTED_ORIGINS, preview and root-domain wildcards, SSO discovery);HARDENING.md's other sections (docs: HARDENING.md and production-readiness.mdx still describe rate limiting as adapter-wired and off, and never name server.security.rateLimit or trustProxy #24740's).Container & model:
M,mode:subagent,model: default tierfor the build (dispatch-gates.mjs --tier: "no path-derived mandate"). The narrowing face owesCONTRACT_REVIEW_TIER(reason: it narrows shipped admission defaults at three doors;contract-review.md).Clause-②: no (narrowing)
Basis for the Clause-② line: each location admits fewer origins than it does today, and none admits more. No published schema widens, and spec is untouched. It owes one
CONTRACT_REVIEW_TIERreview of record before the queue.Responsibility: this lane's transport packages (
packages/adapters/hono,plugin-hono-server) ship the defaults, andplugin-auth(domain:services) holds location 4. No platform path covers them. They reach every host that composescreateHonoAppwithout an origin list, every deployment with a configured list (loopback), and every auth boot with a mixedOS_CORS_ORIGIN. The cloud's serverless preflight short-circuit importscreateOriginMatcher, so it is a cross-repo consumer of location 2, and it is NOT MEASURED from here.Landing: held, beyond the review of record, on the maintainer's word on whether this enters 18.0 (put in this session's chat). The build does not wait on it.
Thread-read: 6288561947
Serial constraints cleared:
packages/adapters/hono/**,plugin-hono-server/src/**ordocs/HARDENING.md. All 12 open PRs were read in this act.code— the spec docstring and auth docs promiseROLE_NOT_FOUND, so an ADR-0112 client cannot recognise the refusal #24750) touchesauth-manager.tsat about:2970(invite-member), about 35 lines from location 4. That is ordinary concurrency: whichever lands second mergesmain.domain:devx,pm:queue, unclaimed) rewrites otherHARDENING.mdsections. Whichever lands second mergesmain.migrate apply) and packages: POST /packages/:id/adopt-orphans skips requireWritablePackage, so it can bind loose items into a read-only package #24813 (domains/packages.ts), are disjoint.objectstack-fleet commentedon Oct 20, 2026
os-dev-report
{ "issue": 24809, "status": "done", "branch": "claude/issue-24809-cors-family-closure", "pr": "https://github.com/objectstack-ai/objectstack/pull/24873", "session": "session_01LmjU2x1faLZ39DVTFyVca6 (subagent: the parent's, as the commits' Claude-Session trailer reads)", "premise_still_valid": true, "summary": "All four locations were present on main as the card reads them. Each was pinned red first, on a9c5f629a (base b3fc122f0 plus the test files only). Changes: (1) resolveCorsAdmission (new, plugin-hono-server src/cors-admission.ts) is the one rule; HonoServerPlugin.init() and @objectstack/hono's createHonoApp both read it, so createHonoApp loses its old no-list default and refuses '*' with credentials when the app is created. (2) matchOriginPattern matches the pattern only; createOriginMatcher admits a loopback origin beside a non-empty list on a development boot only. isDevelopmentBoot is passed in by the rule, and when the option is omitted it is read at matcher creation, so the cloud preflight (a one-argument caller) follows the transport on the same boot. (3) isAllHostOriginPattern (new): '*' or scheme://* with an optional port; with credentials on, any such entry is refused like '*', and the message names the entry. (4) auth-manager.ts trustedOrigins block judges each OS_CORS_ORIGIN entry and drops an entry that admits every host. Enumeration pin: http-conformance src/cors-doors.conformance.test.ts, 3 doors by 9 configurations. Docs: HARDENING § CORS and both READMEs. One minor BREAKING changeset for 3 packages.", "tests": "Red on a9c5f629a (base + tests; plugin-hono-server and hono from source, http-conformance against a dist built at that commit), each exit 1: plugin-hono-server cors-default-admission 'Tests 5 failed | 15 passed (20)'; hono hono-cors-admission 'Tests 7 failed | 3 passed (10)'; plugin-auth auth-manager.cors-origin-trust 'Tests 3 failed | 1 passed (4)'; http-conformance cors-doors 'Tests 11 failed | 7 passed (18)'. Every failure was an assertion (a foreign or loopback origin echoed, a refusal not thrown, '*' or https://* in the trusted set); none was a load error. Green on fb509fc0a (branch merged with origin/main 9a06f0be0), each exit 0: plugin-hono-server test 35 files / 448 passed, typecheck ok; hono test 12 files / 225 passed, typecheck ok; plugin-auth 'exec vitest run --maxWorkers=2' 171 files / 3264 passed + 10 skipped, typecheck ok; http-conformance 19 files / 246 passed, typecheck ok. Each new test file is listed by its package's tsc -p tsconfig.test.json --listFiles (count 1 each). The reverse verification is the committed red-then-green pair above; no separate ablation, and no permanent mutation file. test:repo was not run locally; it is declared to CI.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on fb509fc0a: 85 families, all run; 84 exit 0; 'pnpm check:dual-build-cjs-loads' exit 3 PREREQUISITE NOT MET (needs every package's dist; this worktree built the tested closure only), so NOT MEASURED locally, run by CI Lint & Repo Gates. --ran with codes: '85 derived, 84 run, 1 NOT-MEASURED, 0 UNRUN'. Changeset family re-run on bb2fde296 (the final head; it differs from fb509fc0a only by changeset text), each exit 0: check-adr-0087-registration --base origin/main (no-migration-prescription noticed), check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main, check-changeset-fixed, check:doc-authoring. 'pnpm lint' on bb2fde296: exit 0. check-platform-checklist --changed-from not run, because no export is renamed or removed. PR check-runs: in_progress at report time; not awaited, per contract.", "mcp_calls": "0 — none", "api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs, each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create, which made POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/24873/assignees [os-justin]; its read-back was 16415 bytes sent and 16415 stored, identical, and REST GET confirmed draft true and assignee os-justin; (2) this report, POST /repos/objectstack-ai/objectstack/issues/24809/comments. There were 6 git pushes, which are not REST. No label write.", "open_questions": [ { "question": "The Clause-② line. The PR body and the changeset carry the claim's 'Clause-②: no (narrowing)', copied as the contract requires. The measured diff answers 「本卡放宽接受集或扩大公开面吗」 with yes on two faces. First, plugin-hono-server gains exports-addressable symbols: resolveCorsAdmission, the types CorsAdmission, CorsAdmissionInput and CorsAdmissionDoor, isAllHostOriginPattern, OriginMatcherOptions, and an optional second parameter on createOriginMatcher. Second, createHonoApp with a list of exact origins now admits loopback on a development boot, where its old exact-string pass-through never did. Which line stands?", "options": [ "A: amend both carriers (PR body line 2 and the changeset's Clause-② line) to 'Clause-②: yes (narrowing)'. That is one seat-written body edit plus one changeset commit. The level stays minor and BREAKING, and the CONTRACT_REVIEW_TIER face is unchanged.", "B: keep 'no (narrowing)', reading the new exports as a sibling-sharing seam and the development-boot loopback gain as the one rule's intended answer. That is zero work, but the review would have to accept a line the exports-map test contradicts." ], "recommendation": "A. Business need: the exports exist only because two published packages must share one rule, and the development-boot gain is the card's own Expected, so both facts are real and pulled. Long-term soundness: a declaration that matches the exports map is what both gates and the contract review read. Guarding against AI mistakes: a 'no' that the measured surface contradicts teaches the next author that the line is decorative. Startup focus: the surface added is the minimum for one shared rule, and the line only records it. Cost: one body edit and one changeset line." }, { "question": "Zone 2 #5 was falsified, and the route changed under the ruling's intent. Measured on better-auth 1.7.3 (matchesOriginPattern in dist/auth/trusted-origins.mjs): https://* and *://* each match every origin of their scheme, as '*' does. So auth drops the same narrow all-host class, not only '*'. plugin-auth does not depend on plugin-hono-server, so the auth block carries a one-line regex equal to isAllHostOriginPattern, and the http-conformance pin holds the two equal on its rows. Keep it that way, or move the predicate to a package both already depend on?", "options": [ "A: keep the inline predicate in auth's block, held equal by the enumeration pin. That is zero further work, and the file surface stays as claimed.", "B: move isAllHostOriginPattern to @objectstack/core beside isDevelopmentBoot, and import it from both sides. That gives one implementation, but it needs a core edit outside the claimed surface, a fourth package in the changeset, and core's lane." ], "recommendation": "A for this card. Business need: the class is three spellings, and the pin already fails on drift between the two copies. Long-term soundness: B is the cleaner end state, but it widens a closure card into core. Guarding against AI mistakes: the pin, not the copy count, is what stops a silent divergence. Startup focus: no new surface and no new gate. B can be its own card if the seat wants one implementation." } ], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted in PR Acceptance notes only. The packages/cli/src/commands/serve.ts comment at about :4182 says better-auth's trusted-origin glob does not cross dot separators. On the installed 1.7.3 it does: https://*.example.com matched https://a.b.example.com. This is a pre-existing comment precision with no runtime reach. Dedupe words: better-auth wildcard dot separator trustedOrigins serve.ts comment", "carrier: none (承接者:无) · noted in PR Acceptance notes only. content/docs/deployment/environment-variables.mdx, OS_CORS_ORIGIN row: '`*` is refused at boot unless OS_CORS_CREDENTIALS=false' stays true but does not name the all-host patterns now refused too. It is outside this claim's docs face. Dedupe words: environment-variables OS_CORS_ORIGIN all-host pattern refused docs row", "carrier: none (承接者:无) · noted in PR Acceptance notes only. The auth-manager.ts trustedOrigins block comment 'When CORS allows all origins (default)' above the development substitution predates #24738's default. That behaviour is held off, so the comment was not touched. Dedupe words: auth-manager trustedOrigins development substitution comment CORS default", "carrier: none (承接者:无) · noted in PR Acceptance notes only. https://*.* matches nearly every public host at both matchers, and is outside the narrow all-host class by design (Zone 2 #4). Dedupe words: CORS pattern wildcard host *.* credentials refusal narrow" ], "line_budget": "No skills/** or governed surface is touched, so no line ratchet applies. Net diff against merge-base 9a06f0be0..bb2fde296 (origin/main as merged): 19 files, +1005 / -161. Of that, tests are +694 / -23 across 5 files; source is +244 / -126 across 7 files; docs, READMEs and the changeset are +56 / -12; the http-conformance manifest, vitest config and lockfile are +11 / -0.", "deviations": [ "Zone 2 #5: the route changed after measurement (see open_questions[1]). Auth drops every all-host entry, not only '*'.", "File surface: the enumeration pin's home, packages/qa/http-conformance, gained a devDependency on @objectstack/plugin-auth, 3 lines of pnpm-lock.yaml (pnpm's own re-resolve also flipped an unrelated esbuild version in two importers; that was reverted by hand, and install --frozen-lockfile passed), and an anchored source alias in vitest.config.ts. These are needed so the pin can hold a real AuthManager. check:test-source-alias passed with no ledger growth.", "packages/plugins/plugin-hono-server/src/pattern-matcher.test.ts had two cases that pinned the old always-admit-loopback convenience. They were rewritten to the development-only rule. They were Location 2's only in-repo consumers.", "The Clause-② line was copied from the claim, though the measurement contradicts it (open_questions[0]).", "The commit trailers use AGENTS.md's model-free pair (Co-authored-by: Claude), not the harness reminder's model-named form.", "The first build call's wait in the lock queue outlasted the 600 s tool timeout, so the harness moved that call to the background. The build finished (VERDICT command-exit 0), and its completion was read before any source was edited. Only scratchpad drafting happened in between.", "plugin-auth's suite ran as 'pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2' (all 171 files), not 'pnpm test', so that it would stay under the box's worker cap.", "Gates were derived on fb509fc0a. The final head bb2fde296 changes only the changeset text, and the changeset family was re-run there." ], "files_changed": [ ".changeset/24809-cors-doors-one-admission-rule.md", "docs/HARDENING.md", "packages/adapters/hono/README.md", "packages/adapters/hono/src/hono-cors-admission.test.ts", "packages/adapters/hono/src/index.ts", "packages/plugins/plugin-auth/src/auth-manager.cors-origin-trust.test.ts", "packages/plugins/plugin-auth/src/auth-manager.ts", "packages/plugins/plugin-hono-server/README.md", "packages/plugins/plugin-hono-server/src/adapter.ts", "packages/plugins/plugin-hono-server/src/cors-admission.ts", "packages/plugins/plugin-hono-server/src/cors-default-admission.test.ts", "packages/plugins/plugin-hono-server/src/hono-plugin.ts", "packages/plugins/plugin-hono-server/src/index.ts", "packages/plugins/plugin-hono-server/src/pattern-matcher.test.ts", "packages/plugins/plugin-hono-server/src/pattern-matcher.ts", "packages/qa/http-conformance/package.json", "packages/qa/http-conformance/src/cors-doors.conformance.test.ts", "packages/qa/http-conformance/vitest.config.ts", "pnpm-lock.yaml" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 9 (amended: the
Clause-②line, on the dev's measurement; it supersedes claim6289236849) ·domain:cliseat 2 · p1 ·security(the CORS family's closure card) · 2026-10-20T12:29ZSession:
session_01LmjU2x1faLZ39DVTFyVca6Account:
os-justin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24809-cors-family-closureWorktree:
objectstack-issue-24809Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)Disclosure: under the maintainer's ruling on #9387 (
5323310017), this claim, the PR and every comment state the defaults, their locations and the governing text only. Tests that pin the refusals are the fix's evidence, and they stay.File surface (read on
origin/mainf7546a4de, which carries PR #24806; line numbers move; stop on a breach and explain it in the report):packages/adapters/hono/src/index.ts,createHonoApp's CORS block (about:607–:690). With no list it still defaults to'*'with credentials on and reflects the request origin. It takes the same ruleHonoServerPluginnow has, through one shared rule inplugin-hono-server(which the adapter already imports from), not a copy.packages/plugins/plugin-hono-server/src/pattern-matcher.ts,matchOriginPattern(about:47–:49), which admits every loopback origin ahead of any configured list, in every mode. Loopback is admitted on a development boot only, beside a list too, unless the dev measures a consumer this breaks; then they stop and report it.'*'with credentials (hono-plugin.ts, and the adapter's equivalent) also covers a pattern whose host part matches every host.packages/plugins/plugin-auth/src/auth-manager.ts, thetrustedOriginsblock (about:3011–:3015). It tests the wholeOS_CORS_ORIGINvalue against'*'and splits afterwards, so a'*'entry beside real origins enters better-auth's trusted set. Each entry is judged, not the whole string. This file isdomain:services' (lane table). This seat follows its own card across under a cross-lane declaration on [PM seat] domain:services — ⏳ vacant #6021 in this act; it does not take that lane's other work.OS_CORS_ORIGIN/ mounts CORS (HonoServerPlugin,createHonoApp, auth's trusted-origin merge) and asserts the same answer for the same configuration.docs/HARDENING.md§ CORS (itscreateHonoAppparagraph and the loopback row) and the two packages' READMEs.packages/spec/**(CorsConfigSchema);content/docs/deployment/cli.mdx(its "(CORS)" line, still held for whichever PR lands second); better-auth's other trusted-origin sources (OS_TRUSTED_ORIGINS, preview and root-domain wildcards, SSO discovery);HARDENING.md's other sections (docs: HARDENING.md and production-readiness.mdx still describe rate limiting as adapter-wired and off, and never name server.security.rateLimit or trustProxy #24740's).Container & model:
M,mode:subagent,model: default tierfor the build (dispatch-gates.mjs --tier: "no path-derived mandate"). The narrowing face owesCONTRACT_REVIEW_TIER(reason: it narrows shipped admission defaults at three doors;contract-review.md).Clause-②: yes (narrowing)
Basis for the Clause-② line, measured by the dev (report
6290828606, open question 1):@objectstack/plugin-hono-servergains exports so that two published doors share one rule:resolveCorsAdmission, its types,isAllHostOriginPattern,OriginMatcherOptions, and an optional second parameter oncreateOriginMatcher.createHonoAppwith a list of exact origins now admits loopback on a development boot, which is the card's own Expected.CONTRACT_REVIEW_TIERreview of record covers both faces before the queue.Responsibility: this lane's transport packages (
packages/adapters/hono,plugin-hono-server) ship the defaults, andplugin-auth(domain:services) holds location 4. No platform path covers them. They reach every host that composescreateHonoAppwithout an origin list, every deployment with a configured list (loopback), and every auth boot with a mixedOS_CORS_ORIGIN. The cloud's serverless preflight short-circuit importscreateOriginMatcher, so it is a cross-repo consumer of location 2, and it is NOT MEASURED from here.Landing: held, beyond the review of record, on the maintainer's word on whether this enters 18.0 (put in this session's chat). The build does not wait on it.
Thread-read: 6290828606
Serial constraints cleared:
packages/adapters/hono/**,plugin-hono-server/src/**ordocs/HARDENING.md. All 12 open PRs were read in this act.code— the spec docstring and auth docs promiseROLE_NOT_FOUND, so an ADR-0112 client cannot recognise the refusal #24750) touchesauth-manager.tsat about:2970(invite-member), about 35 lines from location 4. That is ordinary concurrency: whichever lands second mergesmain.domain:devx,pm:queue, unclaimed) rewrites otherHARDENING.mdsections. Whichever lands second mergesmain.migrate apply) and packages: POST /packages/:id/adopt-orphans skips requireWritablePackage, so it can bind loose items into a read-only package #24813 (domains/packages.ts), are disjoint.objectstack-fleet commentedon Oct 20, 2026
ACCEPT → PR #24873 comment
6291362699·domain:cliseat 2 · sessionsession_01LmjU2x1faLZ39DVTFyVca6· 2026-10-20T12:57Z · headdf7d1e1fd. The review of record is6291333954(CONTRACT_REVIEW_TIER, PASS). Both transport doors and auth now judge CORS origins by one rule, and one pin lists every door. Before landing:18.0.0was versioned after this branch's merge base);Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
os-dev-report
{ "issue": 24809, "status": "done", "branch": "claude/issue-24809-cors-family-closure", "pr": "https://github.com/objectstack-ai/objectstack/pull/24873", "session": "session_01LmjU2x1faLZ39DVTFyVca6 (subagent: the parent's, as the commit's Claude-Session trailer reads)", "premise_still_valid": true, "summary": "Round 9 prose-only patch on PR #24873, head df7d1e1fd -> 0ffd23e64d (one commit, one path: the changeset). The changeset now names #24738's change by the version that shipped it (`18.0.0`), gains the `createHonoApp` row for `cors.origin` written as one comma-separated string of exact origins (admitted nothing before, admits each listed origin now: a widening only toward what the operator listed, which the option's TSDoc already promised), and its 'Unchanged' bullet names that spelling as the one exception. The PR body gains the same Contract row, and its three release lines now read `main` as it stands: `18.0.0` versioned in `06cab4849` and tagged, `.changeset/pre.json` gone, the next version pass consumes this changeset, landing waits on the maintainer's word. The three `minor` lines, the BREAKING banner's first sentence, the `Clause-②: yes (narrowing)` line and the adr-0087 marker are byte-identical (scripted compare against df7d1e1fd), so the head moved on changeset prose alone and review of record 6291333954 keeps governing.", "tests": "No test, code, doc, README, package or lockfile path moved: `git diff --name-only df7d1e1fd 0ffd23e64d` lists the changeset only. No suite was run this round and no ablation was owed (no tested behaviour changed); the suite runs and pins of record are the prior round's (PR body, Tests). Premises measured at source, nothing run for them: (1) `git show origin/main:packages/plugins/plugin-hono-server/CHANGELOG.md` line 3 `## 18.0.0`, line 7 `- f7546a4: fix(plugin-hono-server)!: the CORS default admits no cross-origin caller, ...`; `merge-base --is-ancestor 06cab4849` -> origin/main exit 0, -> HEAD exit 1, control leg 9a06f0be0 (older than 06cab4849, its ancestor) -> HEAD exit 0; tags `@objectstack/plugin-hono-server@18.0.0`, `@objectstack/hono@18.0.0`, `@objectstack/plugin-auth@18.0.0` peel to 06cab4849; `.changeset/pre.json` absent on origin/main; all three packages in the one `fixed` group. (2) `packages/adapters/hono/src/index.ts` at 9a06f0be0, CORS block about :615-:680: `cors.origin` was taken as written, a string without `*` failed `hasWildcardPattern` and went to `hono/cors` unchanged; installed hono 4.13.12 `dist/cjs/middleware/cors/index.js:63` answers a string origin only for an identical request origin, so one comma-separated string admitted no origin; the env spelling was split on commas (:627), unchanged. At the head, `resolveCorsAdmission` (cors-admission.ts:75-115) hands the option to `createOriginMatcher` (pattern-matcher.ts:120-138), which splits it through `normalizeOriginPatterns` (:86-93). The option's TSDoc at 9a06f0be0 (:54) read 'Comma-separated string or array'.", "gates": "All on 0ffd23e64d. (a) `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: 85 commands (19 paths vs merge base 9a06f0be0; +1006/-161 = 1167 changed lines, under 3000). Every one run, exit captured before any pipe: first pass 80 exit 0 and 5 exit 3 PREREQUISITE NOT MET (`check:doc-formula-expressions`, `check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:sourcemap-no-sources-content`); then `os-verify-lock.sh -c 'turbo run build --filter=!@objectstack/docs --concurrency=2'` -> `VERDICT command-exit 0 · held the lock 5s · waited 230s` (76 tasks, 75 cached); the five re-run exit 0. (b) The 10 'Declared WIDE population' families from the full derivation, each exit 0: check:init-service-contract, check:live-db-isolation, check:meta-type-normalized, check:optional-error-sink, check:resume-authority-declared, check:runner-env-posture, check:settings-bind-window, check:startup-registry-verdict, check:verify-stand-in, check:wildcard-fallthrough. (c) `--ran` over the final record: 'Run reconciliation — 85 derived, 85 run, 0 NOT-MEASURED, 0 UNRUN.' and '✓ dispatch-gates --ran: 85 derived famil(ies) accounted for — 85 run, 0 NOT-MEASURED (a DERIVED zero — all 85 recorded an exit code and none of them is 3).' exit 0. (d) `node scripts/check-changeset-no-major.mjs --base origin/main`: exit 0, '✓ This diff introduces no `major` bump.' (e) `node scripts/check-adr-0087-registration.mjs --base 9a06f0be0` (the merge base): exit 0, '✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' with the changeset read as `[BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription)`. (f) `pnpm lint` once: exit 0. (g) pre-push: '✓ check:commit-card-trailers: 1 commit message(s) on this push carry no card relation and no model identifier in the trailer pair.' (h) Control-byte self-scan of the changeset and the sent body: none. (i) Not run locally, CI's: the 51 artifact-roster families, the 6 path-scheduled CI jobs, the 4 type-check lanes, `test:repo`. (j) CI on 0ffd23e64d read at 2026-10-20T13:35Z, latest run per name: 8 success, 8 skipped, 23 in_progress (Test Core 1/6-6/6, Build Core, Governed Surface Queue Guard and Check Changeset among them; Lint & Repo Gates and TypeScript Type Check not yet listed): in_progress, NOT MEASURED, not waited on.", "mcp_calls": "0 — no MCP tool called", "api_writes": "2 — both through the fleet-write relay (each one `repository_dispatch` executed as objectstack-fleet[bot]): `issue_patch` -> PATCH /repos/objectstack-ai/objectstack/issues/24873 (run 39431538065, read back 17522 bytes sent, 17522 stored, identical); `comment` -> POST /repos/objectstack-ai/objectstack/issues/24809/comments (this report). Plus `git push` of the branch, df7d1e1fd4..0ffd23e64d fast-forward (not a REST write).", "open_questions": [], "out_of_scope_findings": [ "carrier: domain:cli seat 2 (platform-readings owner) · noted, not filed · reading, not a defect: the relay's `issue_patch` on a PR stored exactly the bytes sent (17522 = 17522) and appended NO footer block, where platform-readings (PR body, edit side) records bare REST `PATCH /issues/N` on a PR as always appending one bare footer (+58). This body was sent without its session-URL footer on that reading, so PR #24873's body now ends with no attribution footer; one seat body write restores it if wanted. Dedupe words: relay issue_patch footer, PR body footer append, platform-readings edit side" ], "line_budget": "changeset 22 -> 23 lines (+3/-2, one commit); PR net vs merge base 9a06f0be0 19 files +1006/-161 = 1167 changed lines (dispatch-gates: under the 3000 human-merge threshold); PR body 132 -> 130 lines, 16525 -> 17522 bytes (+1 Contract row, 3 lines rewritten, the 3-line footer block not sent).", "deviations": [ "PR body footer: the session-URL footer block was left out of the sent body on the platform-readings edit-side prescription; this channel appended none, so the stored body carries no footer (see out_of_scope_findings). The dispatch's 'change no other line' is otherwise kept.", "PR body 'Unchanged' exact-origin bullet (Contract section) is not among the named lines and was not edited; the added row calls itself 'the one exception to the exact-origin line under \"Unchanged\"', so the body does not contradict itself. Narrowing that bullet too is one clause in a seat body write.", "The gate batch 61-85 outlived the 10-minute foreground cap and the harness moved it to the background; it was awaited in the foreground with `tail --pid` (no wake wait), exits read from the runner's own record.", "No empty-branch push probe: the branch already stood at df7d1e1fd (= PR head); the one push was the fast-forward.", "The harness attribution reminder names a model in its Co-Authored-By line; the commit carries AGENTS.md's model-free pair instead.", "origin/main moved after the review's reading: c2d4072281 (#24866) touches `packages/plugins/plugin-auth/src/auth-manager.ts` at hunks about :48, :4593 and :8897+ (location 4 is about :3026), and 06cab4849 already touched `packages/qa/http-conformance/package.json` (the version field), where the review read 'none of them moves any of the PR's 19 paths'. Local `git merge-tree --write-tree HEAD origin/main` exit 0, no conflict; GitHub reports mergeable true. No merge of main (multi-agent discipline 10)." ], "files_changed": [ ".changeset/24809-cors-doors-one-admission-rule.md", "PR #24873 body (issue_patch, not a repository path)" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Landed: PR #24873 →
02e6d732f(Fixes #24809)domain:cliseat 2 ·os-justin· sessionsession_01LmjU2x1faLZ39DVTFyVca6· 2026-10-21T02:02Z6305774402origin/main02e6d732fis on it, with a single parent73432299c(squash). 19 files, +1006 / −161. 16 files' blobs equal the ACCEPTed head0ffd23e64's. The other 3 (auth-manager.ts,http-conformance/package.json,pnpm-lock.yaml) also carrymain's own changes from the 73 commits past the branch, and in each the squash's hunks are byte-equal to the PR's (+/−line digest compared)gh-readonly-queue/main/*pr-24873*head remainscompletedby the PR'sFixes. In the stroke after this comment,pm:dispatchedcomes off6289236849, amended6290874806;6290828606;6291333954(CONTRACT_REVIEW_TIER, PASS);6291362699;0ffd23e64(6292221723);6305774402).resolveCorsAdmission, read by both transport doors (HonoServerPlugin,createHonoApp);createHonoApp's default admits no cross-origin caller;OS_CORS_ORIGINentry.Three packages ship
minor, BREAKING, in the version after18.0.0. Disclosure ruling skills: five principle gaps the #9296 wave exposed in checklist-test / checklist-author / dogfood-verification #93875323310017held for every artifact.environment-variables.mdxOS_CORS_ORIGINrow (incomplete, not false), with the heldcli.mdx"(CORS)" line;serve.tsglob comment, for the next touch of that block.Generated by Claude Code