[GH-ISSUE #630] Cannot connect to a WebSocket from another host #392

Closed
opened 2026-03-15 14:13:01 +03:00 by kerem · 9 comments
Owner

Originally created by @gmile on GitHub (Jan 26, 2026).
Original GitHub issue: https://github.com/axllent/mailpit/issues/630

After CSWSH fix in v1.28.2 (CVE-2026-22689), it seems like it's longer possible to connect to WS from another origin. My use-case for using the WS is to connect & watch the emails arriving to "inbox" during e2e tests. I have.

Tests need to listen for incoming emails via WebSocket (wss://email-service:8025/api/events) while the browser context is on https://web-service. After upgrading to v1.28.2+, all WebSocket connections fail with:

websocket: request origin not allowed by Upgrader.CheckOrigin

I found the --api-cors flag, but it seems it does not affect the WebSocket upgrade origin validation.

If this is a reasonable request then, perhaps, this could be solved by introducing another configuration flag, e.g. --ws-allowed-origins that accepts a comma-separated list of allowed origins for WebSocket connections:

To use like this, for example:

mailpit --ws-allowed-origins "https://web-service,https://localhost:3000"
Originally created by @gmile on GitHub (Jan 26, 2026). Original GitHub issue: https://github.com/axllent/mailpit/issues/630 After CSWSH fix in v1.28.2 (CVE-2026-22689), it seems like it's longer possible to connect to WS from another origin. My use-case for using the WS is to connect & watch the emails arriving to "inbox" during e2e tests. I have. - Web application served from https://web-service - Mailpit HTTP server running at https://email-service:8025 Tests need to listen for incoming emails via WebSocket (wss://email-service:8025/api/events) while the browser context is on https://web-service. After upgrading to v1.28.2+, all WebSocket connections fail with: ``` websocket: request origin not allowed by Upgrader.CheckOrigin ``` I found the `--api-cors` flag, but it seems it does not affect the WebSocket upgrade origin validation. If this is a reasonable request then, perhaps, this could be solved by introducing another configuration flag, e.g. `--ws-allowed-origins` that accepts a comma-separated list of allowed origins for WebSocket connections: To use like this, for example: ``` mailpit --ws-allowed-origins "https://web-service,https://localhost:3000" ```
kerem closed this issue 2026-03-15 14:13:06 +03:00
Author
Owner

@axllent commented on GitHub (Jan 27, 2026):

Hi @gmile. As I am sure you are aware, locking this down was very necessary in regards to security, as any website with malicious code could read the websocket data - provided they knew the host, port and events API endpoint of course. I hadn't realised however that there were use-cases for accessing the websocket via another (non-Mailpit) hosts.

This is a reasonable request, however before I decide whether to implement it, could you please tell me why you aren't just using the Mailpit UI to view these? In other words, what is so special about the web application on "http://web-service" ?

For what it's worth, I have tested a proof-of-concept which works - hostnames could be provided as a comma-separated list (without the "http" protocol, eg: mailpit --ws-allowed-origins "web-service,localhost:3000") which seems to work nicely.

<!-- gh-comment-id:3802862379 --> @axllent commented on GitHub (Jan 27, 2026): Hi @gmile. As I am sure you are aware, locking this down was very necessary in regards to security, as any website with malicious code could read the websocket data - provided they knew the host, port and events API endpoint of course. I hadn't realised however that there were use-cases for accessing the websocket via another (non-Mailpit) hosts. This is a reasonable request, however before I decide whether to implement it, could you please tell me why you aren't just using the Mailpit UI to view these? In other words, what is so special about the web application on "http://web-service" ? For what it's worth, I have tested a proof-of-concept which works - hostnames could be provided as a comma-separated list (without the "http" protocol, eg: `mailpit --ws-allowed-origins "web-service,localhost:3000"`) which seems to work nicely.
Author
Owner

@gmile commented on GitHub (Jan 27, 2026):

Hi, @axllent. Thank you for looking into this. Yes, I fully understand the CVS and how security has priority here.

In other words, what is so special about the web application on "http://web-service" ?

To your question, here's nothing so special about it per se. Having ability to create a websocket to Mailpit is just a convenience, that depends on idioms specific to Playwright. Here's more context.

Today, in Playwright I do this:

  1. write a test, that opens a page,
  2. in the setup part of that test, I establish a WS connection to Mailpit,
  3. as soon as my test triggers an sending of an email - the email "immediately" arrives to my "test's inbox": e.g. to the code via WS a channel.

This approach allows me to keep the test code clean and efficient: both in a sense that there's no need to spawn another tab in browser via Playwright, but also that the WS notification would immediately "come to me", not the other way around (e.g. where I would have to explicitly poll the API to find it).

At present, since creating WS from a separate origin stopped being possible, I technically could change the way the tests are written to this:

  1. in a "setup" stage of a test case in playwright, open a new tab,
  2. in that tab, visit Mailpit URL,
  3. proceed with my test in 1st tab until the moment when I know an email was sent,
  4. go to 2nd tab and check if the email arrived in inbox, then read its contents (a typical scenario is: need to find a link inside the contents of an email, then visit that link in 1st tab)

During 4, I have a choice: either subscribe to the WS channel, this time from this other tab containing Mailpit, or learn about the DOM elements tree of Mailpit, then "teach" my test code "click though" the Mailpit user interface to locate the right email.

2nd option is not ideal, because I would need to make my test code effectively depend on DOM details, which I consider a private area of Mailpit that shall keep the freedom of changing without a notice. 1st option is likely doable in principle, and admittedly haven't looked into it yet.

Upd. Another option would be to not deal with tabs, but poll Mailpit for new matching emails, of course. (Forgot to explicitly mention this when I initially wrote the comment). Tough, this would defeat the initial purpose of having the ability to read events from websocket for me.

<!-- gh-comment-id:3805517680 --> @gmile commented on GitHub (Jan 27, 2026): Hi, @axllent. Thank you for looking into this. Yes, I fully understand the CVS and how security has priority here. > In other words, what is so special about the web application on "http://web-service" ? To your question, here's nothing so special about it per se. Having ability to create a websocket to Mailpit is just a convenience, that depends on idioms specific to Playwright. Here's more context. Today, in Playwright I do this: 1. write a test, that opens a page, 2. in the setup part of that test, I establish a WS connection to Mailpit, 3. as soon as my test triggers an sending of an email - the email "immediately" arrives to my "test's inbox": e.g. to the code via WS a channel. This approach allows me to keep the test code clean and efficient: both in a sense that there's no need to spawn another tab in browser via Playwright, but also that the WS notification would immediately "come to me", not the other way around (e.g. where I would have to explicitly poll the API to find it). At present, since creating WS from a separate origin stopped being possible, I technically could change the way the tests are written to this: 1. in a "setup" stage of a test case in playwright, open a new tab, 2. in that tab, visit Mailpit URL, 3. proceed with my test in 1st tab until the moment when I know an email was sent, 4. go to 2nd tab and check if the email arrived in inbox, then read its contents (a typical scenario is: need to find a link inside the contents of an email, then visit that link in 1st tab) During 4, I have a choice: either subscribe to the WS channel, this time from this other tab containing Mailpit, or learn about the DOM elements tree of Mailpit, then "teach" my test code "click though" the Mailpit user interface to locate the right email. 2nd option is not ideal, because I would need to make my test code effectively depend on DOM details, which I consider a private area of Mailpit that shall keep the freedom of changing without a notice. 1st option is likely doable in principle, and admittedly haven't looked into it yet. **Upd**. Another option would be to not deal with tabs, but poll Mailpit for new matching emails, of course. (Forgot to explicitly mention this when I initially wrote the comment). Tough, this would defeat the initial purpose of [having the ability](https://github.com/axllent/mailpit/issues/449) to read events from websocket for me.
Author
Owner

@axllent commented on GitHub (Jan 28, 2026):

Thank you for the detailed information @gmile. Let me first apologise for all the questions, I find that I really need to determine whether a feature is a "need to have" or a "want to have", and if it is a "want to have" I have to then determine whether this will benefit just one user, or a lot of users. The reason for this is that if I added every requested feature, then Mailpit would already have different 150 flags & environment variables, and a lot more "hardly unused" code to maintain, and navigating the CLI help screen &/or website would be even more difficult.

This sounds like a feature somewhere in between a "need" and a "want" - you definitely have a specific use-case for your testing - however I suspect that you're maybe not the only one who uses the websocket via a different host. In addition to this, and as you know, the websocket has never been an official part of the API because of it's fluid nature (because it could technically change at any point with new functionality), so adding in specific functionality (which includes another flag/env variable) to support an unsupported API endpoint seems redundant to me.

So to be honest, I do not know what the right solution is here. I really want to make everyone happy, but can't always :) It would be really handy if other users suddenly reported the same issue right now (wishful thinking of course) - this would at least confirm that there was a wider need for the feature. There is at least one other user I know of who uses the websocket, although they connect to it directly (and yes, they are also aware that there is no guarantee the data format will remain the same, but it saved them polling from multiple hosts 24/7).

Maybe I ask some other questions before I try make a decision (yes sorry, a few more questions....):

  1. How often are you running these tests?
  2. If there was no websocket, would polling be a technical problem or would this just create a lot of work for you to implement. I'm not overly familiar with Playwright (it's on the TODO list).

What I am trying to understand is: how convenient is this feature for you really, and if there a feasible alternative (which isn't going to make your life too difficult, or introduce reliability issues for your testing process)?

Let me be clear, technically it's not a big thing for me to add - I already have a working proof of concept - but it is more about me supporting an unsupported feature (websockets) which I have no intention of officially supporting in the future.

<!-- gh-comment-id:3808877176 --> @axllent commented on GitHub (Jan 28, 2026): Thank you for the detailed information @gmile. Let me first apologise for all the questions, I find that I really need to determine whether a feature is a "need to have" or a "want to have", and if it is a "want to have" I have to then determine whether this will benefit just one user, or a lot of users. The reason for this is that if I added every requested feature, then Mailpit would already have different 150 flags & environment variables, and a lot more "hardly unused" code to maintain, and navigating the CLI help screen &/or website would be even more difficult. This sounds like a feature somewhere in between a "need" and a "want" - you definitely have a specific use-case for your testing - however I suspect that you're maybe not the only one who uses the websocket via a different host. In addition to this, and as you know, the [websocket has never been](https://mailpit.axllent.org/docs/api-v1/websocket/) an official part of the API because of it's fluid nature (because it could _technically_ change at any point with new functionality), so adding in specific functionality (which includes another flag/env variable) to support an unsupported API endpoint seems redundant to me. So to be honest, I do not know what the right solution is here. I really want to make everyone happy, but can't always :) It would be really handy if other users suddenly reported the same issue right now (wishful thinking of course) - this would at least confirm that there was a wider need for the feature. There is at least one other user I know of who uses the websocket, although they connect to it directly (and yes, they are also aware that there is no guarantee the data format will remain the same, but it saved them polling from multiple hosts 24/7). Maybe I ask some other questions before I try make a decision (yes sorry, a few more questions....): 1. How often are you running these tests? 2. If there was no websocket, would polling be a technical problem or would this just create a lot of work for you to implement. I'm not overly familiar with Playwright (it's on the TODO list). What I am trying to understand is: how convenient is this feature for you really, and if there a feasible alternative (which isn't going to make your life too difficult, or introduce reliability issues for your testing process)? Let me be clear, technically it's not a big thing for me to add - I already have a working proof of concept - but it is more about me supporting an unsupported feature (websockets) which I have no intention of officially supporting in the future.
Author
Owner

@gmile commented on GitHub (Jan 28, 2026):

No need to be apologize about anything, I completely understand your reasoning and where the questions are coming from 🙇

How often are you running these tests?

We use Mailpit every day to help us drive tests on one project currently. There are two more projects pending to start using Mailpit.

If there was no websocket, would polling be a technical problem or would this just create a lot of work for you to implement. I'm not overly familiar with Playwright (it's on the TODO list).

Technically, it shouldn't be a problem, no. Just less convenient code-wise. I would need to implement a helper function in my tests, that will do some sort of polling of Mailpit API. The WS approach felt more natural for this use case, e.g. expect "email arrives to you" as oppose you "you open email inbox once in a while to see if new email arrived".

We can park this for now, no worries at all. I understand that it's not sustainable to satisfy each and every user's request, especially for niche cases like this one.

<!-- gh-comment-id:3809649404 --> @gmile commented on GitHub (Jan 28, 2026): No need to be apologize about anything, I completely understand your reasoning and where the questions are coming from 🙇 > How often are you running these tests? We use Mailpit every day to help us drive tests on one project currently. There are two more projects pending to start using Mailpit. > If there was no websocket, would polling be a technical problem or would this just create a lot of work for you to implement. I'm not overly familiar with Playwright (it's on the TODO list). Technically, it shouldn't be a problem, no. Just less convenient code-wise. I would need to implement a helper function in my tests, that will do some sort of polling of Mailpit API. The WS approach felt more natural for this use case, e.g. expect "email arrives to you" as oppose you "you open email inbox once in a while to see if new email arrived". We can park this for now, no worries at all. I understand that it's not sustainable to satisfy each and every user's request, especially for niche cases like this one.
Author
Owner

@axllent commented on GitHub (Jan 30, 2026):

Sorry for the delay @gmile - OK, after looking more into this, I have come to realise that the existing --api-cors functionality is somewhat restricted whereby it would be great to provide an "array" (comma-separated) list of allowed hosts for API access, and not only that, use Mailpit to block invalid CORS requests (rather than relying on the browser to block these).

Once I have this functionality working, then I just apply the same rules to the websocket route too, and so --api-cors will technically apply to both the API and websocket.

This would allow you to set --api-cors "https://web-service,https://localhost:3000", --api-cors "web-service,localhost", or if you wanted to trust everything --api-cors "*" and it would work (both websockets and standard API requests). Mailpit will always allow "itself" (connections within the web UI), but this is regarding cross-domain connections whereby the browser provides the origin HTTP header and which does not match Mailpit.

I have a lot more testing to do, but I believe this should work and be backwards-compatible.

<!-- gh-comment-id:3826108082 --> @axllent commented on GitHub (Jan 30, 2026): Sorry for the delay @gmile - OK, after looking more into this, I have come to realise that the existing `--api-cors` functionality is somewhat restricted whereby it would be great to provide an "array" (comma-separated) list of allowed hosts for API access, and not only that, use Mailpit to **block** invalid CORS requests (rather than relying on the browser to block these). Once I have this functionality working, then I just apply the same rules to the websocket route too, and so `--api-cors` will technically apply to both the API and websocket. This would allow you to set `--api-cors "https://web-service,https://localhost:3000"`, `--api-cors "web-service,localhost"`, or if you wanted to trust everything `--api-cors "*"` and it would work (both websockets and standard API requests). Mailpit will always allow "itself" (connections within the web UI), but this is regarding cross-domain connections whereby the browser provides the `origin` HTTP header and which does not match Mailpit. I have a lot more testing to do, but I believe this should work and be backwards-compatible.
Author
Owner

@gmile commented on GitHub (Jan 31, 2026):

Thank you for considering this @axllent! Looking forward to testing this.

<!-- gh-comment-id:3828147737 --> @gmile commented on GitHub (Jan 31, 2026): Thank you for considering this @axllent! Looking forward to testing this.
Author
Owner

@axllent commented on GitHub (Feb 1, 2026):

This feature has now been included as part v1.29.0, which should resolve your issues provided you include the origin in the CORS flag / environment as previously described. My testing here worked fine, but please confirm it works for you too? Thanks.

<!-- gh-comment-id:3830255328 --> @axllent commented on GitHub (Feb 1, 2026): This feature has now been included as part [v1.29.0](https://github.com/axllent/mailpit/releases/tag/v1.29.0), which should resolve your issues provided you include the origin in the CORS flag / environment as previously described. My testing here worked fine, but please confirm it works for you too? Thanks.
Author
Owner

@gmile commented on GitHub (Feb 1, 2026):

@axllent I've tested the 1.29.0, it works as advertised. Thank you!

<!-- gh-comment-id:3831133152 --> @gmile commented on GitHub (Feb 1, 2026): @axllent I've tested the 1.29.0, it works as advertised. Thank you!
Author
Owner

@axllent commented on GitHub (Feb 1, 2026):

Fantastic, thanks for the quick feedback!

<!-- gh-comment-id:3831571680 --> @axllent commented on GitHub (Feb 1, 2026): Fantastic, thanks for the quick feedback!
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/mailpit#392
No description provided.