fix(submit): allowlist switch_tab values instead of trusting client input

`handle_event("switch_tab", ...)` ran `String.to_existing_atom/1` on
the raw `tab` value from the browser, so a stale, forged, or
mistyped client event with any unknown string crashed the LiveView
with `ArgumentError: not an already existing atom`.

Compares against an explicit `@valid_tabs` allowlist (`:single`,
`:csv`, `:adif`) — known values switch the active tab, anything
else is ignored.
This commit is contained in:
Graham McIntire 2026-04-25 10:10:42 -05:00
parent c17f912622
commit 90bf44ce90
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 34 additions and 2 deletions

View file

@ -54,10 +54,20 @@ defmodule MicrowavepropWeb.SubmitLive do
end
end
@valid_tabs ~w(single csv adif)a
@impl true
def handle_event("switch_tab", %{"tab" => tab}, socket) do
socket = assign(socket, active_tab: String.to_existing_atom(tab))
{:noreply, MicrowavepropWeb.LiveStashGuard.stash(socket)}
case Enum.find(@valid_tabs, &(Atom.to_string(&1) == tab)) do
nil ->
# Stale or forged client event — ignore instead of crashing the
# LiveView via String.to_existing_atom/1.
{:noreply, socket}
atom ->
socket = assign(socket, active_tab: atom)
{:noreply, MicrowavepropWeb.LiveStashGuard.stash(socket)}
end
end
def handle_event("validate", %{"contact" => contact_params}, socket) do

View file

@ -43,6 +43,28 @@ defmodule MicrowavepropWeb.SubmitLiveTest do
end
end
describe "switch_tab" do
test "ignores an unknown tab string instead of crashing the LiveView", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/submit")
# An unknown tab name used to flow into String.to_existing_atom/1
# and bring the LiveView down. We expect the event to be a no-op
# and the process to stay alive.
render_hook(lv, "switch_tab", %{"tab" => "definitely-not-a-tab-#{System.unique_integer()}"})
assert Process.alive?(lv.pid)
end
test "honors a known tab name", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/submit")
html = render_hook(lv, "switch_tab", %{"tab" => "csv"})
# Active tab moved to CSV — the CSV section's heading is now visible.
assert html =~ "CSV"
end
end
describe "validate" do
test "shows validation errors on change", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/submit")