<!-- Canonical: https://lucioduran.com/es/decisions/scoped-remote-image-patterns
Language: en
Content-SHA256: 9e915dfd35ef0a1093780d8e0790008e34a493d6d0437445dbbb7841cc105fd8
-->

ADR-016accepted20 de mayo de 2024

# Scoped Remote Image Patterns over Generic CDN Proxy

## Contexto

The portfolio displays images from multiple origins: profile photos and social cards from statics.lucioduran.com, dynamic content images from api.lucioduran.com, and the main domain lucioduran.com for statically hosted assets. Next.js Image component provides automatic optimization (WebP/AVIF conversion, responsive sizing, lazy loading) but requires explicit configuration of allowed remote image sources via remotePatterns in next.config.js. The configuration choice is between a permissive approach (allowing any hostname via a wildcard pattern) and a restrictive approach (explicitly listing each allowed origin). A wildcard pattern simplifies configuration but creates an open proxy, any external image URL processed through Next.js Image becomes an attack vector for SSRF (Server-Side Request Forgery), where an attacker could force the server to fetch internal network resources. For a publicly deployed site on Vercel, this could expose Vercel's internal infrastructure to probing.

## Decisión

Configure next.config.js remotePatterns with exactly three explicitly listed hostnames, all under the lucioduran.com domain: statics.lucioduran.com (static assets CDN), api.lucioduran.com (dynamic content images), and lucioduran.com (root domain assets). Each entry specifies protocol: 'https', no HTTP sources allowed. No wildcard patterns, no catch-all rules, no third-party hostnames. Images from any other origin must be downloaded and self-hosted before they can be optimized through Next.js Image. This creates a closed system where every image URL processed by the optimization pipeline is from a controlled, first-party origin.

## Consecuencias

Positive: The image optimization endpoint cannot be used as an SSRF proxy, only three known, controlled hostnames are processable. HTTPS-only enforcement prevents accidental mixed-content serving. The explicit listing serves as documentation of the site's image infrastructure, a new developer can immediately see which origins serve images. All three origins share a common root domain (lucioduran.com), making SSL certificate management consistent. The constraint forces discipline: every image must be hosted on owned infrastructure, preventing dependency on third-party image services that could change URLs or go offline. Negative: Adding a new image source (e.g., a profile photo from a third-party service, screenshots from external tools) requires a next.config.js change and redeploy. If a content entry references an image on an unlisted domain, the Next.js Image component falls back to an unoptimized img tag or errors, depending on configuration. The three-origin limitation means all images must be uploaded to owned infrastructure first, adding a manual step to the content authoring workflow.

## Incertidumbre Calibrada

## Predicciones al Momento de la Decisión

Expected the three-origin restriction to cover all current and near-future image sources. Predicted the manual upload step (images to owned infrastructure) would add negligible friction because image changes are infrequent. Assumed no third-party image sources would be needed.

## Resultados Medidos

The three-origin restriction has covered 100% of image needs for 20+ months. No third-party image source has been required. The manual upload step is negligible as predicted, images change approximately once per quarter. The ~60% size reduction from WebP optimization is consistent across all image types (profile photos, social cards, content images). The unexpected benefit: having all images on owned infrastructure means zero risk of broken images from third-party URL changes or service discontinuations, a problem that has affected previous projects with images hotlinked from external services.

## Incógnitas al Momento de la Decisión

Did not know whether future content types would require images from external origins (e.g., GitHub avatar URLs, certification badge images from third-party issuers). So far they haven't, all images are self-hosted. Also unknown: whether Vercel's image optimization pricing would change for free tier users. Currently unlimited, but usage-based pricing could make aggressive image optimization costly at scale.

## Clasificación de Reversibilidad

Puerta de Ida y Vuelta

Adding new origins to remotePatterns is a single line addition to next.config.js. Switching to a wildcard pattern (hostname: '\*\*') is a one-line change. Removing Image optimization entirely and using standard img tags requires replacing Next.js Image imports across components. Estimated effort for any pattern change: 5 minutes plus redeploy.

## Contra-Argumento Más Fuerte

The SSRF risk for a static portfolio site on Vercel is theoretical, there are no internal network resources to probe, and Vercel's infrastructure is not vulnerable to SSRF via image optimization endpoints. A wildcard pattern would simplify the workflow for adding new image sources and eliminate the manual upload step. For a single-developer project with full control over content, the security posture is arguably over-restrictive. The counter-counter: the restrictive pattern is not just about SSRF, it's about infrastructure ownership. Depending on external image hosts creates a silent availability risk that is harder to diagnose than a build failure.

## Contexto Técnico

Stack

- Next.js Image
- next.config.js remotePatterns
- WebP/AVIF
- Vercel Image Optimization

Allowed Origins

3

Protocol Restriction

HTTPS only

Wildcard Patterns

0

Avg Image Size Reduction

~60% (WebP vs PNG)

Restricciones

- No wildcard hostname patterns
- HTTPS only
- All origins must be first-party controlled

## Decisiones Relacionadas

- [ADR-015accepted

  Critical Rendering Path Optimization Strategy](<https://lucioduran.com/es/decisions/critical-rendering-path-optimization>)
- [ADR-017accepted

  Defense-in-Depth HTTP Security Headers](<https://lucioduran.com/es/decisions/defense-in-depth-security-headers>)

[Volver a Decisiones](<https://lucioduran.com/es/decisions>)
