NextJS build ignoring headers set in netlify.toml

I’m trying to configure better security for the pages we’re serving out of Netlify by enabling X-Frame-Optons, XSS, and CSP.

The site is running off a development branch and can be found
Custom Domain: https://gallery.nftx.design/
Netlify Domain: https://quirky-ritchie-40c445.netlify.app/

The netlify.toml file looks like this

[[headers]]
  for = "/*"
  [headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
Content-Security-Policy = "default-src 'none'; script-src 'self' www.googletagmanager.com www.google-analytics.com; connect-src 'self' nftx.ethereumdb.com; img-src 'self' cms.nftx.xyz res.cloudinary.com; style-src 'self';base-uri 'self';form-action 'self'"

[[redirects]]
  from = "/live"
  to = "https://gallery.nftx.org"
  status = 200
  headers = {X-From = "Netlify"}

  [[redirects]]
  from = "/javery"
  to = "https://justinavery.me"
  status = 301
  headers = {X-From = "Netlify"}

The build works find and we can access the pages.

If I go to the homepage there are none of the headers that are provided. To test whether the netlify.toml file was being picked up at all I added in the redirect options. If you visit either of the redirect pages, the headers for xframe, xss, and csp that are set work perfectly, but any other pages that are served do not have those headers set.

The deploy output logs are below

12:20:19 PM: Build ready to start
12:20:22 PM: build-image version: 3571f0130496395a23bffe9820bc78b4f73a6234
12:20:22 PM: build-image tag: v3.7.0
12:20:22 PM: buildbot version: 945e22ac61fb3a0e5ec8bebfe91c852809f9f21b
12:20:22 PM: Fetching cached dependencies
12:20:22 PM: Starting to download cache of 194.4MB
12:20:23 PM: Finished downloading cache in 1.080102934s
12:20:23 PM: Starting to extract cache
12:20:34 PM: Finished extracting cache in 11.056909138s
12:20:34 PM: Finished fetching cache in 12.203175846s
12:20:34 PM: Starting to prepare the repo for build
12:20:35 PM: Preparing Git Reference refs/heads/develop
12:20:35 PM: Parsing package.json dependencies
12:20:37 PM: Starting build script
12:20:37 PM: Installing dependencies
12:20:37 PM: Python version set to 2.7
12:20:38 PM: Started restoring cached node version
12:20:42 PM: Finished restoring cached node version
12:20:43 PM: v12.18.0 is already installed.
12:20:44 PM: Now using node v12.18.0 (npm v6.14.4)
12:20:44 PM: Started restoring cached build plugins
12:20:44 PM: Finished restoring cached build plugins
12:20:44 PM: Attempting ruby version 2.7.1, read from environment
12:20:46 PM: Using ruby version 2.7.1
12:20:47 PM: Using PHP version 5.6
12:20:47 PM: Started restoring cached yarn cache
12:20:47 PM: Finished restoring cached yarn cache
12:20:48 PM: Started restoring cached node modules
12:20:48 PM: Finished restoring cached node modules
12:20:48 PM: Installing NPM modules using Yarn version 1.22.4
12:20:49 PM: yarn install v1.22.4
12:20:49 PM: [1/4] Resolving packages...
12:20:51 PM: success Already up-to-date.
12:20:51 PM: Done in 1.89s.
12:20:51 PM: NPM modules installed using Yarn
12:20:51 PM: Started restoring cached go cache
12:20:51 PM: Finished restoring cached go cache
12:20:52 PM: go version go1.14.4 linux/amd64
12:20:52 PM: go version go1.14.4 linux/amd64
12:20:52 PM: Installing missing commands
12:20:52 PM: Verify run directory
12:20:54 PM: ​
12:20:54 PM: ────────────────────────────────────────────────────────────────
12:20:54 PM:   Netlify Build                                                 
12:20:54 PM: ────────────────────────────────────────────────────────────────
12:20:54 PM: ​
12:20:54 PM: ❯ Version
12:20:54 PM:   @netlify/build 9.13.0
12:20:54 PM: ​
12:20:54 PM: ❯ Flags
12:20:54 PM:   apiHost: api.netlify.com
12:20:54 PM:   cacheDir: /opt/build/cache
12:20:54 PM:   deployId: 605b2e836c56f700075b458a
12:20:54 PM:   mode: buildbot
12:20:54 PM:   testOpts:
12:20:54 PM:     silentLingeringProcesses: ''
12:20:54 PM: ​
12:20:54 PM: ❯ Current directory
12:20:54 PM:   /opt/build/repo
12:20:54 PM: ​
12:20:54 PM: ❯ Config file
12:20:54 PM:   /opt/build/repo/netlify.toml
12:20:54 PM: ​
12:20:54 PM: ❯ Context
12:20:54 PM:   production
12:20:54 PM: ​
12:20:54 PM: ❯ Loading plugins
12:20:54 PM:    - @netlify/plugin-nextjs@3.0.3 from Netlify app
12:20:55 PM: ​
12:20:55 PM: ────────────────────────────────────────────────────────────────
12:20:55 PM:   1. onPreBuild command from @netlify/plugin-nextjs             
12:20:55 PM: ────────────────────────────────────────────────────────────────
12:20:55 PM: ​
12:20:55 PM: ​
12:20:55 PM: (@netlify/plugin-nextjs onPreBuild completed in 40ms)
12:20:55 PM: ​
12:20:55 PM: ────────────────────────────────────────────────────────────────
12:20:55 PM:   2. Build command from Netlify app                             
12:20:55 PM: ────────────────────────────────────────────────────────────────
12:20:55 PM: ​
12:20:55 PM: $ yarn build
12:20:55 PM: yarn run v1.22.4
12:20:55 PM: $ next build
12:20:57 PM: warn  - No build cache found. Please configure build caching for faster rebuilds. Read more: https://err.sh/next.js/no-cache
12:20:57 PM: info  - Creating an optimized production build...
12:21:54 PM: warn  - Compiled with warnings
12:21:54 PM: ./node_modules/next/dist/next-server/server/load-components.js
12:21:54 PM: Critical dependency: the request of a dependency is an expression
12:21:54 PM: ./node_modules/next/dist/next-server/server/load-components.js
12:21:54 PM: Critical dependency: the request of a dependency is an expression
12:21:54 PM: ./node_modules/next/dist/next-server/server/require.js
12:21:54 PM: Critical dependency: the request of a dependency is an expression
12:21:54 PM: ./node_modules/next/dist/next-server/server/require.js
12:21:54 PM: Critical dependency: the request of a dependency is an expression
12:21:54 PM: ./node_modules/next/dist/next-server/server/require.js
12:21:54 PM: Critical dependency: the request of a dependency is an expression
12:21:54 PM: ./node_modules/node-fetch/lib/index.js
12:21:54 PM: Module not found: Can't resolve 'encoding' in '/opt/build/repo/node_modules/node-fetch/lib'
12:21:54 PM: info  - Collecting page data...
12:21:56 PM: info  - Generating static pages (0/8)
12:21:58 PM: info  - Generating static pages (2/8)
12:21:58 PM: info  - Generating static pages (4/8)
12:21:59 PM: info  - Generating static pages (6/8)
12:21:59 PM: info  - Generating static pages (8/8)
12:21:59 PM: info  - Finalizing page optimization...
12:21:59 PM: Page                                                           Size     First Load JS
12:21:59 PM: ┌ λ /                                                          2.52 kB         114 kB
12:21:59 PM: ├   /_app                                                      0 B            95.8 kB
12:21:59 PM: ├ ○ /404                                                       1.21 kB          97 kB
12:21:59 PM: ├ λ /collections                                               2.98 kB         114 kB
12:21:59 PM: ├ λ /collections/[collection]                                  3.71 kB         115 kB
12:21:59 PM: ├ ○ /funds                                                     3.35 kB         114 kB
12:21:59 PM: ├ ○ /funds/[fund]                                              6.21 kB         117 kB
12:21:59 PM: └ ○ /funds/[fund]/[asset]                                      4.55 kB         116 kB
12:21:59 PM: + First Load JS shared by all                                  95.8 kB
12:21:59 PM:   ├ chunks/commons.159a23.js                                   15.3 kB
12:21:59 PM:   ├ chunks/de3257859699bd07530b444bb691db27aa14c49c.976484.js  20.5 kB
12:21:59 PM:   ├ chunks/framework.e25a57.js                                 42.3 kB
12:21:59 PM:   ├ chunks/main.81d0fc.js                                      6.46 kB
12:21:59 PM:   ├ chunks/pages/_app.1100ad.js                                9.97 kB
12:21:59 PM:   ├ chunks/webpack.461e3d.js                                   1.29 kB
12:21:59 PM:   └ css/7385eb6fabd3ac1ce1bd.css                               4.38 kB
12:21:59 PM: λ  (Lambda)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
12:21:59 PM: ○  (Static)  automatically rendered as static HTML (uses no initial props)
12:21:59 PM: ●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
12:21:59 PM:    (ISR)     incremental static regeneration (uses revalidate in getStaticProps)
12:21:59 PM: Done in 63.57s.
12:21:59 PM: ​
12:21:59 PM: (build.command completed in 1m 3.9s)
12:21:59 PM: ​
12:21:59 PM: ────────────────────────────────────────────────────────────────
12:21:59 PM:   3. onBuild command from @netlify/plugin-nextjs                
12:21:59 PM: ────────────────────────────────────────────────────────────────
12:21:59 PM: ​
12:21:59 PM: ** Running Next on Netlify package **
12:21:59 PM: 🚀 Next on Netlify 🚀
12:21:59 PM: 🌍️ Copying public/ folder to out
12:21:59 PM: 💼 Copying static NextJS assets to out
12:21:59 PM: 💫 Setting up API endpoints as Netlify Functions in netlify/functions
12:21:59 PM: 💫 Setting up pages with getInitialProps as Netlify Functions in netlify/functions
12:21:59 PM: 💫 Setting up pages with getServerSideProps as Netlify Functions in netlify/functions
12:21:59 PM: 🔥 Copying pre-rendered pages with getStaticProps and JSON data to out
12:21:59 PM: 💫 Setting up pages with getStaticProps and fallback: true as Netlify Functions in netlify/functions
12:21:59 PM: 💫 Setting up pages with getStaticProps and revalidation interval as Netlify Functions in netlify/functions
12:21:59 PM: 🔥 Copying pre-rendered pages without props to out
12:21:59 PM: 🔀 Setting up redirects
12:21:59 PM: 🔀 Setting up headers
12:21:59 PM: ✅ Success! All done!
12:21:59 PM: ​
12:21:59 PM: (@netlify/plugin-nextjs onBuild completed in 116ms)
12:21:59 PM: ​
12:21:59 PM: ────────────────────────────────────────────────────────────────
12:21:59 PM:   4. Functions bundling                                         
12:21:59 PM: ────────────────────────────────────────────────────────────────
12:21:59 PM: ​
12:21:59 PM: Packaging Functions from netlify/functions directory:
12:21:59 PM:  - next_image.js
12:21:59 PM:  - next_collections/next_collections.js
12:21:59 PM:  - next_collections_collection/next_collections_collection.js
12:21:59 PM:  - next_index/next_index.js
12:21:59 PM: ​
12:22:26 PM: ​
12:22:26 PM: (Functions bundling completed in 26.9s)
12:22:26 PM: ​
12:22:26 PM: ────────────────────────────────────────────────────────────────
12:22:26 PM:   5. Deploy site                                                
12:22:26 PM: ────────────────────────────────────────────────────────────────
12:22:26 PM: ​
12:22:26 PM: Starting to deploy site from 'out'
12:22:26 PM: Creating deploy tree 
12:22:26 PM: Creating deploy upload records
12:22:27 PM: 5 new files to upload
12:22:27 PM: 3 new functions to upload
12:22:28 PM: Site deploy was successfully initiated
12:22:28 PM: ​
12:22:28 PM: (Deploy site completed in 2.1s)
12:22:28 PM: ​
12:22:28 PM: ────────────────────────────────────────────────────────────────
12:22:28 PM:   Netlify Build Complete                                        
12:22:28 PM: ────────────────────────────────────────────────────────────────
12:22:28 PM: ​
12:22:28 PM: Starting post processing
12:22:28 PM: (Netlify Build completed in 1m 34s)
12:22:28 PM: Post processing - HTML
12:22:28 PM: Caching artifacts
12:22:28 PM: Started saving node modules
12:22:28 PM: Finished saving node modules
12:22:28 PM: Started saving build plugins
12:22:28 PM: Finished saving build plugins
12:22:28 PM: Started saving yarn cache
12:22:28 PM: Finished saving yarn cache
12:22:28 PM: Started saving pip cache
12:22:29 PM: Finished saving pip cache
12:22:29 PM: Started saving emacs cask dependencies
12:22:29 PM: Finished saving emacs cask dependencies
12:22:29 PM: Started saving maven dependencies
12:22:29 PM: Finished saving maven dependencies
12:22:29 PM: Started saving boot dependencies
12:22:29 PM: Finished saving boot dependencies
12:22:29 PM: Post processing - header rules
12:22:29 PM: Started saving rust rustup cache
12:22:29 PM: Finished saving rust rustup cache
12:22:29 PM: Started saving go dependencies
12:22:29 PM: Finished saving go dependencies
12:22:29 PM: Build script success
12:22:29 PM: Post processing - redirect rules
12:22:29 PM: Post processing done
12:22:29 PM: Site is live ✨
12:23:17 PM: Finished processing build request in 2m55.178915454s
1 Like

Another thing to point out is that the static files, CSS JS etc, are being served with the correct headers but not the document (webpage) itsef.

1 Like

Inside of the next.config.js file I’ve also configured headers using the following.

When I yarn build and npm start locally it’s serving the right headers, but when it deploys off the develop branch from Netlify I’m not seeing the updates.

async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'X-XSS-Protection',
            value: '1; mode=block',
          },
          {
            key: 'Content-Security-Policy',
            value:
              "default-src 'none'; script-src 'self' 'unsafe-inline' www.googletagmanager.com www.google-analytics.com; font-src 'self'; connect-src 'self' nftx.ethereumdb.com api.covalenthq.com api.opensea.io www.google-analytics.com stats.g.doubleclick.net; img-src 'self' cms.nftx.xyz res.cloudinary.com www.google-analytics.com lh3.googleusercontent.com www.google.com www.google.co.uk; style-src 'self' 'unsafe-inline'; base-uri 'self';form-action 'self'",
          },
        ],
      },
    ];
  },
1 Like

I can confirm, I’m having the exact same problem. I couldn’t believe it when I saw your issue, opened only 24 hours ago.

I’m in the same case - tried to improve my security, but the original document isn’t served with the correct header. All other requests are fine, only the original (most important) document doesn’t have the correct headers.

Since we both got this issue right now and nobody got it before, maybe it’s a regression?
I also tried using _headers, but it’s not working.

1 Like

I have also tried to add the next-secure-headers to the nextjs build along with the following in the next.config.js file

// /next.config.js
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { createSecureHeaders } = require('next-secure-headers');

module.exports = {
  async headers() {
    return [{ source: '/(.*)', headers: createSecureHeaders() }];
  },

  // Enable this when we're actually using the nextjs server and not static
  i18n: {
    locales: ['en'],
    defaultLocale: 'en',
  },
  trailingSlash: true,
  env: {
    appVersion: process.env.npm_package_version,
  },
  // for @netlify/plugin-next-js
  target: 'serverless',
};

Again, running on local it works perfectly, but it doesn’t work when I deploy to Netlify.

1 Like

Exact same problem here… Would be great to have a fix for it! :slightly_smiling_face:

2 Likes

Some further points for this…

  1. Land on app homepage - https://quirky-ritchie-40c445.netlify.app/ : no CSP or headers
  2. Navigate to a link on the page - https://quirky-ritchie-40c445.netlify.app/funds/punk-basic/ - no request so still no headers on the page.
  3. Refresh the https://quirky-ritchie-40c445.netlify.app/funds/punk-basic/ page, CSP and other headers are now set/enabled.

So landing on any pages that are not the main app page, or viewing any files, will serve the asset with the right headers applied.

The main app page, or any content navigated to via that page do not send the right headers.

1 Like

Exact same problem on my end ! Would be great to have a fix soon !

1 Like

Hey there, folks! Thanks for writing in and sharing this feedback. I am taking these questions to a member of our team so that they can dig in further. I appreciate your patience.

2 Likes

Thanks @hillary, do you know what the eta might be on this?

Hey there, @justincavery :wave:

I have a meeting about this today, actually! Stay tuned! Thanks for following up :slight_smile:

1 Like

A visual demonstration for your meeting :slight_smile:

Homepage :sob:

Any other page :grinning:

1 Like

Hey there, folks! Thanks for your patience here.

I have brought this issue to the Director of Support as well as some NextJS pros at Netlify. Below I have outlined information relevant to the questions above:

  1. Though when running locally _headers could cover such routes, it is intended at Netlify that custom _headers in that file or in netlify.toml do not cover routes we proxy to another service, we route traffic to functions via a reverse proxy. This limitation is described in our documentation. I encourage you to take a look!

  2. Much of the less-static content in next.js sites is served via functions.

  3. If you want all of those functions to use your headers, you’d need to return all of their headers and other HTTP response from the lambda itself. Essentially, the headers would need to be added to the response from those functions directly, at run time.

  4. We currently don’t support tailoring responses using next.config.js . There is an open issue on the topic in our codebase to implement this, here: Support experimental next.config.js redirects and rewrites · Issue #150 · netlify/netlify-plugin-nextjs · GitHub. As this is open source, feel free to chime in on the issue.

  5. Lastly, I don’t have any great suggestions about “how to add those headers to those lambdas’ code”. I wish I had better news! However, I can tell you that brainstorming in that issue will have the eyes of the dev team for our next implementation, in case any method today can be found. But, our Support team, after consulting with the lead of that dev team about this thread, do not know of one.

Thanks again for your patience on this. I will keep my eyes peeled for responses to this thread!

It’s a shame this feature isn’t available. It basically prevent HSTS support on Netlify, which is the new security standard for modern websites.
No Netlify website can be on Google’s HSTS preload list (which is also used by Firefox & Safari) then. For reference, i’m talking about https://hstspreload.org/.

This also make this whole section of the documentation false, which mean that this was written without being tested at any point (at least for NextJS).

Finally, it makes it impossible to implement tons of security rules on our websites. This gives terrible scores on security-related checking tools, like https://observatory.mozilla.org. To name a few security rules that we can’t define:

  • Content Security Policy. Makes us vulnerable on cross-site scripting (XSS) and clickjacking attacks.
  • X-Content-Type-Options. Makes us vulnerable on XSS attacks.
  • X-Frame-Options. Makes us vulnerable on clickjacking attacks, allowing malicious sites to trick users into clicking links on our site (even though they may appear to not be on our site at all). To cite MDN:

As such, the use of the X-Frame-Options header is mandatory for all new websites, and all existing websites are expected to add support for X-Frame-Options as soon as possible.

  • X-XSS-Protection. Stops pages from loading when they detect reflected cross-site scripting (XSS) attacks. Mostly uneccessary when Content-Security-Policy is defined, but we can’t define it :slight_smile:

On the other end, never had the same problem with Vercel. The security implications are huge for any NextJS website hosted on Netlify. But you want a few weeks to brainstorm the issue? There is no issue to brainstorm, it’s a security problem that requires immediate fix.

This isn’t up to my client’s security standards, so I will move on to Vercel for the time being. Thanks for the quick answer though.

I agree with the comments from @TheMrZZ , however I’m not convinced this is a widespread issue that can’t be solved.

I’ve used this repo GitHub - cassidoo/next-netlify-starter: A one-click starter project for Next and Netlify to get started with a next.js site and it is hosted here https://stupefied-boyd-1f4090.netlify.app/

By default, the netlify.toml file has the following

[build]
  command = "npm run build"
  publish = "out"

however I updated that with the same details that I have in my other toml file for the site headers I’m having issues with, and the new file now looks like this

[build]
  command = "npm run build"
  publish = "out"


[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
    Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' www.googletagmanager.com www.google-analytics.com; connect-src 'self' nftx.ethereumdb.com api.opensea.io api.covalenthq.com www.google-analytics.com; img-src 'self' cms.nftx.xyz res.cloudinary.com lh3.googleusercontent.com storage.opensea.io www.google-analytics.com; style-src 'self' 'unsafe-inline' ;base-uri 'self';form-action 'self'; font-src 'self'; prefetch-src 'self' 'unsafe-inline'; manifest-src 'self'"

With this change and a refresh on that site I now have the right headers set for optimal security on the landing page, however, on my original site the first view doesn’t have the security headers but the subsequent pages viewed do contain them.

The difference between the two implementations seems to be only the [build] command that comes shipped with the project.

This post was flagged by the community and is temporarily hidden.