Skip to content

feat: automatically trust project's hosted handler domain (built-with domain)#1470

Closed
N2D4 wants to merge 2 commits into
devfrom
devin/1779411645-trust-hosted-handler-domain
Closed

feat: automatically trust project's hosted handler domain (built-with domain)#1470
N2D4 wants to merge 2 commits into
devfrom
devin/1779411645-trust-hosted-handler-domain

Conversation

@N2D4
Copy link
Copy Markdown
Contributor

@N2D4 N2D4 commented May 22, 2026

Automatically accept <project-id>.built-with-stack-auth.com (or whatever the configured hosted handler domain suffix is) as a trusted domain for each project, without requiring it to be explicitly added in the project config.

Changes:

  • redirect-urls.tsx: Include hosted handler domain (both HTTP and HTTPS) in trusted domains for redirect URL validation — this covers OAuth callbacks, passkeys, and all other redirect checks
  • turnstile.tsx: Accept the hosted handler hostname for Turnstile verification
  • oauth/model.tsx: Include hosted handler URL in OAuth client redirect URIs
  • Added unit tests for the new behavior

Link to Devin session: https://app.devin.ai/sessions/11f027a384674ca9b703b14db1a80d46
Requested by: @N2D4


Note

Medium Risk
Expands the set of automatically trusted redirect/hostname origins based on an env-configured suffix and project ID, affecting OAuth redirect validation and bot-challenge hostname checks. Risk is moderate because mistakes could unintentionally widen redirect URI acceptance.

Overview
Automatically trusts the project hosted handler domain (<project-id>${NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX}) without requiring it in tenancy config.

validateRedirectUrl now always includes https://<project-id>.<suffix> in its trusted domain list (with a new getHostedHandlerDomainSuffix() helper), OAuthModel.getClient always adds the hosted handler /handler URL to redirectUris, and Turnstile hostname validation now treats the hosted handler hostname as allowed.

Adds unit tests covering default/custom suffix behavior and ensuring only the correct project/HTTPS hosted domain is accepted.

Reviewed by Cursor Bugbot for commit cfb9af8. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Added support for platform-provided hosted handler domains as trusted endpoints across redirect URL validation, OAuth configuration, and Turnstile verification.
    • Hosted handler domains are now configurable via environment variables with a default built-with domain suffix.
  • Tests

    • Added test coverage for hosted handler domain validation behavior.

Review Change Stack

… domain)

The <project-id>.built-with-stack-auth.com domain (or whatever the
configured hosted handler domain suffix is) is now automatically
accepted as a trusted domain for each project.

Changes:
- redirect-urls.tsx: Include hosted handler domain in trusted domains
  for redirect URL validation (covers OAuth callbacks, passkeys, etc.)
- turnstile.tsx: Accept hosted handler hostname for Turnstile validation
- oauth/model.tsx: Include hosted handler in OAuth client redirect URIs

Co-Authored-By: Konstantin Wohlwend <n2d4xc@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment May 22, 2026 1:15am
stack-auth-mcp Ready Ready Preview, Comment May 22, 2026 1:15am
stack-auth-skills Ready Ready Preview, Comment May 22, 2026 1:15am
stack-backend Ready Ready Preview, Comment May 22, 2026 1:15am
stack-dashboard Ready Ready Preview, Comment May 22, 2026 1:15am
stack-demo Ready Ready Preview, Comment May 22, 2026 1:15am
stack-docs Ready Ready Preview, Comment May 22, 2026 1:15am
stack-preview-backend Ready Ready Preview, Comment May 22, 2026 1:15am
stack-preview-dashboard Ready Ready Preview, Comment May 22, 2026 1:15am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Backend validation endpoints now trust a project-specific hosted handler domain derived from an environment-configurable suffix. A new shared helper getHostedHandlerDomainSuffix() is integrated into redirect URL validation, Turnstile hostname checking, and OAuth redirect URI configuration, with test coverage verifying the feature works as expected.

Changes

Hosted Handler Domain Trust Configuration

Layer / File(s) Summary
Hosted handler domain suffix helper
apps/backend/src/lib/redirect-urls.tsx
New exported getHostedHandlerDomainSuffix() function reads NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX environment variable with a default fallback to .built-with-stack-auth.com.
Redirect URL validation with hosted handler support
apps/backend/src/lib/redirect-urls.tsx, apps/backend/src/lib/redirect-urls.test.tsx
validateRedirectUrl constructs the project-hosted handler domain and adds it to the trusted domains list. Test setup is updated to support parameterized projectId in createMockTenancy. New tests verify the hosted handler domain is trusted over HTTPS but rejected over HTTP or with wrong project IDs, and coexists with other trusted domains.
Turnstile hostname validation with hosted handler
apps/backend/src/lib/turnstile.tsx
isAllowedTurnstileHostname imports the suffix helper and constructs the project-hosted handler URL as a trusted hostname when the Turnstile response hostname matches.
OAuth redirect URI configuration with hosted handler
apps/backend/src/oauth/model.tsx
OAuthModel.getClient imports the suffix helper and appends the project-hosted handler /handler endpoint to the redirectUris list.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • hexclave/stack-auth#1458: Both PRs modify validateRedirectUrl behavior—this PR extends trustedDomains with the project hosted-handler domain, while the retrieved PR refactors validateRedirectUrl to delegate trusted-domain validation to the shared redirect-urls utility.

Suggested reviewers

  • Copilot

Poem

🐰 A project domain, now trusted and true,
Built with Stack Auth, in the hosted queue,
From redirects to OAuth flows that gleam,
One suffix to rule the validation dream! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: automatically trusting the project's hosted handler domain without explicit configuration.
Description check ✅ Passed The description comprehensively covers the changes across multiple files, explains the rationale, lists affected areas (OAuth, Turnstile, redirect validation), and mentions added tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1779411645-trust-hosted-handler-domain

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Konstantin Wohlwend <n2d4xc@gmail.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cfb9af8. Configure here.

*/
export function getHostedHandlerDomainSuffix(): string {
return getEnvVariable("NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX", defaultHostedHandlerDomainSuffix);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dev suffix breaks hosted trust

High Severity

The getHostedHandlerDomainSuffix function returns the raw NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX env var. In local dev, this includes an unresolved ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81} placeholder. This causes the backend to trust a malformed hosted domain, leading to automatic redirect URL trust failures due to a mismatch with client-used domains.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cfb9af8. Configure here.

trustedDomains: [
...Object.values(tenancy.config.domains.trustedDomains).map(domain => domain.baseUrl),
`https://${hostedDomain}`,
],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignores hosted handler URL template

Medium Severity

Redirect, OAuth, and Turnstile trust only https://{projectId}{domainSuffix}, but hosted handler URLs come from NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE when set (e.g. path-based http://localhost:PORT/{projectId}/handler/...). Those real URLs can stay untrusted despite this change.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cfb9af8. Configure here.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the backend’s domain/redirect validation logic to automatically treat each project’s hosted handler domain (<project-id><suffix>, e.g. <project-id>.built-with-stack-auth.com) as trusted without requiring explicit tenancy configuration, aligning redirect URL validation, OAuth redirect URI registration, and Turnstile hostname checks.

Changes:

  • Add getHostedHandlerDomainSuffix() and include the hosted handler HTTPS origin in validateRedirectUrl trusted domains.
  • Always append the hosted handler /handler URL to OAuth client redirectUris.
  • Allow the hosted handler hostname in Turnstile hostname validation.
  • Add unit tests for hosted handler redirect URL trust behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
apps/backend/src/lib/redirect-urls.tsx Adds hosted handler suffix helper and auto-trusts hosted handler domain for redirect validation.
apps/backend/src/oauth/model.tsx Adds hosted handler /handler URL to OAuth client redirect URIs.
apps/backend/src/lib/turnstile.tsx Allows hosted handler hostname during Turnstile verification.
apps/backend/src/lib/redirect-urls.test.tsx Adds tests to validate hosted handler domain redirect behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +8 to +14

/**
* Returns the domain suffix for the hosted handler (e.g. ".built-with-stack-auth.com" in
* production, ".localhost:8109" in local dev).
*/
export function getHostedHandlerDomainSuffix(): string {
return getEnvVariable("NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX", defaultHostedHandlerDomainSuffix);
Comment on lines +21 to +27
const hostedDomain = `${tenancy.project.id}${getHostedHandlerDomainSuffix()}`;
return validateRedirectUrlAgainstTrustedDomains(urlOrString, {
allowLocalhost: tenancy.config.domains.allowLocalhost,
trustedDomains: Object.values(tenancy.config.domains.trustedDomains).map(domain => domain.baseUrl),
trustedDomains: [
...Object.values(tenancy.config.domains.trustedDomains).map(domain => domain.baseUrl),
`https://${hostedDomain}`,
],
Comment on lines +478 to +504
describe('hosted handler domain (built-with domain)', () => {
it('should trust the project hosted handler domain with default suffix', () => {
vi.stubEnv('NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX', '.built-with-stack-auth.com');
const projectId = '12345678-1234-1234-1234-123456789012';
const tenancy = createMockTenancy({
domains: {
allowLocalhost: false,
trustedDomains: {},
},
}, projectId);

// HTTPS on the built-with domain should be trusted
expect(validateRedirectUrl(`https://${projectId}.built-with-stack-auth.com/callback`, tenancy)).toBe(true);
expect(validateRedirectUrl(`https://${projectId}.built-with-stack-auth.com/`, tenancy)).toBe(true);
expect(validateRedirectUrl(`https://${projectId}.built-with-stack-auth.com/handler/oauth-callback`, tenancy)).toBe(true);

// HTTP on the built-with domain should NOT be trusted
expect(validateRedirectUrl(`http://${projectId}.built-with-stack-auth.com/callback`, tenancy)).toBe(false);

// Different project IDs should NOT be trusted
expect(validateRedirectUrl('https://other-project.built-with-stack-auth.com/callback', tenancy)).toBe(false);

// Unrelated domains should NOT be trusted
expect(validateRedirectUrl('https://example.com/callback', tenancy)).toBe(false);

vi.unstubAllEnvs();
});
Comment on lines +55 to +60
// The project's hosted handler domain (e.g. <project-id>.built-with-stack-auth.com) is always trusted
const hostedHandlerUrl = createUrlIfValid(`https://${tenancy.project.id}${getHostedHandlerDomainSuffix()}`);
if (hostedHandlerUrl != null && hostedHandlerUrl.hostname === hostname) {
return true;
}

Comment on lines +79 to +82
// The project's hosted handler domain is always trusted
const hostedDomain = `${tenancy.project.id}${getHostedHandlerDomainSuffix()}`;
redirectUris.push(new URL("/handler", `https://${hostedDomain}`).toString());

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR automatically trusts each project's hosted handler domain (<project-id>.<suffix>) as a redirect URL, Turnstile hostname, and OAuth redirect URI, removing the requirement to manually add it in project settings.

  • redirect-urls.tsx injects https://<project-id><suffix> into the trusted-domain list used for all redirect URL validation (OAuth callbacks, passkeys, etc.), and turnstile.tsx mirrors this for Turnstile hostname checks.
  • oauth/model.tsx pushes the hosted-domain URI into the OAuth client's redirectUris list, but because this push is unconditional it makes the existing allowLocalhost empty-list guard permanently unreachable — the http://localhost fallback will never be added for projects using allowLocalhost: true with no configured trusted domains.

Confidence Score: 3/5

Safe to merge for HTTPS-only hosted-handler flows, but the localhost OAuth fallback in the OAuth model is now unreachable, which may silently break allowLocalhost-only projects depending on how the oauth2-server library behaves.

The redirect-URL and Turnstile changes are clean and well-tested. The concern in oauth/model.tsx is that the unconditional hosted-domain push makes the allowLocalhost guard permanently dead code, so the localhost URI is never added to client.redirectUris for projects with no configured trusted domains.

apps/backend/src/oauth/model.tsx — the allowLocalhost empty-list fallback block needs verification or removal.

Important Files Changed

Filename Overview
apps/backend/src/lib/redirect-urls.tsx Adds getHostedHandlerDomainSuffix() helper and injects https://. into the trusted-domain list; straightforward and correct.
apps/backend/src/lib/turnstile.tsx Adds hosted-handler hostname check inside isAllowedTurnstileHostname; uses createUrlIfValid safely and mirrors the redirect-URL trust logic correctly.
apps/backend/src/oauth/model.tsx Always pushes hosted-domain URI into redirectUris, making the subsequent allowLocalhost empty-list guard permanently dead. http://localhost will never be added to the OAuth client's redirectUris for projects that relied on that fallback.
apps/backend/src/lib/redirect-urls.test.tsx Good coverage for the new hosted-handler trust paths; one test case for wrong port inadvertently passes for the wrong reason (HTTP instead of HTTPS).
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/backend/src/lib/redirect-urls.test.tsx:519-520
**"Wrong port" test case uses HTTP, not HTTPS — it tests the wrong thing**

The assertion passes because the protocol is HTTP, not because the port is wrong. Since only `https://…` is added to trusted domains, any HTTP URL returns `false` regardless of the port. The test comment says "Wrong port should NOT be trusted" but it never reaches port-matching logic. To actually verify the port-mismatch rejection, the URL should use `https://`.

### Issue 2 of 2
apps/backend/src/oauth/model.tsx:83-85
**Dead code — localhost fallback is never reached**

The hosted domain URI is pushed unconditionally on line 81, so `redirectUris.length` is always `>= 1` when this guard is evaluated — the `length === 0` condition can never be true and the localhost fallback is unreachable. For projects that rely on `allowLocalhost: true` with no configured trusted domains, the localhost URI no longer appears in `client.redirectUris`. If the oauth2-server library uses that list for auto-selection when no `redirect_uri` is included in the request, those OAuth flows would change behavior silently. The block should either be removed (if `validateRedirectUri` is fully authoritative) or repositioned so the localhost fallback is still added when appropriate.

Reviews (1): Last reviewed commit: "fix: only trust https:// for built-with ..." | Re-trigger Greptile

Comment on lines +519 to +520

// Wrong port should NOT be trusted
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 "Wrong port" test case uses HTTP, not HTTPS — it tests the wrong thing

The assertion passes because the protocol is HTTP, not because the port is wrong. Since only https://… is added to trusted domains, any HTTP URL returns false regardless of the port. The test comment says "Wrong port should NOT be trusted" but it never reaches port-matching logic. To actually verify the port-mismatch rejection, the URL should use https://.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/backend/src/lib/redirect-urls.test.tsx
Line: 519-520

Comment:
**"Wrong port" test case uses HTTP, not HTTPS — it tests the wrong thing**

The assertion passes because the protocol is HTTP, not because the port is wrong. Since only `https://…` is added to trusted domains, any HTTP URL returns `false` regardless of the port. The test comment says "Wrong port should NOT be trusted" but it never reaches port-matching logic. To actually verify the port-mismatch rejection, the URL should use `https://`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 83 to 85
if (redirectUris.length === 0 && tenancy.config.domains.allowLocalhost) {
redirectUris.push("http://localhost");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Dead code — localhost fallback is never reached

The hosted domain URI is pushed unconditionally on line 81, so redirectUris.length is always >= 1 when this guard is evaluated — the length === 0 condition can never be true and the localhost fallback is unreachable. For projects that rely on allowLocalhost: true with no configured trusted domains, the localhost URI no longer appears in client.redirectUris. If the oauth2-server library uses that list for auto-selection when no redirect_uri is included in the request, those OAuth flows would change behavior silently. The block should either be removed (if validateRedirectUri is fully authoritative) or repositioned so the localhost fallback is still added when appropriate.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/backend/src/oauth/model.tsx
Line: 83-85

Comment:
**Dead code — localhost fallback is never reached**

The hosted domain URI is pushed unconditionally on line 81, so `redirectUris.length` is always `>= 1` when this guard is evaluated — the `length === 0` condition can never be true and the localhost fallback is unreachable. For projects that rely on `allowLocalhost: true` with no configured trusted domains, the localhost URI no longer appears in `client.redirectUris`. If the oauth2-server library uses that list for auto-selection when no `redirect_uri` is included in the request, those OAuth flows would change behavior silently. The block should either be removed (if `validateRedirectUri` is fully authoritative) or repositioned so the localhost fallback is still added when appropriate.

How can I resolve this? If you propose a fix, please make it concise.

@N2D4 N2D4 closed this May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants