(motir-core) The public surface honours registered addresses — CORS and the hand-off return target accept every registered public address, not only motir.co
Opened by Zhu Yue ·
Two shipped modules assume the public site has exactly ONE origin, and after this story it has many. Widen both to the set of REGISTERED addresses — read from the store, cached — and nothing else.
Measured on origin/main (2026-09-02): lib/publicProjects/cors.ts — publicCorsHeaders(origin) returns headers only when origin === publicSiteOrigin(); lib/publicProjects/returnTarget.ts — resolvePublicReturnTarget accepts a destination only when parsed.origin === publicSiteOrigin(). Both are correct today and both break the moment a page on acme.<base> pages an items list (a browser fetch to app.motir.co — CORS) or a visitor on roadmap.acme.com presses Follow (the hand-off's return — refused, so they land on the signed-in home instead of the page they left).
What ships
lib/publicAddresses/allowedOrigins.ts—isRegisteredPublicOrigin(origin): Promise<boolean>: true forpublicSiteOrigin(), forhttps://<live workspace_subdomain>, and forhttps://<issued custom_domain>— a read through the repository'sfindByHostname, cached in process for 60 s (a positive AND a negative cache, so a bot sprayingOriginvalues cannot turn the CORS check into a database probe per request). Aliases and non-issued domains are NOT origins that may act.cors.ts—publicCorsHeadersbecomes async and consults the registry; thenull-when-not-allowed contract,Vary: Origin, and neverAccess-Control-Allow-Credentialsare unchanged (AMENDMENT 4 §D: the allow-list is a convenience, not a trust boundary, because no credential can ride the request). Every caller ofpublicCorsHeadersupdated for the async signature —grepthem and list them in the PR.returnTarget.ts—resolvePublicReturnTargetaccepts a destination whose ORIGIN is a registered public origin; the positive-comparison posture is kept (an allow-list compared byURL.origin, never a reflection or astartsWith), and the fallback staysHANDOFF_FALLBACK_PATH.appUrlSeam.test.ts's single-reader rule forMOTIR_PUBLIC_SITE_URLstill holds: the registry callspublicSiteOrigin(), it does not read the variable.- Tests: table-driven over the origin set — the configured site, a live subdomain, an alias (refused), an
unverifiedand anissuedcustom domain (refused / allowed),https://motir.co@evil.test/and the other homographsreturnTarget.ts's header lists (all refused); the cache's negative arm (one DB read for N identical unknown origins); a real hand-off throughapp/act/route.tsreturning to a custom-domain page.
Boundary
No route added, no DTO change, no session or cookie attribute touched — sameSite: 'lax' and the host-only cookie are public-surface-hosts.md §4's and stay exactly as they are; a test in this card re-asserts the cookie attributes did not move. Reads the store only (the store).
Acceptance criteria
- A
fetchfrom a live tenant subdomain origin to a public read route receivesAccess-Control-Allow-Originechoing that origin andVary: Origin, and NOAccess-Control-Allow-Credentials; an alias origin and an unverified custom domain receive no CORS headers. app/act/route.tsreturns a visitor to areturnURL on anissuedcustom domain, and still falls back toHANDOFF_FALLBACK_PATHfor every homograph and for a non-registered origin.- N requests with the same unknown
Originwithin the cache window perform one repository read (asserted with a spy). lib/auth/index.ts's session-cookie attributes are byte-identical toorigin/main(a snapshot test), and noDomainattribute is introduced.- No file outside
motir-coreis touched.
Context refs
motir-core/lib/publicProjects/cors.ts·returnTarget.ts— the two modules, and their header comments on why the allow-list is positive and credential-freemotir-core/app/act/route.ts— the hand-off (MOTIR-4114)motir-core/docs/decisions/public-surface-hosts.md§4 + AMENDMENT 4 §B/§D — the constraints this must not loosenmotir-core/tests/hosting/appUrlSeam.test.ts— the single-reader rule- the store —
findByHostname
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.