feat(backend): wildcard trusted origins + reserved subdomain skip-list#214
Conversation
Subdomain org middleware now ignores well-known reserved hostnames (www, api, app, admin, etc.) instead of treating them as org slugs. When AUTH_COOKIE_DOMAIN is set, BetterAuth trustedOrigins gains a wildcard entry for the matching apex so cross-subdomain auth works (e.g. acme.lobu.ai posting to lobu.ai).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ac62122499
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| trustedOriginSet.add(`https://*.${normalized}`); | ||
| trustedOriginSet.add(`https://${normalized}`); |
There was a problem hiding this comment.
Include non-HTTPS wildcard in cookie-domain trusted origins
This adds only https://*.{domain} and https://{domain} when AUTH_COOKIE_DOMAIN is set, so cross-subdomain auth still fails for HTTP deployments (for example self-hosted/staging environments that intentionally run without TLS) because Origin: http://tenant.example.com can never match the new entries. Since this block is meant to make cross-subdomain requests work generically, derive the scheme from the resolved base URL or use a protocol-agnostic wildcard so non-HTTPS environments don't regress.
Useful? React with 👍 / 👎.
Without this, setting PUBLIC_WEB_URL=https://app.lobu.ai while serving multi-tenant orgs at *.lobu.ai caused every acme.lobu.ai request to be 302'd to app.lobu.ai — defeating the host-based org routing this PR introduces. The redirect now treats any host under the auth cookie zone as canonical-friendly (since cross-subdomain cookies make them all part of the same logical origin).
…OOKIE_DOMAIN PR #214 set up wildcard cert/ingress, AUTH_COOKIE_DOMAIN=.lobu.ai, and BetterAuth wildcard trust so {org}.lobu.ai could carry sessions. But the middleware that actually populates `subdomainOrg` keyed off the PUBLIC_WEB_URL hostname (app.lobu.ai), so `acme.lobu.ai` didn't match `.app.lobu.ai` and the slug was never extracted — only *.app.lobu.ai worked. - Add getSubdomainZone() + extractSubdomainOrg() helpers that prefer AUTH_COOKIE_DOMAIN as the zone and fall back to the PUBLIC_WEB_URL hostname for deployments without a cookie zone. - Update the subdomain middleware to use the zone helpers, with port stripping and case-insensitive matching. - Extend the CORS allow-list to trust the cookie zone apex and its wildcard subdomains so the bare lobu.ai landing and org subdomains can call app.lobu.ai. - 13 new tests covering zone selection, reserved-subdomain skip, multi-label rejection, port handling, and case-insensitivity.
…OOKIE_DOMAIN (#224) PR #214 set up wildcard cert/ingress, AUTH_COOKIE_DOMAIN=.lobu.ai, and BetterAuth wildcard trust so {org}.lobu.ai could carry sessions. But the middleware that actually populates `subdomainOrg` keyed off the PUBLIC_WEB_URL hostname (app.lobu.ai), so `acme.lobu.ai` didn't match `.app.lobu.ai` and the slug was never extracted — only *.app.lobu.ai worked. - Add getSubdomainZone() + extractSubdomainOrg() helpers that prefer AUTH_COOKIE_DOMAIN as the zone and fall back to the PUBLIC_WEB_URL hostname for deployments without a cookie zone. - Update the subdomain middleware to use the zone helpers, with port stripping and case-insensitive matching. - Extend the CORS allow-list to trust the cookie zone apex and its wildcard subdomains so the bare lobu.ai landing and org subdomains can call app.lobu.ai. - 13 new tests covering zone selection, reserved-subdomain skip, multi-label rejection, port handling, and case-insensitivity.
Second pass on the 2-week PR review. Five more gaps closed: - gateway: unit tests for verifyOwnedAgentAccess covering owner, cross-tenant, cross-platform, agent-scoped, admin-bypass, unknown-agent, and external OAuth mismatches (#285 follow-up). Closes the test hole in the cross-tenant ownership guard. - owletto-backend: validate each CSP frame-ancestor entry against a strict host-source / scheme-source grammar before joining (#246 follow-up). Malformed env entries like `https:// lobu.ai` are now dropped instead of silently rendered into the directive. - owletto-backend: introduce normalizeHost() in utils/public-origin and use it from getSubdomainZone, extractSubdomainOrg, getCanonicalRedirectUrl, and the BetterAuth trustedOrigins wiring (#234/#224/#214 follow-up). Unifies the ad-hoc .toLowerCase()/.replace() patterns and adds IDN→punycode so `müller.lobu.ai` matches the ASCII zone configured in env. - owletto-backend: redact member emails that surface via template_data and tab template_data in resolve_path, not only on the single resolved entity (#309 follow-up). A dashboard data source that enumerates $member entities no longer leaks emails to non-admin callers. New utils/member-redaction helper plus unit coverage. - owletto-backend: treat #311 as already closed — ToolContext.memberRole is `string | null` (required, not optional), so TypeScript already catches future literal omissions at construction.
Second pass on the 2-week PR review. Five more gaps closed: - gateway: unit tests for verifyOwnedAgentAccess covering owner, cross-tenant, cross-platform, agent-scoped, admin-bypass, unknown-agent, and external OAuth mismatches (#285 follow-up). Closes the test hole in the cross-tenant ownership guard. - owletto-backend: validate each CSP frame-ancestor entry against a strict host-source / scheme-source grammar before joining (#246 follow-up). Malformed env entries like `https:// lobu.ai` are now dropped instead of silently rendered into the directive. - owletto-backend: introduce normalizeHost() in utils/public-origin and use it from getSubdomainZone, extractSubdomainOrg, getCanonicalRedirectUrl, and the BetterAuth trustedOrigins wiring (#234/#224/#214 follow-up). Unifies the ad-hoc .toLowerCase()/.replace() patterns and adds IDN→punycode so `müller.lobu.ai` matches the ASCII zone configured in env. - owletto-backend: redact member emails that surface via template_data and tab template_data in resolve_path, not only on the single resolved entity (#309 follow-up). A dashboard data source that enumerates $member entities no longer leaks emails to non-admin callers. New utils/member-redaction helper plus unit coverage. - owletto-backend: treat #311 as already closed — ToolContext.memberRole is `string | null` (required, not optional), so TypeScript already catches future literal omissions at construction.
…instr guard, MCP join rate-limit) (#325) * fix: address gaps found in post-merge review of last 2 weeks of PRs Follow-ups from an aggregated teammate review of 128 PRs merged between 2026-04-09 and 2026-04-23. Five concrete gaps patched: - worker: constrain UploadUserFile to the workspace root (#203 follow-up). path.join allowed `../` and absolute paths to escape the workspace. Now resolves and rejects anything outside workspaceDir when one is set. - core: flip Sentry sendDefaultPii to false (#172 follow-up). User content and identifiers flow through this stack; the schema has no scrubbing so PII-by-default was unsafe. - gateway: make SlackInstructionProvider extend BaseInstructionProvider (#269 follow-up). Sibling Skills/Network providers are wrapped in a try/catch that returns "" on error; Slack was bypassing it and would crash session-context assembly if listConnections threw. - owletto-backend: rate-limit the join_organization MCP tool to match the REST endpoint (#296 follow-up). Keyed on userId since MCP calls don't carry a client IP. Skipped one reviewer finding: removing the process.env fallback for API keys at worker.ts:1099/1109 (the inconsistency with #225 base-URL code). Embedded/dev workers depend on that fallback since credentialStore is only populated from gateway-supplied session context. * fix: address remaining gaps from post-merge review Second pass on the 2-week PR review. Five more gaps closed: - gateway: unit tests for verifyOwnedAgentAccess covering owner, cross-tenant, cross-platform, agent-scoped, admin-bypass, unknown-agent, and external OAuth mismatches (#285 follow-up). Closes the test hole in the cross-tenant ownership guard. - owletto-backend: validate each CSP frame-ancestor entry against a strict host-source / scheme-source grammar before joining (#246 follow-up). Malformed env entries like `https:// lobu.ai` are now dropped instead of silently rendered into the directive. - owletto-backend: introduce normalizeHost() in utils/public-origin and use it from getSubdomainZone, extractSubdomainOrg, getCanonicalRedirectUrl, and the BetterAuth trustedOrigins wiring (#234/#224/#214 follow-up). Unifies the ad-hoc .toLowerCase()/.replace() patterns and adds IDN→punycode so `müller.lobu.ai` matches the ASCII zone configured in env. - owletto-backend: redact member emails that surface via template_data and tab template_data in resolve_path, not only on the single resolved entity (#309 follow-up). A dashboard data source that enumerates $member entities no longer leaks emails to non-admin callers. New utils/member-redaction helper plus unit coverage. - owletto-backend: treat #311 as already closed — ToolContext.memberRole is `string | null` (required, not optional), so TypeScript already catches future literal omissions at construction. --------- Co-authored-by: Claude <noreply@anthropic.com>
Picks up: feat(mac): context picker always visible, persisted, no sign-in override (#214) Before: 06b1543 After: c3564c4
Summary
PR6 of the owletto consolidation plan — backend half. The companion infra changes live in
owletto-deploy(private GitOps repo).The host-based subdomain extraction itself was already in place from the earlier merge (`packages/owletto-backend/src/index.ts:269-287`); `mcpAuth` already falls back to `subdomainOrg` when no path param is present (`packages/owletto-backend/src/workspace/multi-tenant.ts:62`). Path-based `/:orgSlug/` routes stay until the `.lobu.ai` ingress and DNS are live.
Out-of-band prereqs (companion deploy commit)
Test plan