From 9a5b641d437887610fc64ee944401baf0e1b796e Mon Sep 17 00:00:00 2001 From: HectorIFC Date: Fri, 28 Aug 2026 01:44:49 -0300 Subject: [PATCH 1/2] fix(security): take the dashboard cookie Secure flag off the broker TLS setting Closes #50. secure_cookie_flag/0 decided whether to mark the dashboard session cookie Secure by reading :enable_tls, which describes a different listener. Malachi.Dashboard listens with :gen_tcp.listen and has no TLS path anywhere in the module, so the flag answered a question about the broker on 4040 and was applied to a cookie issued on 4041, which is always plain HTTP. It is not conditional on an operator turning something on, which is worse than the issue recorded. Resolving the config confirms it: with CONFIG_ENV=prod, require_tls is true unless explicitly disabled and enable_tls follows it, so every production build marked the cookie Secure over plain HTTP. A browser refuses to store a Secure cookie from a non-trustworthy origin, so login failed in silence: the form posts, the server answers 200, nothing happens. Only localhost and 127.0.0.1 escaped it, being origins browsers treat as trustworthy, which is why the composes that bind 4041 to loopback never showed it. The flag now comes from MALACHI_DASHBOARD_SECURE_COOKIE, off by default, joining the seven other dashboard_* settings that already read MALACHI_DASHBOARD_*. Off matches the transport the listener actually serves; behind a TLS-terminating proxy, which is the only shape where the dashboard is reached over HTTPS, the answer is constant and the operator states it once. Deriving it from X-Forwarded-Proto was the other candidate and was rejected on cost. It needs the same one new setting, to gate when the header may be trusted, and in exchange accepts a client-controlled input into a security decision. Nothing in this repository trusts a proxy header today: client_ip comes from :inet.peername/1. Per-request derivation buys nothing when the answer never varies. Read this part before deploying. Today the cookie is Secure by accident in production, so anyone already running behind a TLS proxy has a login that works and a cookie that is protected. Defaulting to false fixes the broken majority and silently downgrades them. There is no default that is right for both, so the choice is to match the real transport and make the change loud: the boot log now states which policy is in effect, and the Docker guide says outright that a proxied deployment which worked before now needs the variable set. The other half of the issue was that the failure is silent, and it stays silent unless the new setting is also wrong. A login now warns when X-Forwarded-Proto is present and contradicts the policy, in both directions. The header is read to report and never to decide, which is why this needs no trusted-proxy gate; a missing header stays quiet, because a proxy that forwards nothing is ordinary and warning on it would make the warning worth ignoring. The flag had no test at all, which is how it drifted. The new ones set enable_tls to the opposite of the expected outcome in every case, so they fail if the broker setting ever creeps back in, and they cover the cookie-clearing redirect too, since it shares the same function precisely so the two cannot disagree. Five of the six failed before the change. They bind the socket with a match rather than a case, unlike the login test above them, which passes quietly when the connection fails. Verified from outside as well as in: against a dev server, login returns the cookie without Secure by default and with it under MALACHI_DASHBOARD_SECURE_COOKIE=true, the boot line reports each state, and a request carrying X-Forwarded-Proto: http against a Secure policy produces the warning while https produces nothing. Suite is 1231 tests and 0 failures including the multinode tag; credo --strict, dialyzer, the docs build and the Sobelow gate are clean. --- README.md | 1 + config/runtime.exs | 6 ++ docs/DOCKER_README.md | 12 +++- lib/malachi/dashboard.ex | 44 ++++++++++-- lib/malachi/i18n.ex | 32 +++++++++ test/dashboard_security_test.exs | 116 +++++++++++++++++++++++++++++++ 6 files changed, 204 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9811d40..80602ab 100644 --- a/README.md +++ b/README.md @@ -513,6 +513,7 @@ curl -H "Authorization: Bearer $TOKEN" http://localhost:4041/ | `MALACHI_DASHBOARD_AUTH_RATE_WINDOW_MS` | `60000` | Rate limit window (1 minute) | | `MALACHI_DASHBOARD_CORS_ENABLED` | `false` | Enable CORS for `/metrics` and `/stream` | | `MALACHI_DASHBOARD_CORS_ORIGINS` | `*` | Allowed CORS origins (comma-separated) | +| `MALACHI_DASHBOARD_SECURE_COOKIE` | `false` | Mark the session cookie `Secure`. Set it only behind a TLS-terminating proxy: the dashboard listener itself serves plain HTTP, and a browser refuses to store a `Secure` cookie from a plain-HTTP origin, which makes login fail with no error. | | `MALACHI_DASHBOARD_CSP` | (default) | Custom Content-Security-Policy | | `MALACHI_HSTS_ENABLED` | `true` | Enable HTTP Strict Transport Security | | `MALACHI_HSTS_MAX_AGE` | `31536000` | HSTS max-age (1 year) | diff --git a/config/runtime.exs b/config/runtime.exs index b641740..c058ec7 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -334,6 +334,12 @@ config :malachi, end), dashboard_auth_rate_limit: parse_int.(System.get_env("MALACHI_DASHBOARD_AUTH_RATE_LIMIT"), 10), dashboard_auth_rate_window_ms: parse_int.(System.get_env("MALACHI_DASHBOARD_AUTH_RATE_WINDOW_MS"), 60_000), + # Whether the session cookie is marked Secure. Off by default because `Malachi.Dashboard` listens with + # `:gen_tcp.listen` and has no TLS path, so the transport it actually serves is plain HTTP, and a browser + # refuses to store a Secure cookie from a non-trustworthy origin. Turn it on when a TLS-terminating proxy + # sits in front, which is the only shape where the dashboard is reached over HTTPS. This deliberately does + # not read `enable_tls`: that switch describes the broker listener on another port. + dashboard_secure_cookie: System.get_env("MALACHI_DASHBOARD_SECURE_COOKIE") == "true", # CORS configuration dashboard_cors_enabled: System.get_env("MALACHI_DASHBOARD_CORS_ENABLED") == "true", dashboard_cors_origins: diff --git a/docs/DOCKER_README.md b/docs/DOCKER_README.md index cc986ff..99caeae 100644 --- a/docs/DOCKER_README.md +++ b/docs/DOCKER_README.md @@ -84,7 +84,8 @@ Open [http://localhost:4041](http://localhost:4041) in your browser. | `MALACHI_TCP_PORT` | `4040` | TCP server port for clients | | `MALACHI_DASHBOARD_PORT` | `4041` | HTTP dashboard port | | `MALACHI_LOCALE` | `en_US` | Language (`en_US`, `pt_BR`) | -| `MALACHI_ENABLE_TLS` | `false` | Enable TLS encryption | +| `MALACHI_ENABLE_TLS` | `false` | Enable TLS encryption on the broker port. It says nothing about the dashboard, which has no TLS path. | +| `MALACHI_DASHBOARD_SECURE_COOKIE` | `false` | Mark the dashboard session cookie `Secure`. Set it only behind a TLS-terminating proxy; see the ports note below. | | `MALACHI_ADMIN_PASS` | *(generated)* | Admin password; if unset, a random one is generated and logged on first boot. Without a persistent ra volume, this happens again on every restart. See Users and credentials. | | `MALACHI_LOG_DATA_DIR` | *(tmp)* | Directory for the durable log segments. Must be an absolute path on a volume; see Data Persistence. | | `MALACHI_RA_DATA_DIR` | *(tmp)* | Directory for the ra log (users, ACLs, lockouts). Must be an absolute path on a volume; see Data Persistence. | @@ -140,6 +141,15 @@ the login form and the session cookie. Publishing it on every interface would ha cleartext next to a broker you had just taken the trouble to encrypt. Reach a remote dashboard through a reverse proxy that terminates TLS in front of it, not by widening this binding. +When you do put a proxy in front, set `MALACHI_DASHBOARD_SECURE_COOKIE=true`. That is the one thing +the server cannot work out for itself: it only ever sees a plain-HTTP connection from the proxy, so +whether the browser reached it over HTTPS is a fact about your deployment. Leave it off for a +dashboard reached directly, because a browser refuses to store a `Secure` cookie served over plain +HTTP, and the resulting login answers 200 and simply does nothing. The boot log states which of the +two is in effect. Note that this used to follow `MALACHI_ENABLE_TLS`, which describes the broker port +and made every production build mark the cookie `Secure` over plain HTTP; if you run behind a proxy +and login worked before without setting anything, that is the setting you now need. + The mounted files are read at boot and validated then, so a certificate that is expired or unreadable stops the container rather than being discovered by a client later. diff --git a/lib/malachi/dashboard.ex b/lib/malachi/dashboard.ex index e855146..ef36906 100644 --- a/lib/malachi/dashboard.ex +++ b/lib/malachi/dashboard.ex @@ -32,6 +32,15 @@ defmodule Malachi.Dashboard do case :gen_tcp.listen(port, opts) do {:ok, socket} -> Logger.info(I18n.t(:dashboard_started, port: port)) + # Stating the cookie policy at boot is part of the fix rather than decoration: the failure it + # guards against is a login that answers 200 and does nothing, which is invisible from the outside. + # The two states need different advice, not one sentence with a boolean in it, so they are two + # messages: one names the variable to set, the other names the assumption being made. + Logger.info( + if secure_cookie?(), + do: I18n.t(:dashboard_cookie_secure), + else: I18n.t(:dashboard_cookie_plain) + ) send(self(), :accept) {:ok, %{socket: socket, port: port}} @@ -341,14 +350,37 @@ defmodule Malachi.Dashboard do "HTTP/1.1 302 Found\r\nLocation: /login\r\nSet-Cookie: malachi_token=; HttpOnly; Path=/; SameSite=Strict; Max-Age=0#{secure_cookie_flag()}\r\nCache-Control: no-store\r\nContent-Length: 0\r\n\r\n" end - # "; Secure" only under TLS, so the cookie is not marked Secure on a plain-HTTP dev server (where the - # browser would then silently drop it). Shared by the login cookie and the cookie-clearing redirects, so a - # change to the Secure policy cannot make them disagree. + # "; Secure" comes from the dashboard's own setting, and from nothing else. This module listens with + # `:gen_tcp.listen` and has no TLS path, so the transport it serves is always plain HTTP; the only way it + # is reached over HTTPS is behind a proxy that terminates TLS, and that is a fact about the deployment + # which only the operator can state. It used to read `:enable_tls`, the broker's switch for another port, + # which marked the cookie Secure over plain HTTP: browsers refuse to store that, so login failed with a + # 200 and no error for anyone not on localhost. Shared by the login cookie and the cookie-clearing + # redirects, so a change to the policy cannot make them disagree. defp secure_cookie_flag do - if Application.get_env(:malachi, :enable_tls), do: "; Secure", else: "" + if secure_cookie?(), do: "; Secure", else: "" end - defp send_login_success(socket, token) do + defp secure_cookie?, do: Application.get_env(:malachi, :dashboard_secure_cookie, false) + + # `X-Forwarded-Proto` is read here to REPORT, never to decide. The cookie policy comes from configuration + # and nothing a client sends can change it, which is why this needs no trusted-proxy gate. What it buys is + # the one diagnosis the server can otherwise not make: whether the policy matches how the browser actually + # reached the dashboard. Only a header that is present and disagrees is worth a line, in either direction. + # A proxy that forwards nothing is ordinary, so its absence has to stay silent or the warning becomes + # noise and stops being read. + defp warn_on_proto_mismatch(headers) do + case {secure_cookie?(), Map.get(headers, "x-forwarded-proto")} do + {_same, nil} -> :ok + {true, "https"} -> :ok + {false, proto} when proto != "https" -> :ok + {true, proto} -> Logger.warning(I18n.t(:dashboard_cookie_secure_over_plain, proto: proto)) + {false, _https} -> Logger.warning(I18n.t(:dashboard_cookie_plain_over_https)) + end + end + + defp send_login_success(socket, token, headers) do + warn_on_proto_mismatch(headers) response_body = Jason.encode!(%{"s" => "ok", "token" => token}) cookie_header = @@ -468,7 +500,7 @@ defmodule Malachi.Dashboard do %{} ) - send_login_success(socket, token) + send_login_success(socket, token, headers) {:error, _reason} -> Metrics.increment_dashboard_auth_failed() diff --git a/lib/malachi/i18n.ex b/lib/malachi/i18n.ex index a051540..6459184 100644 --- a/lib/malachi/i18n.ex +++ b/lib/malachi/i18n.ex @@ -129,6 +129,38 @@ defmodule Malachi.I18n do "pt_BR" => "🌐 Malachi Dashboard rodando em http://localhost:%{port}", "en_US" => "🌐 Malachi Dashboard running at http://localhost:%{port}" }, + dashboard_cookie_plain: %{ + "pt_BR" => + "Cookie de sessao do dashboard sem Secure. O listener serve HTTP puro; defina " <> + "MALACHI_DASHBOARD_SECURE_COOKIE=true se um proxy terminar TLS na frente dele", + "en_US" => + "Dashboard session cookie is not marked Secure. This listener serves plain HTTP; set " <> + "MALACHI_DASHBOARD_SECURE_COOKIE=true when a TLS-terminating proxy sits in front of it" + }, + dashboard_cookie_secure: %{ + "pt_BR" => + "Cookie de sessao do dashboard marcado como Secure. Isso pressupoe um proxy terminando TLS na " <> + "frente: alcancado direto por HTTP, o navegador descarta o cookie e o login falha sem erro", + "en_US" => + "Dashboard session cookie is marked Secure. That assumes a TLS-terminating proxy in front: " <> + "reached directly over HTTP, the browser drops the cookie and login fails with no error" + }, + dashboard_cookie_secure_over_plain: %{ + "pt_BR" => + "Cookie Secure emitido, mas a requisicao chegou com X-Forwarded-Proto: %{proto}. Se o navegador " <> + "alcanca o dashboard por HTTP puro, ele descarta o cookie e o login falha sem erro", + "en_US" => + "Issued a Secure cookie, but the request arrived with X-Forwarded-Proto: %{proto}. If the browser " <> + "reaches the dashboard over plain HTTP it drops the cookie and login fails with no error" + }, + dashboard_cookie_plain_over_https: %{ + "pt_BR" => + "Requisicao chegou com X-Forwarded-Proto: https, mas o cookie de sessao nao esta marcado como " <> + "Secure. Defina MALACHI_DASHBOARD_SECURE_COOKIE=true para o navegador nao o enviar por HTTP", + "en_US" => + "Request arrived with X-Forwarded-Proto: https, but the session cookie is not marked Secure. Set " <> + "MALACHI_DASHBOARD_SECURE_COOKIE=true so the browser will not send it over plain HTTP" + }, rate_limiter_started: %{ "pt_BR" => "✅ RateLimiter iniciado", "en_US" => "✅ RateLimiter started" diff --git a/test/dashboard_security_test.exs b/test/dashboard_security_test.exs index 90a3b58..23d52cd 100644 --- a/test/dashboard_security_test.exs +++ b/test/dashboard_security_test.exs @@ -1,6 +1,8 @@ defmodule Malachi.DashboardSecurityTest do use ExUnit.Case, async: false + import ExUnit.CaptureLog + alias Malachi.Auth.UserStore alias Malachi.Dashboard.SecurityHeaders alias Malachi.Test.DashboardHelper @@ -424,6 +426,73 @@ defmodule Malachi.DashboardSecurityTest do end end + # Every test here sets `:enable_tls` to the opposite of the cookie policy, and that opposition is the + # assertion: the flag used to be read from `:enable_tls`, which describes the broker listener on 4040, + # while the cookie is issued on 4041, which has no TLS path at all. Pinning them against each other is + # what proves the broker's setting no longer participates. + describe "session cookie Secure flag" do + test "is set when the dashboard is configured for it, with the broker's TLS off" do + response = with_cookie_policy(true, false, &login_response/0) + + assert String.contains?(response, "Set-Cookie: malachi_token=") + assert String.contains?(response, "; Secure") + end + + test "is absent when the dashboard is not configured for it, with the broker's TLS on" do + # The regression. Marking the cookie Secure over plain HTTP makes the browser refuse to store it, + # so the login form posts, the server answers 200, and nothing happens. Only localhost escapes it, + # because browsers treat that origin as trustworthy. + response = with_cookie_policy(false, true, &login_response/0) + + assert String.contains?(response, "Set-Cookie: malachi_token=") + refute String.contains?(response, "; Secure") + end + + test "the cookie-clearing redirect follows the same policy as the login cookie" do + secure = with_cookie_policy(true, false, &clearing_response/0) + plain = with_cookie_policy(false, true, &clearing_response/0) + + # They share secure_cookie_flag/0 precisely so a policy change cannot make them disagree: a clear + # whose attributes do not match the cookie that was set is a clear the browser can ignore. + assert String.downcase(secure) =~ "set-cookie: malachi_token=;" + assert String.contains?(secure, "; Secure") + assert String.downcase(plain) =~ "set-cookie: malachi_token=;" + refute String.contains?(plain, "; Secure") + end + + test "warns when a forwarded protocol says the cookie will be dropped" do + log = + capture_log(fn -> + with_cookie_policy(true, false, fn -> login_response(%{"X-Forwarded-Proto" => "http"}) end) + end) + + assert log =~ "X-Forwarded-Proto" + end + + test "warns when the request arrived over HTTPS but the cookie is not Secure" do + log = + capture_log(fn -> + with_cookie_policy(false, true, fn -> login_response(%{"X-Forwarded-Proto" => "https"}) end) + end) + + assert log =~ "X-Forwarded-Proto" + end + + test "stays quiet when the forwarded protocol agrees, and when there is none" do + # A proxy that does not forward the header is ordinary, so its absence cannot be a warning without + # becoming noise. Silence here is what keeps the two warnings above worth reading. + agreeing = + capture_log(fn -> + with_cookie_policy(true, false, fn -> login_response(%{"X-Forwarded-Proto" => "https"}) end) + end) + + absent = capture_log(fn -> with_cookie_policy(true, false, &login_response/0) end) + + refute agreeing =~ "X-Forwarded-Proto" + refute absent =~ "X-Forwarded-Proto" + end + end + # CORS is off by default, so each test sets exactly the configuration it exercises. The preflight must # agree with what a real request would get: it answers from the same builder. describe "CORS preflight" do @@ -1125,6 +1194,53 @@ defmodule Malachi.DashboardSecurityTest do response end + # Applies a cookie policy for the duration of `fun` and restores both settings afterwards, including on + # a failing assertion: leaking either of these would silently change what the rest of the suite tests. + # `enable_tls` is passed explicitly rather than left alone because these tests exist to show it has no + # say, which only means something when it is set against the expected outcome. + defp with_cookie_policy(secure_cookie?, enable_tls?, fun) do + previous = { + Application.get_env(:malachi, :dashboard_secure_cookie), + Application.get_env(:malachi, :enable_tls) + } + + Application.put_env(:malachi, :dashboard_secure_cookie, secure_cookie?) + Application.put_env(:malachi, :enable_tls, enable_tls?) + + try do + fun.() + after + {previous_cookie, previous_tls} = previous + Application.put_env(:malachi, :dashboard_secure_cookie, previous_cookie) + Application.put_env(:malachi, :enable_tls, previous_tls) + end + end + + # Both of these bind the socket with a match rather than a case: a connection that fails has to fail the + # test, not pass it quietly. + defp login_response(extra_headers \\ %{}) do + {:ok, socket} = DashboardHelper.connect() + body = Jason.encode!(%{"username" => "dashboard_admin", "password" => "admin_pass_123"}) + + {:ok, response} = + DashboardHelper.request(socket, :POST, "/login", body: body, headers: extra_headers) + + :gen_tcp.close(socket) + response + end + + defp clearing_response do + {:ok, socket} = DashboardHelper.connect() + + {:ok, response} = + DashboardHelper.request(socket, :GET, "/", + headers: %{"Cookie" => "malachi_token=not_a_real_token"} + ) + + :gen_tcp.close(socket) + response + end + # Extracts the numeric status from an HTTP response, and its JSON body. defp status_code(response) do case Regex.run(~r"HTTP/1\.1 (\d{3})", response) do From d5a2b40c69bfc06bbbd597a78073fd8cae04176a Mon Sep 17 00:00:00 2001 From: HectorIFC Date: Fri, 28 Aug 2026 02:30:26 -0300 Subject: [PATCH 2/2] fix(security): format the cookie change and accent its Portuguese log messages Two follow-ups to the previous commit, both mine to have avoided. The CI test job failed on mix format --check-formatted, which my verification routine did not run. Two files were unformatted: a blank line missing after the boot-time policy log in dashboard.ex, and a call that fits on one line in dashboard_security_test.exs. The four new pt_BR strings in the i18n catalogue were written without diacritics: sessao, pressupoe, alcanca, requisicao, nao. That is inconsistent with the catalogue itself, where 38 of the 69 pt_BR strings are accented, and these are user-facing log lines rather than code, so they get the standard forms. The ASCII habit belongs to commit messages and comments, not here. Suite is 1231 tests and 0 failures including the multinode tag, confirmed over three consecutive runs. format, credo --strict, dialyzer, the docs build and the Sobelow gate are clean. Worth recording for anyone reading the previous commit's verification claims: three timing-sensitive tests failed intermittently while checking this, and none of them were caused by the change. The machine was carrying a load average of 130 on eight cores, from sixteen orphaned shell processes left running for over seven hours by an earlier attempt in this session to reproduce the flake in issue #56 under artificial load. They are gone, and the suite is stable without them. My earlier attribution of one of those failures to a dev server was wrong; this was the cause. --- lib/malachi/dashboard.ex | 1 + lib/malachi/i18n.ex | 14 +++++++------- test/dashboard_security_test.exs | 4 +--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/malachi/dashboard.ex b/lib/malachi/dashboard.ex index ef36906..7ebddd4 100644 --- a/lib/malachi/dashboard.ex +++ b/lib/malachi/dashboard.ex @@ -41,6 +41,7 @@ defmodule Malachi.Dashboard do do: I18n.t(:dashboard_cookie_secure), else: I18n.t(:dashboard_cookie_plain) ) + send(self(), :accept) {:ok, %{socket: socket, port: port}} diff --git a/lib/malachi/i18n.ex b/lib/malachi/i18n.ex index 6459184..6edd74d 100644 --- a/lib/malachi/i18n.ex +++ b/lib/malachi/i18n.ex @@ -131,7 +131,7 @@ defmodule Malachi.I18n do }, dashboard_cookie_plain: %{ "pt_BR" => - "Cookie de sessao do dashboard sem Secure. O listener serve HTTP puro; defina " <> + "Cookie de sessão do dashboard sem Secure. O listener serve HTTP puro; defina " <> "MALACHI_DASHBOARD_SECURE_COOKIE=true se um proxy terminar TLS na frente dele", "en_US" => "Dashboard session cookie is not marked Secure. This listener serves plain HTTP; set " <> @@ -139,24 +139,24 @@ defmodule Malachi.I18n do }, dashboard_cookie_secure: %{ "pt_BR" => - "Cookie de sessao do dashboard marcado como Secure. Isso pressupoe um proxy terminando TLS na " <> - "frente: alcancado direto por HTTP, o navegador descarta o cookie e o login falha sem erro", + "Cookie de sessão do dashboard marcado como Secure. Isso pressupõe um proxy terminando TLS na " <> + "frente: alcançado direto por HTTP, o navegador descarta o cookie e o login falha sem erro", "en_US" => "Dashboard session cookie is marked Secure. That assumes a TLS-terminating proxy in front: " <> "reached directly over HTTP, the browser drops the cookie and login fails with no error" }, dashboard_cookie_secure_over_plain: %{ "pt_BR" => - "Cookie Secure emitido, mas a requisicao chegou com X-Forwarded-Proto: %{proto}. Se o navegador " <> - "alcanca o dashboard por HTTP puro, ele descarta o cookie e o login falha sem erro", + "Cookie Secure emitido, mas a requisição chegou com X-Forwarded-Proto: %{proto}. Se o navegador " <> + "alcança o dashboard por HTTP puro, ele descarta o cookie e o login falha sem erro", "en_US" => "Issued a Secure cookie, but the request arrived with X-Forwarded-Proto: %{proto}. If the browser " <> "reaches the dashboard over plain HTTP it drops the cookie and login fails with no error" }, dashboard_cookie_plain_over_https: %{ "pt_BR" => - "Requisicao chegou com X-Forwarded-Proto: https, mas o cookie de sessao nao esta marcado como " <> - "Secure. Defina MALACHI_DASHBOARD_SECURE_COOKIE=true para o navegador nao o enviar por HTTP", + "Requisição chegou com X-Forwarded-Proto: https, mas o cookie de sessão não está marcado como " <> + "Secure. Defina MALACHI_DASHBOARD_SECURE_COOKIE=true para o navegador não o enviar por HTTP", "en_US" => "Request arrived with X-Forwarded-Proto: https, but the session cookie is not marked Secure. Set " <> "MALACHI_DASHBOARD_SECURE_COOKIE=true so the browser will not send it over plain HTTP" diff --git a/test/dashboard_security_test.exs b/test/dashboard_security_test.exs index 23d52cd..73c5615 100644 --- a/test/dashboard_security_test.exs +++ b/test/dashboard_security_test.exs @@ -1233,9 +1233,7 @@ defmodule Malachi.DashboardSecurityTest do {:ok, socket} = DashboardHelper.connect() {:ok, response} = - DashboardHelper.request(socket, :GET, "/", - headers: %{"Cookie" => "malachi_token=not_a_real_token"} - ) + DashboardHelper.request(socket, :GET, "/", headers: %{"Cookie" => "malachi_token=not_a_real_token"}) :gen_tcp.close(socket) response