Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-4218Implemented

(motir-core) The public surface honours registered addresses — CORS and the hand-off return target accept every registered public address, not only motir.co

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.tspublicCorsHeaders(origin) returns headers only when origin === publicSiteOrigin(); lib/publicProjects/returnTarget.tsresolvePublicReturnTarget 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.tsisRegisteredPublicOrigin(origin): Promise<boolean>: true for publicSiteOrigin(), for https://<live workspace_subdomain>, and for https://<issued custom_domain> — a read through the repository's findByHostname, cached in process for 60 s (a positive AND a negative cache, so a bot spraying Origin values cannot turn the CORS check into a database probe per request). Aliases and non-issued domains are NOT origins that may act.
  • cors.tspublicCorsHeaders becomes async and consults the registry; the null-when-not-allowed contract, Vary: Origin, and never Access-Control-Allow-Credentials are unchanged (AMENDMENT 4 §D: the allow-list is a convenience, not a trust boundary, because no credential can ride the request). Every caller of publicCorsHeaders updated for the async signature — grep them and list them in the PR.
  • returnTarget.tsresolvePublicReturnTarget accepts a destination whose ORIGIN is a registered public origin; the positive-comparison posture is kept (an allow-list compared by URL.origin, never a reflection or a startsWith), and the fallback stays HANDOFF_FALLBACK_PATH.
  • appUrlSeam.test.ts's single-reader rule for MOTIR_PUBLIC_SITE_URL still holds: the registry calls publicSiteOrigin(), it does not read the variable.
  • Tests: table-driven over the origin set — the configured site, a live subdomain, an alias (refused), an unverified and an issued custom domain (refused / allowed), https://motir.co@evil.test/ and the other homographs returnTarget.ts's header lists (all refused); the cache's negative arm (one DB read for N identical unknown origins); a real hand-off through app/act/route.ts returning 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 fetch from a live tenant subdomain origin to a public read route receives Access-Control-Allow-Origin echoing that origin and Vary: Origin, and NO Access-Control-Allow-Credentials; an alias origin and an unverified custom domain receive no CORS headers.
  • app/act/route.ts returns a visitor to a return URL on an issued custom domain, and still falls back to HANDOFF_FALLBACK_PATH for every homograph and for a non-registered origin.
  • N requests with the same unknown Origin within the cache window perform one repository read (asserted with a spy).
  • lib/auth/index.ts's session-cookie attributes are byte-identical to origin/main (a snapshot test), and no Domain attribute is introduced.
  • No file outside motir-core is 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-free
  • motir-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 loosen
  • motir-core/tests/hosting/appUrlSeam.test.ts — the single-reader rule
  • the storefindByHostname