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

Cross-site POST form submissions are forbidden #6589

Closed
asoltys opened this issue Sep 5, 2022 · 35 comments · Fixed by MikeBild/sveltekit-adapter-aws#10
Closed

Cross-site POST form submissions are forbidden #6589

asoltys opened this issue Sep 5, 2022 · 35 comments · Fixed by MikeBild/sveltekit-adapter-aws#10

Comments

@asoltys
Copy link

asoltys commented Sep 5, 2022

Describe the bug

I'm getting an error message about a cross-site request when submitting a form to a relative URL that's handled by an endpoint on the same server.

The error only occurs when running the production build with the node-adapter, not with the dev server.

Reproduction

https://github.com/asoltys/svelte-cross-site-repro

Logs

Cross-site POST form submissions are forbidden

System Info

System:
    OS: Linux 5.19 Pop!_OS 22.04 LTS
    CPU: (12) x64 Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
    Memory: 4.55 GB / 15.35 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 18.7.0 - ~/.local/share/pnpm/node
    npm: 8.15.0 - ~/.local/share/pnpm/npm
  Browsers:
    Chrome: 104.0.5112.101
    Firefox: 103.0
  npmPackages:
    @sveltejs/adapter-auto: next => 1.0.0-next.71 
    @sveltejs/adapter-node: 1.0.0-next.88 => 1.0.0-next.88 
    @sveltejs/kit: next => 1.0.0-next.470 
    svelte: ^3.44.0 => 3.50.0 
    vite: ^3.1.0 => 3.1.0

Severity

serious, but I can work around it

Additional Information

No response

@Conduitry
Copy link
Member

How are you running the server? I would bet the issue is that you're not using https://github.com/sveltejs/kit/tree/master/packages/adapter-node#environment-variables to tell the server what origin it's serving from. Note that in particular in production the Node adapter will assume by default that it has an HTTPS proxy in front of it and that its origin is https://[host header], not http://[host header].

@asoltys
Copy link
Author

asoltys commented Sep 5, 2022

Thanks, I did have it behind an https proxy but something must have been off because setting the ORIGIN env var explicitly helped.

@xpluscal
Copy link

xpluscal commented Dec 6, 2022

So how is the ORIGIN env var being pulled in?
Does it have to be present at build and or runtime?
From the docs it says running this during build, but still having it not taking effect in the live build on the site.

Any help appreciated.

@kyllerss
Copy link

kyllerss commented Dec 9, 2022

Same issue here.

@nevermindthelabel
Copy link

@kyllerss @xpluscal what I have done that has been working is in your run command (this is from my Dockerfile) CMD ORIGIN=https://whateverYourProdDomain node build

@kyllerss
Copy link

@nevermindthelabel I'm not familiar w/ the node build command. Are you building it into your docker image? In my case, I have mounted a local directory to my docker image. I'm using vite build to generate the build. Then the command I'm using to run it is vite preview --host.

I've tried prefixing both the build step and the execution step w/ the environment variable but no luck. Does the env variable need to be part of the build to be compiled into the output? Does vite build do something to prevent the env variable from being passed through to the underlying build step?

@nevermindthelabel
Copy link

@nevermindthelabel I'm not familiar w/ the node build command. Are you building it into your docker image? In my case, I have mounted a local directory to my docker image. I'm using vite build to generate the build. Then the command I'm using to run it is vite preview --host.

I've tried prefixing both the build step and the execution step w/ the environment variable but no luck. Does the env variable need to be part of the build to be compiled into the output? Does vite build do something to prevent the env variable from being passed through to the underlying build step?

I should also say that I am using adapter-node. That builds the build directory, so under the hood it is running node build/index.js.

You need to pass the origin as an argument at runtime, and if you are doing it from docker you might need to do -e origin=http://localhost:port or wherever the sveltekit app is running.

@Bishwas-py
Copy link

Cross-site POST form submissions are forbidden

Use @sveltejs/adapter-node adapter, and pass ORIGIN=https://yoursite.com environment variable while deploying/building it. Here's a proper solution for it.

@na2axl
Copy link

na2axl commented Dec 15, 2022

Can someone please know how to handle this if the app is served from multiple origins (subdomains like *.mysite.com) ?

@bitdom8
Copy link

bitdom8 commented Dec 24, 2022

Same here, demand to use import adapter from '@sveltejs/adapter-auto'; since it builds half of the size

@trulycool
Copy link

Encountered and solved this issue; full details down towards bottom of thread:
MikeBild/sveltekit-adapter-aws#10

Just putting it down here as I'd visited earlier and somehow missed following that link at the top indicating [that link] had closed this issue...

@danpaldev
Copy link

And while on development how can I set the ORIGIN env variable?

I used VITE_ORIGIN and ORIGIN in my .env file and both seem to have no effect.

@Destaq
Copy link

Destaq commented May 22, 2023

And while on development how can I set the ORIGIN env variable?

I used VITE_ORIGIN and ORIGIN in my .env file and both seem to have no effect.

Looks like that's still an open issue: #8026

@giusecapo
Copy link

I'm using adapter-vercel since my app's running on vercel and I have this issue... any solutions for the ones that doesn't use adapter-node?

@joecorsi
Copy link

joecorsi commented Jul 18, 2023

im using import adapter from '@sveltejs/adapter-auto'; and deploying to vercel and im still seeing this issue. any help?

@geoffrich
Copy link
Member

@joecorsi if you're having this issue on Vercel, then this is a different issue. Please open a separate issue with a minimal reproduction.

@joecorsi
Copy link

@geoffrich thanks for reply. the issue i was having was just local. once deployed i dont have this issue. adding this to svelte.config.js while developing solves the issue:

csrf: { checkOrigin: false, }

@stav
Copy link

stav commented Dec 6, 2023

I'm just previewing on localhost but I added a .env file like in the docs and that helped me:

ORIGIN=http://localhost:3000

@laszlo1337
Copy link

Does anybody know if ORIGIN env variable can have some kind of wildcard or something?
What I need to achieve is I am hosting my app on mydomain.pl and mydomain.com will point to the instance on mydomain.pl. So I need ORIGIN to handle both cases - it doesn't infer it automatically for some reason

@devtrix
Copy link

devtrix commented Dec 22, 2023

I'm just previewing on localhost but I added a .env file like in the docs and that helped me:

ORIGIN=http://localhost:3000

Thank you for being specific and not sending me on a wild goose chase.

@ifduyue
Copy link

ifduyue commented Jan 16, 2024

How to handle multiple ORIGINs?

@FarhanAliRaza
Copy link

For anybody new coming to this issue looking for a solution.
ORIGIN=https://yoursite.com node ./build/index.js
.. this is the solution you need to run when running your server from the downlink.

@laszlo1337
Copy link

How to handle multiple ORIGINs?

Perhaps you could create a new issue with this question? I will be needing this in the future maybe :)

@stefanosandes
Copy link

For anybody new coming to this issue looking for a solution. ORIGIN=https://yoursite.com node ./build/index.js .. this is the solution you need to run when running your server from the downlink.

So, this broke any app that responds on multiple domains, right?

@stefanosandes
Copy link

For anybody new coming to this issue looking for a solution. ORIGIN=https://yoursite.com node ./build/index.js .. this is the solution you need to run when running your server from the downlink.

So, this broke any app that responds on multiple domains, right?

To add some context, I have an application behind a "proxy" (rewrites on vercel.json) on Vercel. When I try to submit a form, I get this response: {"message":"Cross-site POST form submissions are forbidden"}. The problem with this solution is that this app will respond to many custom domains, so hardcoding a specific domain is not a solution.

@acoyfellow
Copy link

@stefanosandes did you ever come up with a solution? running into this same issue now.

@stefanosandes
Copy link

@stefanosandes did you ever come up with a solution? running into this same issue now.

No, unfortunately.

@milansimek
Copy link

In my case the issue was present at local dev environment but not on production. My dev environment runs behind Nginx reverse proxy.

The way I fixed it is by replacing https:// with http:// in the Origin header:

 proxy_set_header Origin http://$http_host;

Something similar might work with other reverse proxy configurations.

It seems Vite completely ignores the x-forwarded-proto and SSL-Offloaded headers so these don't have any effect.

To debug the issue you can compare the request origin header and the url.origin property here: node_modules/@sveltejs/kit/src/runtime/server/respond.js:63

Hope this helps someone!

@wardou2
Copy link

wardou2 commented Mar 7, 2024

I worked around the multiple ORIGIN issue by modifying SvelteKit's internal CSRF implementation to allow multiple origins
in hooks.server.ts:

import { error, type Handle, type RequestEvent } from "@sveltejs/kit";
import { allowedOrigins } from "$lib/config";

const csrf = (
    event: RequestEvent,
    allowedOrigins: string[],
) => {
    const { request, url } = event;

    const forbidden =
        isFormContentType(request) &&
        (request.method === "POST" ||
            request.method === "PUT" ||
            request.method === "PATCH" ||
            request.method === "DELETE") &&
        !allowedOrigins.includes(request.headers.get("origin") || "");

    if (forbidden) {
        error(403, `Cross-site ${request.method} form submissions are forbidden`);
    }
};

function isContentType(request: Request, ...types: string[]) {
    const type = request.headers.get("content-type")?.split(";", 1)[0].trim() ?? "";
    return types.includes(type.toLowerCase());
}
function isFormContentType(request: Request) {
    // These content types must be protected against CSRF
    // https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype
    return isContentType(
        request,
        "application/x-www-form-urlencoded",
        "multipart/form-data",
        "text/plain",
    );
}


export const handle: Handle = async ({ event, resolve }) => {
    csrf(event, allowedOrigins);
    return await resolve(event);
};

You'll also have to set csrf: false in svelte.config.js, as we've re-implemented it here.

Inspiration from this thread.

@sundaycrafts
Copy link

sundaycrafts commented Mar 8, 2024

It might not apply to many people, but I thought I'd share my case. The issue occurred when I made a fetch request from the browser to an endpoint defined in +server.js (which was actually a ts file). If the 'Content-Type': 'application/json' header wasn't included as a following, it seemed to be treated as a default action, which likely caused this message to appear. ORIGIN in .env file not relevant in this case. You can success to fetch even when the server in behind the reverse proxy such as Ngrok or Cloudflare tunnel and the ORIGIN is http://localhost:5173.

// no
await fetch(location.origin, {
    method: 'post',
    body: JSON.stringify({data: res.data} satisfies MyData),
});

// ok
await fetch(location.origin, {
    method: 'post',
    body: JSON.stringify({data: res.data} satisfies MyData),
    headers: {
        'Content-Type': 'application/json'
    }
});

I hope this information will be useful to future readers.

@edproton
Copy link

Hope I help someone,

Im running my app with node adapter through docker

in development I put in docker file

# Assuming the default app runnning host:port
ENV ORIGIN=http://localhost:3000 

or in docker-compose file

version: '3.8'
services:
  mysveltenodeapp-deploy:
    image: mysveltenodeapp
    ports:
      - '3000:3000'
    environment:
      - ...
      - ORIGIN=http://localhost:3000

@rasitayaz
Copy link

If the 'Content-Type': 'application/json' header wasn't included as a following, it seemed to be treated as a default action, which likely caused this message to appear. ORIGIN in .env file not relevant in this case.

Thank you @sundaycrafts, this was useful for me.

@Stephen10121
Copy link

Hope I help someone,

Im running my app with node adapter through docker

in development I put in docker file

# Assuming the default app runnning host:port
ENV ORIGIN=http://localhost:3000 

or in docker-compose file

version: '3.8'
services:
  mysveltenodeapp-deploy:
    image: mysveltenodeapp
    ports:
      - '3000:3000'
    environment:
      - ...
      - ORIGIN=http://localhost:3000

Currently, this is working for me. I'm running my sveltekit app in a docker container. So far, the form is submitting successfully. I'm planning to put this app behind a reverse proxy using nginx with:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;

Hoping this still will work even behind a proxy because I want this form to submit from multiple domains and it seems like you cant have a wildcard ORIGIN var unless you do a custom one yourself.

@tmjns
Copy link

tmjns commented Oct 1, 2024

I'm just previewing on localhost but I added a .env file like in the docs and that helped me:

ORIGIN=http://localhost:3000

After installing "dotenv" via npm manually, I configured the scripts section of my package.json with this line: "start": "node --env-file=.env build". Then, I updated my .env file with ORIGIN=http://localhost:3000, and it worked! 😁

@ieedan
Copy link

ieedan commented Oct 5, 2024

For anyone deploying with docker make sure you are also setting:

ENV NODE_ENV=production

Someone else might be able to explain why this is necessary but it worked for me!

Dockerfile:

FROM node:20.17-alpine AS builder

WORKDIR /app

RUN npm install [email protected] -g

# install dependencies
COPY package.json pnpm-lock.yaml ./
RUN pnpm install

COPY . .
RUN pnpm build


FROM node:20.17-alpine AS runner

WORKDIR /app

COPY --from=builder /app/build ./build
# we copy the package json so that it knows the module type
COPY --from=builder /app/package.json ./package.json

# set NODE_ENV environment variable to production
ENV NODE_ENV=production

# run app
CMD ["node", "build"]

EXPOSE 80

docker-compose.yaml:

services:
  www:
    container_name: www
    build:
       context: ./sites/www
       dockerfile: Dockerfile
    ports: 
      - 80:80
    environment:
      - PORT=80
      - ORIGIN=http://YOUR_ORIGIN_HERE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.