Skip to content

fix(web): unoptimize external next/image srcs instead of a host wildcard - #8501

Merged
pandeymangg merged 8 commits into
mainfrom
matti/eng-1678-stop-nextimage-from-allowing-arbitrary-remote-hosts
Jul 23, 2026
Merged

fix(web): unoptimize external next/image srcs instead of a host wildcard#8501
pandeymangg merged 8 commits into
mainfrom
matti/eng-1678-stop-nextimage-from-allowing-arbitrary-remote-hosts

Conversation

@mattinannt

@mattinannt mattinannt commented Jul 13, 2026

Copy link
Copy Markdown
Member

What

Removes the images.remotePatterns hostname: "**" wildcard from the Next.js image optimizer and replaces it with a model that is safe by default and needs no per-deployment configuration.

Why

With the wildcard, next/image would fetch and optimize an image from any HTTPS host — effectively an open proxy (SSRF-adjacent surface, bandwidth/CPU abuse, optimizer-cache poisoning). It can't simply be replaced with a static allowlist, because:

  • next.config (including remotePatterns) is frozen into the build — the same image serves app.formbricks.com, ksa.formbricks.com, and every self-hoster's own domain, so the running domain can't be baked in.
  • The wildcard's real purpose was arbitrary user-provided external image URLs (survey backgrounds, brand logos, picture-choice images), which are unknowable at build time.

How

The optimizer is only ever pointed at hosts we control; everything else is served directly (unoptimized), so the optimizer is never a proxy for hosts we don't control.

  • apps/web/lib/image-hosts.ts is the single source of truth:
    • OPTIMIZABLE_IMAGE_HOSTS — the universal provider/CDN hosts that are identical on every deployment (GitHub/Slack/Google avatars, Unsplash, the cloud CDN bucket). The deployment's own domain is intentionally not listed.
    • isExternalImageSrc(src)false (optimize) for relative paths, data: URIs, static imports, and allowlisted hosts; true (bypass the optimizer) for any other absolute URL.
  • next.config.mjs builds images.remotePatterns from OPTIMIZABLE_IMAGE_HOSTS.
  • Dynamic <Image> sites (backgrounds, logos, picture choices, file previews, integration icons) set unoptimized={isExternalImageSrc(src)}.

First-party uploads need no allowlist entry: they're served from same-origin /storage/... (relative) paths, which Next treats as local images (localPatterns, optimized by default) and never checks against remotePatterns. That's why the running domain doesn't need to be listed and the same build works on any domain.

Result: first-party /storage uploads and known providers are optimized; arbitrary external images render directly (no optimization, but no proxy risk); zero per-deployment config — identical behavior on both clouds and every self-host.

Validation

  • Production build bakes only the curated hosts, no ** (verified in .next/required-server-files.json).
  • pnpm build (12/12), pnpm lint, and pnpm test (22/22) pass. apps/web/lib/image-hosts.test.ts covers isExternalImageSrc.

Fixes ENG-1678

🤖 Generated with Claude Code

next.config.mjs built a curated images.remotePatterns allowlist and
then unconditionally appended { protocol: https, hostname: '**' },
turning the image optimizer into an open proxy (bandwidth/CPU abuse,
SSRF-adjacent surface, optimizer cache poisoning).

- remove the unconditional wildcard
- derive instance-specific hosts (WEBAPP_URL, PUBLIC_URL,
  S3_ENDPOINT_URL) from the environment, deduped against the allowlist
- uploaded files are unaffected: they are served from same-origin
  /storage/... paths which never consult remotePatterns
- deployments that render user-provided image URLs from arbitrary
  hosts can opt back in with IMAGES_ALLOW_ALL_HTTPS_HOSTS=1
  (documented in .env.example, hashed via turbo build.env)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Added a shared image-host allowlist and isExternalImageSrc classifier with tests. Next.js image remote patterns now derive from the allowlist, use HTTP for loopback hosts, and no longer include a wildcard HTTPS pattern. Multiple survey, workspace, whitelabel, and shared UI image components now set unoptimized for external sources. PUBLIC_URL was repositioned in the Turbo build environment list.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Title check ✅ Passed The title is concise, conventional, and accurately summarizes the main change to Next.js image handling.
Description check ✅ Passed The description clearly explains what changed, why, how it works, and includes validation results, though it does not match the template exactly.
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.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
turbo.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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.

@mattinannt
mattinannt marked this pull request as draft July 13, 2026 12:31
The previous approach rebuilt images.remotePatterns from WEBAPP_URL /
PUBLIC_URL / S3_ENDPOINT_URL at config-eval time plus an
IMAGES_ALLOW_ALL_HTTPS_HOSTS=1 opt-in wildcard. That can't work for
Formbricks' deployment model: next.config (incl. remotePatterns) is
frozen into the build (required-server-files.json), the same image
serves multiple domains (app./ksa.formbricks.com + every self-hoster),
and the wildcard's real purpose is arbitrary user-provided external
image URLs — unknowable at build time.

New model (ENG-1678):
- lib/image-hosts.ts is the single source of truth for optimizable
  hosts (universal provider/CDN hosts only; NOT the deployment domain).
  next.config builds remotePatterns from it via jiti.
- isExternalImageSrc() decides per <Image>: relative /storage paths and
  allowlisted hosts optimize; any other absolute URL renders
  , so the optimizer never proxies arbitrary hosts.
- Applied unoptimized to the ~14 dynamic <Image> sites (backgrounds,
  logos, picture choices, file previews, integration icons).
- Removed the env-derivation + wildcard, the app.formbricks.com entry,
  and IMAGES_ALLOW_ALL_HTTPS_HOSTS (turbo.json build.env + .env.example).

Works identically on both clouds and every self-host with zero config;
first-party /storage uploads (relative) still optimize via localPatterns
and never need the running domain in remotePatterns.

Verified: build bakes only the curated hosts, no '**'; pnpm lint/test/
build green; new lib/image-hosts.test.ts covers the predicate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 33605bfa81a8
@mattinannt mattinannt changed the title fix(web): stop next/image from optimizing arbitrary remote hosts fix(web): unoptimize external next/image srcs instead of a host wildcard Jul 21, 2026
No conflicts. Keeps this PR's isExternalImageSrc/unoptimized approach on
top of current main.

Entire-Checkpoint: 2bab7c2c0e4a
@mattinannt
mattinannt marked this pull request as ready for review July 21, 2026 12:34
formbricks-cdn.s3.eu-central-1.amazonaws.com is Formbricks-Cloud
infrastructure and is referenced only by test/mock fixtures (a
Playwright file-upload helper that fetch()es sample images, and
i18n.mock.ts) — no product feature serves images from it. Uploaded
files (cloud and self-hosted alike) are served same-origin via relative
/storage/... paths, so no deployment's S3 host ever needs to be in
remotePatterns. The allowlist now contains only universal provider
hosts real features use: OAuth avatars and the Unsplash picker.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 8e2ce98eae3f
@mattinannt

Copy link
Copy Markdown
Member Author

@coderabbitai please review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

@mattinannt: I’ll review the changes in #8501, with particular attention to the image-host allowlist behavior and environment-variable handling.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mattinannt
mattinannt requested a review from pandeymangg July 21, 2026 12:35

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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/modules/ui/components/media-background/index.tsx`:
- Line 119: Update both Image usages in
apps/web/modules/ui/components/media-background/index.tsx at lines 119-119 and
155-155: replace layout="fill" with fill, move objectFit into style while
preserving the existing cover and filter styling, and replace onLoadingComplete
with onLoad so both background image cases use modern Next.js props.
🪄 Autofix (Beta)

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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0a537c7-9a78-4379-920a-3acabf120297

📥 Commits

Reviewing files that changed from the base of the PR and between f40542c and 2920e11.

📒 Files selected for processing (17)
  • apps/web/app/(app)/workspaces/[workspaceId]/surveys/[surveyId]/(analysis)/summary/components/PictureChoiceSummary.tsx
  • apps/web/lib/image-hosts.test.ts
  • apps/web/lib/image-hosts.ts
  • apps/web/modules/ee/whitelabel/email-customization/components/email-customization-settings.tsx
  • apps/web/modules/ee/whitelabel/favicon-customization/components/favicon-customization-settings.tsx
  • apps/web/modules/survey/editor/components/logo-settings-card.tsx
  • apps/web/modules/survey/editor/components/option-ids.tsx
  • apps/web/modules/survey/editor/components/unsplash-images.tsx
  • apps/web/modules/ui/components/client-logo/index.tsx
  • apps/web/modules/ui/components/connect-integration/index.tsx
  • apps/web/modules/ui/components/file-input/index.tsx
  • apps/web/modules/ui/components/input-combo-box/index.tsx
  • apps/web/modules/ui/components/media-background/index.tsx
  • apps/web/modules/ui/components/picture-selection-response/index.tsx
  • apps/web/modules/workspaces/settings/look/components/edit-logo.tsx
  • apps/web/next.config.mjs
  • turbo.json

Comment thread apps/web/modules/ui/components/media-background/index.tsx
mattinannt and others added 4 commits July 21, 2026 14:41
SonarQube (S1874): the callable jiti(id) signature is deprecated. The
image-host allowlist is just a string array, so it doesn't need TS
transpilation at config load — moved it to a plain lib/optimizable-image-
hosts.mjs that next.config.mjs static-imports and lib/image-hosts.ts
re-exports. Single source of truth preserved; no jiti in this path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: a0faf2956b57
Both background <Image> usages used legacy next/image props that Next 16
deprecates (and layout/objectFit are ignored by the modern component):
- layout="fill" -> fill
- objectFit="cover" -> moved into style (merged with the existing filter)
- onLoadingComplete -> onLoad

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 3d04bff84ec5
@sonarqubecloud

Copy link
Copy Markdown

@pandeymangg
pandeymangg added this pull request to the merge queue Jul 23, 2026
Merged via the queue into main with commit 3d93676 Jul 23, 2026
17 checks passed
@pandeymangg
pandeymangg deleted the matti/eng-1678-stop-nextimage-from-allowing-arbitrary-remote-hosts branch July 23, 2026 07:48
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