Skip to content

Instantly share code, notes, and snippets.

@hauleth
Created November 16, 2025 18:43
Show Gist options
  • Select an option

  • Save hauleth/af608e6449a32d9884a8311412ad0a64 to your computer and use it in GitHub Desktop.

Select an option

Save hauleth/af608e6449a32d9884a8311412ad0a64 to your computer and use it in GitHub Desktop.
defmodule LangustaWeb.Plug.CSP do
import Plug.Conn
@behaviour Plug
@impl true
def init(opts) do
enable? = opts[:enable?] || (&__MODULE__.__true__/1)
force = opts[:force]
report_only = opts[:report_only]
report_uri = opts[:report_uri]
if report_only not in [nil, %{}] and report_uri in [nil, ""] do
raise ArgumentError,
"`:report_uri` is required when `:report_only` field is non empty"
end
%{
force: force,
report_only: report_only,
enable?: enable?,
report_uri: report_uri
}
end
@impl true
def call(conn, opts) when is_map(opts) do
if opts.enable?.(conn) do
nonce = gen_nonce()
force = header_value(opts.force, nonce, "")
report_only =
header_value(opts.report_only, nonce, "; report-uri #{opts.report_uri}")
conn
|> set_header("content-security-policy", force)
|> set_header("content-security-policy-report-only", report_only)
|> assign(:csp_nonce, nonce)
else
conn
end
end
defp set_header(conn, _name, nil), do: conn
defp set_header(conn, name, values), do: put_resp_header(conn, name, values)
defp header_value(nil, _nonce, _suffix), do: nil
defp header_value(resources, nonce, suffix) do
resources =
Enum.map_intersperse(resources, "; ", fn {key, values} ->
"#{key}-src #{build_resource(values, nonce)}"
end)
IO.iodata_to_binary([resources, suffix])
end
defp gen_nonce,
do: Base.url_encode64(:crypto.strong_rand_bytes(16), padding: false)
defp build_resource(:all, _nonce), do: "*"
defp build_resource(values, nonce) when is_list(values) do
Enum.map_intersperse(values, ?\s, &build(&1, nonce))
end
defp build(:nonce, nonce), do: "'nonce-#{nonce}'"
defp build(atom, _) when is_atom(atom) do
name = atom |> Atom.to_string() |> String.replace("_", "-")
"'#{name}'"
end
defp build({algo, input}, _) when algo in ~w[sha256 sha384 sha512]a do
digest = Base.url_encode64(:crypto.hash(algo, input))
"'#{algo}-#{digest}'"
end
defp build(url, _) when is_binary(url), do: url
@doc false
def __true__(_conn), do: true
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment