Skip to content

fix: use allowlist for remote images #330

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ Similarly, you can customize your publish directory in your `netlify.toml` file:

Read more about [Netlify's build settings](https://docs.netlify.com/configure-builds/get-started/#basic-build-settings) in our docs.

## Image handling

Choose a reason for hiding this comment

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

do you wanna add an anchor to this with the other section headings at the top? :)


The plugin includes a function to generate images for `next/image`. The images are resized on the fly, so the first request will have a short delay. However because the function uses [On-Demand Builders](https://docs.netlify.com/configure-builds/on-demand-builders/), any subsequent requests for that image are served from the edge cache and are super-fast.

By default, images are returned in the same format as the source image if they are in JPEG, PNG, WebP or AVIF format. If you are only targeting modern browsers and want to live life on the edge, you can [set the environment variable](https://docs.netlify.com/configure-builds/environment-variables/) `FORCE_WEBP_OUTPUT` to `"true"`, and it will return all images in WebP format. This will often lead to significant improvements in file size. However you should not use this if you need to support older browsers, as `next/image` does not support picture tag source fallback and images will appear broken. Check [browser support](https://caniuse.com/webp) to see if you are happy to do this.

Choose a reason for hiding this comment

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

live life on the ledge lul


If you want to use remote images in `next/image`, you will need to add the image domains to an allow list. Setting them in `images.domains` in `next.config.js` is not supported: instead you should set the environment variable `NEXT_IMAGE_ALLOWED_DOMAINS` to a comma-separated list of domains, e.g. `NEXT_IMAGE_ALLOWED_DOMAINS="placekitten.com,unsplash.com"`.

Choose a reason for hiding this comment

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

o


## Custom Netlify Redirects

You can define custom redirects in a `_redirects` file.
Expand Down
8 changes: 8 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ module.exports = {

// Because we memoize nextConfig, we need to do this after the write file
const nextConfig = await getNextConfig(utils.failBuild)

if (nextConfig.images.domains.length !== 0 && !process.env.NEXT_IMAGE_ALLOWED_DOMAINS) {
console.log(
`Image domains set in next.config.js are ignored.\nPlease set the env variable NEXT_IMAGE_ALLOWED_DOMAINS to "${nextConfig.images.domains.join(
',',
)}" instead`,
)

Choose a reason for hiding this comment

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

noice

}
await restoreCache({ cache: utils.cache, distDir: nextConfig.distDir })
},
async onBuild({
Expand Down
23 changes: 19 additions & 4 deletions src/lib/templates/imageFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,24 @@ const handler = async (event) => {

const quality = parseInt(q) || 60

const imageUrl = parsedUrl.startsWith('/')
? `${process.env.DEPLOY_URL || `http://${event.headers.host}`}${parsedUrl}`
: parsedUrl
let imageUrl
// Relative image
if (parsedUrl.startsWith('/')) {
imageUrl = `${process.env.DEPLOY_URL || `http://${event.headers.host}`}${parsedUrl}`
} else {
// Remote images need to be in the allowlist
const allowedDomains = process.env.NEXT_IMAGE_ALLOWED_DOMAINS
? process.env.NEXT_IMAGE_ALLOWED_DOMAINS.split(',').map((domain) => domain.trim())
: []

if (!allowedDomains.includes(new URL(parsedUrl).hostname)) {
return {
statusCode: 403,
body: 'Image is not from a permitted domain',
}
}
imageUrl = parsedUrl
}

const imageData = await fetch(imageUrl)

Expand Down Expand Up @@ -68,7 +83,7 @@ const handler = async (event) => {
}
}

if (process.env.FORCE_WEBP_OUTPUT) {
if (process.env.FORCE_WEBP_OUTPUT === 'true' || process.env.FORCE_WEBP_OUTPUT === '1') {
ext = 'webp'
}

Expand Down