Skip to content
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

Vercel deployment bug caused by per-route security config #538

Closed
maxdzin opened this issue Oct 22, 2024 · 12 comments · Fixed by #539 or #532
Closed

Vercel deployment bug caused by per-route security config #538

maxdzin opened this issue Oct 22, 2024 · 12 comments · Fixed by #539 or #532
Assignees
Labels
bug Something isn't working

Comments

@maxdzin
Copy link

maxdzin commented Oct 22, 2024

Hello!

I have a Nuxt project with per-route security config. By deploying it to Vercel it fails with this error:

Error: Builder returned invalid routes: should NOT have fewer than 1 properties

Local build works without errors, and it seems related to some Vercel-specific things.

I tried to dig in, but it is not yet clear to me why is that happening.
The problem relies on using the routeRules security configuration. When I define just some global config of this security module, everything works fine. But when I tried to define it per-route level, Vercel deployment failed with the error noted above. From this discussion I understand that it is due to some headers problem, but I'm not sure how the nuxt-security module should be configured properly by that.

Version

nuxt-security: v2.0.0
nuxt: v3.13.2

Reproduction Link

https://github.com/maxdzin/nuxt-security-config-test

Steps to reproduce

  1. Fork specified minimal reproduction repository.
  2. Deploy it on Vercel.

What is Expected?

The Vercel deployment went without any errors and works properly.

What is actually happening?

By deploying on Vercel the minimal Nuxt + nuxt-security project with per-route security config it fails with this error:

Error: Builder returned invalid routes: should NOT have fewer than 1 properties
@maxdzin maxdzin added the bug Something isn't working label Oct 22, 2024
@maxdzin maxdzin changed the title Vercel deployment bug caused by security config Vercel deployment bug caused by per-route security config Oct 22, 2024
@Baroshem
Copy link
Owner

Hey Buddy,

Thanks for the description and reproduction. I do think as well that it is a Vercel specific thing.

Your reproduction contains several examples of route Rules, have you tried decreasing the number of these rules to one simple one like headers.xXxsProtection = '1' and see if it will fail then as well?

I may find some time next week to look at it but if you will be faster I would try that to see if it is actually about the headers. Maybe it is related to other middleware functions that are set for certain route (like rate limiter) instead of headers and we don't know that because the repro repository contains several examples.

@maxdzin
Copy link
Author

maxdzin commented Oct 24, 2024

@Baroshem Greetings!

I tried so many options, and it seems the headers security settings in the per-route configuration exactly caused the deployment error. When I tried to add some corsHandler or rateLimiter middleware, it deployed fine (check the latest changes in the reproduction repo).

So, yes, it seems that is a specific Vercel thing of routeRules > security > headers config.

@maxdzin
Copy link
Author

maxdzin commented Oct 24, 2024

I also want to note that defining headers in the module's settings will not cause such a thing. Only security.headers in the per-route config caused that deployment error.
So the app with such config deployed well:
nuxt.config.ts

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  modules: ['nuxt-security'],

  compatibilityDate: '2024-04-03',

  devtools: { enabled: true },

  router: {
    options: {
      strict: true,
    },
  },

  routeRules: {
    '/': {
      security: {
        // headers: {
        //   contentSecurityPolicy: {
        //     'frame-ancestors': [
        //       "'self'",
        //     ],
        //   },
        // },
        corsHandler: {
          origin: [
            process.env.NUXT_PUBLIC_APP_BASE_URL as string,
          ],
        },
        rateLimiter: {
          tokensPerInterval: 8,
          interval: 10000,
        },
      },
    },
    '/test': {
      security: {
        // headers: {
        //   contentSecurityPolicy: {
        //     'frame-ancestors': [
        //       "'self'",
        //     ],
        //   },
        // },
        corsHandler: {
          origin: [
            process.env.NUXT_PUBLIC_APP_BASE_URL as string,
          ],
        },
        rateLimiter: {
          tokensPerInterval: 8,
          interval: 10000,
        },
      },
    },
    '/custom': {
      security: {
        // headers: {
        //   contentSecurityPolicy: {
        //     'frame-ancestors': [
        //       "'self'",
        //       'https://example.com',
        //       'https://www.example.com',
        //     ],
        //   },
        // },
        corsHandler: {
          origin: [
            process.env.NUXT_PUBLIC_APP_BASE_URL as string,
            'https://example.com',
            'https://www.example.com',
          ],
        },
        rateLimiter: {
          tokensPerInterval: 8,
          interval: 10000,
        },
      },
    },
    '/api/statistics/custom': {
      security: {
        corsHandler: {
          origin: ['https://example.com', 'https://www.example.com'],
        },
        rateLimiter: {
          tokensPerInterval: 8,
          interval: 10000,
        },
      },
    },
    '/api/statistics/entries': {
      security: {
        corsHandler: {
          origin: [process.env.NUXT_PUBLIC_APP_BASE_URL as string],
        },
        rateLimiter: {
          tokensPerInterval: 8,
          interval: 10000,
        },
      },
    },
  },

  security: {
    headers: {
      contentSecurityPolicy: {
        'img-src': [
          "'self'",
          'data:',
          'https://example.com',
        ],
      },
      crossOriginEmbedderPolicy:
        process.env.NODE_ENV === 'development' ? 'unsafe-none' : 'unsafe-none',
    },
    corsHandler: {
      origin: [process.env.NUXT_PUBLIC_APP_BASE_URL as string],
    },
    rateLimiter: {
      tokensPerInterval: 8,
      interval: 10000,
    },
  },
})

@Baroshem
Copy link
Owner

Hmm interesting @vejja do you have any ideas why it could work like that?

@vejja
Copy link
Collaborator

vejja commented Oct 24, 2024

@maxdzin : I am able to deploy your repro on Vercel without any issue
I don't get any error message. Where did you find that message & do you have some logs to share ?
Got it

@maxdzin
Copy link
Author

maxdzin commented Oct 24, 2024

@vejja Ah, sorry. I tried the successful config and forgot to revert it to the one that failed.
Now it is reverted and you can check the reproduction repo.

@maxdzin
Copy link
Author

maxdzin commented Oct 24, 2024

Something tells me that there could be a problem with the headers config merging from routeRules security. I'll try to research more a bit later.

@vejja
Copy link
Collaborator

vejja commented Oct 24, 2024

Something tells me that there could be a problem with the headers config merging from routeRules security. I'll try to research more a bit later.

You're right. This was exactly the case.
PR#539 should fix.
Very difficult for me to test specific edge cases introduced by Nitro presets for Vercel: if you are able to confirm that this resolves your issue, that would be fantastic 🙏🏻

@maxdzin
Copy link
Author

maxdzin commented Oct 24, 2024

@vejja there's an interesting thing...
I just got a response from Vercel support about adding referrerPolicy: 'no-referrer' into the per-route security headers config, which did the trick, and deployment works (take a look at the latest commit in the reproduction repo).
It is interesting that referrerPolicy: 'no-referrer' is added by default in headers, so maybe the problem relies in how is that put into specific route security config result.

Also, I looked at the module plugins, in particular, routeRules, and it seems there could be a problem with the headers result after merging, but I'm not yet sure (I need to investigate more). Also, it could be something in nitro/context/index.ts. I'll try to take a look when I have more time.

As for PR #539, I'm not sure how to test that PR's changes exactly, so maybe you can share some advice regarding it.

And, thank you for your help and quick response!

@vejja
Copy link
Collaborator

vejja commented Oct 24, 2024

Adding any random value into the per-route config, e.g. headers: { foo: 'bar' } does the trick.
The issue pops out when there is no value for headers in the route rule. Vercel then complains that the object is empty.
The PR fixes this.

As for testing, ideally you would clone from source and build the module locally, then replace it into the node_modules folder

@maxdzin
Copy link
Author

maxdzin commented Oct 24, 2024

Thank you, @vejja
I supposed that. I will do that soon and respond with the results.

@maxdzin
Copy link
Author

maxdzin commented Oct 24, 2024

@vejja I tested the PR fix and it seems works well - the headers specified are passed correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
3 participants