Skip to content

fix: prevent internal origins from leaking into public URLs - #32

Merged
Molret merged 3 commits into
mainfrom
codex/harden-public-origin
Aug 12, 2026
Merged

fix: prevent internal origins from leaking into public URLs#32
Molret merged 3 commits into
mainfrom
codex/harden-public-origin

Conversation

@Molret

@Molret Molret commented Aug 12, 2026

Copy link
Copy Markdown
Member

What changed

  • Centralize public URL generation on HARLY_URL for upload URLs, file URLs, OAuth callbacks, redirects, metadata, webhooks, notifications, and integrations.
  • Prevent production configuration from accepting localhost, loopback, or unspecified bind addresses.
  • Keep a non-routable https://build.invalid placeholder for the production build only.
  • Add regression coverage for the https://0.0.0.0:3000 presign failure.

Root cause

Public presign routes used request.url to make relative local-storage URLs absolute. Behind the reverse proxy, that request origin could be 0.0.0.0:3000, so the browser attempted the upload against an internal address and reported a CORS failure with status null.

Validation

  • pnpm --filter web test — 189 files passed, 871 tests passed; 13 files skipped by integration configuration.
  • pnpm --filter web typecheck
  • pnpm --filter @harly/config typecheck
  • pnpm --filter web exec eslint ...
  • Production build with Dockerfile-equivalent environment variables passed.

Summary by CodeRabbit

  • New Features
    • Added consistent public URL handling across dashboards, portals, emails, webhooks, OAuth integrations, redirects, and API responses.
    • Improved URL generation behind proxies and configured public domains.
  • Bug Fixes
    • Prevented localhost and bind-address URLs from being used in production.
    • Ensured presigned upload and file links use the configured public origin.
  • Documentation
    • Clarified production requirements for secure, reachable public URLs.

Greptile Summary

The PR centralizes externally visible URL construction around the configured Harly public origin and strengthens production validation against local and bind addresses.

  • Uses HARLY_URL for presigned files, OAuth callbacks, redirects, metadata, notifications, webhooks, and integrations.
  • Adds production validation and regression tests for loopback and unspecified hosts.
  • Uses a non-routable build-only origin while keeping metadata routes dynamically resolved at runtime.
  • Updates Docker, Compose, CI, CLI, and configuration documentation for the new public-origin contract.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/web/src/lib/public-origin.ts Introduces the canonical public-origin resolver, production host checks, and relative public URL resolution.
packages/config/src/index.ts Enforces HTTPS and rejects localhost, the full IPv4 loopback range, IPv6 loopback, and unspecified bind addresses in production.
apps/web/src/app/robots.ts Resolves the sitemap origin inside a force-dynamic metadata handler, avoiding capture of the build-only placeholder.
apps/web/src/app/sitemap.ts Generates public sitemap entries from the canonical origin while retaining dynamic database-backed generation.
apps/web/src/app/api/public/v1/resume/presign/route.ts Converts local-storage presign paths using the configured public origin instead of the inbound request origin.
Dockerfile Replaces the local build origin with the non-routable build-only placeholder while leaving runtime configuration external.
packages/config/src/index.test.ts Adds production configuration coverage for bind, localhost, and noncanonical IPv4 loopback addresses.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  E[HARLY_URL] --> V[Production origin validation]
  V --> O[getHarlyPublicOrigin]
  O --> U[Upload and file URLs]
  O --> A[OAuth callbacks and redirects]
  O --> M[Metadata and public pages]
  O --> N[Notifications and webhooks]
  B[Production build] --> P[https://build.invalid placeholder]
  R[Runtime requests] --> O
Loading

Reviews (2): Last reviewed commit: "fix: address PR origin review feedback" | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change centralizes public-origin resolution through getHarlyPublicOrigin() and toHarlyPublicUrl(). Production origins now require reachable HTTPS hosts. Application routes, OAuth flows, redirects, email links, webhooks, metadata, and presigned URLs use the shared resolution logic.

Changes

Public origin and URL handling

Layer / File(s) Summary
Origin validation and build configuration
Dockerfile, packages/config/..., apps/web/src/lib/public-origin.*
Production validation rejects localhost, loopback, and bind-address origins. Production builds use https://build.invalid for unsafe build-time origins.
Public URL and redirect construction
apps/web/src/proxy.ts, apps/web/src/server/scim/http.ts, apps/web/src/app/api/public/v1/...
Redirects and presigned response URLs use the configured public origin. Remote storage URLs remain unchanged.
OAuth callback and installation URLs
apps/web/src/app/api/integrations/..., apps/web/src/app/api/portal/auth/..., apps/web/src/lib/gcal/config.ts
OAuth callback URLs, installation URLs, success redirects, and error redirects use getHarlyPublicOrigin().
Application-generated public URLs
apps/web/src/app/..., apps/web/src/features/..., apps/web/src/lib/..., apps/web/src/server/...
Pages, actions, email links, job URLs, metadata, notifications, passkeys, webhooks, and workspace links use the shared public origin. ReplyHandlingSettingsForm now receives a required appUrl prop.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly describes the main change: preventing internal origins from appearing in public URLs.
Description check ✅ Passed The description explains the change, root cause, user impact, and validation results, but it does not follow all template headings or checklist items.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

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.

@Molret
Molret marked this pull request as ready for review August 12, 2026 07:14
@readme-ai-writer

readme-ai-writer Bot commented Aug 12, 2026

Copy link
Copy Markdown

📚 No documentation changes were needed for this PR.

@strix-security

strix-security Bot commented Aug 12, 2026

Copy link
Copy Markdown

Strix Security Review

No security issues found.

Updated for 1a6fc30.


Reviewed by Strix
Re-run review · Configure security review settings

Comment thread apps/web/src/lib/public-origin.ts Outdated
Comment thread apps/web/src/app/robots.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/lib/public-origin.ts (1)

26-30: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use the documented fallback chain for every public URL.

loadHarlyConfig() supports NEXT_PUBLIC_APP_URL and BETTER_AUTH_URL when HARLY_URL is absent. getHarlyPublicOrigin() omits BETTER_AUTH_URL. publicRedirectUrl() and publicBaseUrl() omit both fallbacks. A supported legacy deployment can therefore generate proxy-origin redirects or fail public URL generation.

  • apps/web/src/lib/public-origin.ts#L26-L30: add BETTER_AUTH_URL before DEFAULT_PUBLIC_ORIGIN.
  • apps/web/src/lib/public-origin.test.ts#L23-L44: add coverage for a BETTER_AUTH_URL-only configuration.
  • apps/web/src/proxy.ts#L68-L70: use the same configured-origin fallback chain before falling back to request.nextUrl.origin.
  • apps/web/src/server/scim/http.ts#L25-L26: use the same configured-origin fallback chain before using request.nextUrl.origin.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/public-origin.ts` around lines 26 - 30, Use the documented
configured-origin fallback chain consistently: update getHarlyPublicOrigin() in
apps/web/src/lib/public-origin.ts#L26-L30 to check BETTER_AUTH_URL after
NEXT_PUBLIC_APP_URL, add BETTER_AUTH_URL-only coverage in
apps/web/src/lib/public-origin.test.ts#L23-L44, and update
apps/web/src/proxy.ts#L68-L70 and apps/web/src/server/scim/http.ts#L25-L26 to
use the same chain before request.nextUrl.origin.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/app/layout.tsx`:
- Line 39: Ensure the public origin is resolved from the final HARLY_URL rather
than the build-time placeholder: update metadataBase in
apps/web/src/app/layout.tsx, resolve the origin inside robots() and export
dynamic = "force-dynamic" in apps/web/src/app/robots.ts, and make no change to
apps/web/src/app/sitemap.ts because its existing dynamic configuration already
handles this.

In `@apps/web/src/features/workspaces/actions.ts`:
- Around line 428-436: Move each getHarlyPublicOrigin() call in the affected
workspace actions before its corresponding membership insert, invitation insert,
or invitation-expiry update, so origin resolution completes before any database
mutation. Reuse the resolved appUrl when constructing the related WelcomeEmail
or invitation email URLs, including the flows around sendWorkspaceEmail at the
shown locations.

In `@packages/config/src/index.ts`:
- Around line 75-82: Update the production host validation in
packages/config/src/index.ts (75-82) to reject all 127.0.0.0/8 addresses and
hostnames ending in .localhost; update apps/web/src/lib/public-origin.ts (6-17)
to reject the full 127.0.0.0/8 range. Add production rejection cases for
https://127.0.0.2 and https://tenant.localhost in
packages/config/src/index.test.ts (28-38), and for https://127.0.0.2 in
apps/web/src/lib/public-origin.test.ts (23-44).

---

Outside diff comments:
In `@apps/web/src/lib/public-origin.ts`:
- Around line 26-30: Use the documented configured-origin fallback chain
consistently: update getHarlyPublicOrigin() in
apps/web/src/lib/public-origin.ts#L26-L30 to check BETTER_AUTH_URL after
NEXT_PUBLIC_APP_URL, add BETTER_AUTH_URL-only coverage in
apps/web/src/lib/public-origin.test.ts#L23-L44, and update
apps/web/src/proxy.ts#L68-L70 and apps/web/src/server/scim/http.ts#L25-L26 to
use the same chain before request.nextUrl.origin.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e4d26f3d-b751-4af9-92e1-04ef7f090871

📥 Commits

Reviewing files that changed from the base of the PR and between caf9d1f and 1a6fc30.

📒 Files selected for processing (44)
  • Dockerfile
  • apps/web/src/app/(dashboard)/settings/developers/page.tsx
  • apps/web/src/app/(dashboard)/settings/email/replies/page.tsx
  • apps/web/src/app/(dashboard)/settings/portal/page.tsx
  • apps/web/src/app/(fullscreen)/dashboard/jobs/[jobId]/page.tsx
  • apps/web/src/app/api/integrations/google/callback/route.ts
  • apps/web/src/app/api/integrations/outlook/callback/route.ts
  • apps/web/src/app/api/integrations/outlook/install/route.ts
  • apps/web/src/app/api/integrations/slack/callback/route.ts
  • apps/web/src/app/api/integrations/slack/install/route.ts
  • apps/web/src/app/api/integrations/zoom/callback/route.ts
  • apps/web/src/app/api/integrations/zoom/install/route.ts
  • apps/web/src/app/api/portal/auth/callback/github/route.ts
  • apps/web/src/app/api/portal/auth/callback/google/route.ts
  • apps/web/src/app/api/portal/auth/callback/linkedin/route.ts
  • apps/web/src/app/api/portal/auth/route.ts
  • apps/web/src/app/api/public/v1/image/presign/route.ts
  • apps/web/src/app/api/public/v1/resume/presign/route.test.ts
  • apps/web/src/app/api/public/v1/resume/presign/route.ts
  • apps/web/src/app/api/setup/claim/route.ts
  • apps/web/src/app/api/setup/complete/route.ts
  • apps/web/src/app/api/v1/openapi.json/route.ts
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/robots.ts
  • apps/web/src/app/sitemap.ts
  • apps/web/src/features/auth/actions.ts
  • apps/web/src/features/jobs/service.ts
  • apps/web/src/features/portal/actions.ts
  • apps/web/src/features/workspaces/ReplyHandlingSettingsCard.tsx
  • apps/web/src/features/workspaces/actions.ts
  • apps/web/src/features/workspaces/cal-settings-actions.ts
  • apps/web/src/lib/email/branding.ts
  • apps/web/src/lib/email/outbox-processor.ts
  • apps/web/src/lib/gcal/config.ts
  • apps/web/src/lib/passkey.ts
  • apps/web/src/lib/public-origin.test.ts
  • apps/web/src/lib/public-origin.ts
  • apps/web/src/proxy.ts
  • apps/web/src/server/notify/dispatch.ts
  • apps/web/src/server/notify/slack.ts
  • apps/web/src/server/scim/http.ts
  • packages/config/README.md
  • packages/config/src/index.test.ts
  • packages/config/src/index.ts

Comment thread apps/web/src/app/layout.tsx Outdated
Comment thread apps/web/src/features/workspaces/actions.ts Outdated
Comment thread packages/config/src/index.ts
@Molret
Molret merged commit 470c805 into main Aug 12, 2026
14 checks passed
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.

1 participant