Api Key Exposure in Phoenix
Phoenix-Specific Remediation
Remediating API key exposure in Phoenix applications requires systematic changes to how secrets are handled. The first step is implementing proper secret management using Phoenix's configuration system. Move all API keys to environment variables and use Phoenix's runtime configuration:
# config/runtime.exs
import Config
config :my_app, MyApp.API,
stripe_key: System.get_env("STRIPE_API_KEY") || raise "STRIPE_API_KEY not found",
aws_key: System.get_env("AWS_ACCESS_KEY_ID") || raise "AWS_ACCESS_KEY_ID not found"
This approach ensures that missing keys cause application startup failures rather than silent fallbacks to insecure defaults.
Implement request filtering in Phoenix controllers to prevent API key leakage through error responses:
defmodule MyAppWeb.Plugs.APIFilter do
import Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
conn
|> put_private(:sensitive_keys, ["api_key", "password", "secret"])
|> register_before_send(&filter_response/1)
end
defp filter_response(conn) do
if conn.status >= 400 do
body = conn.resp_body || ""
filtered_body = body
|> Jason.decode!()
|> filter_sensitive_data()
|> Jason.encode!()
%{conn | resp_body: filtered_body}
else
conn
end
end
defp filter_sensitive_data(map) when is_map(map) do
Map.new(map, fn
{key, value} when key in ["api_key", "password", "secret"] ->
{key, "[FILTERED]"}
{key, value} when is_map(value) ->
{key, filter_sensitive_data(value)}
{key, value} ->
{key, value}
end)
end
defp filter_sensitive_data(list) when is_list(list) do
Enum.map(list, &filter_sensitive_data/1)
end
defp filter_sensitive_data(other), do: other
endAdd this plug to your Phoenix router to automatically filter sensitive data from error responses.
Secure your Phoenix application's logging configuration by implementing structured logging with sensitive data filtering:
defmodule MyAppWeb.Plugs.SecureLogger do
import Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
conn
|> put_private(:log_filter, fn data -> filter_log_data(data) end)
|> register_before_send(fn conn ->
Logger.info("Request completed: #{conn.method} #{conn.request_path}")
conn
end)
end
defp filter_log_data(data) do
Enum.reduce(data, data, fn {key, value}, acc ->
if String.contains?(key, "password") or String.contains?(key, "secret") do
Map.put(acc, key, "[FILTERED]")
else
acc
end
end)
end
endImplement comprehensive testing for API key exposure using Phoenix's testing framework:
defmodule MyAppWeb.APITest do
use MyAppWeb.ConnCase
import Phoenix.ConnTest
setup do
api_key = "sk_test_1234567890"
{:ok, api_key: api_key}
end
test "does not expose API keys in error responses", %{conn: conn, api_key: api_key} do
conn =
conn
|> put_req_header("authorization", "Bearer #{api_key}")
|> post("/api/v1/endpoint", %{invalid: "data"})
assert json_response(conn, 400)["error"] != api_key
refute String.contains?(conn.resp_body, api_key)
end
test "logs do not contain sensitive data", %{conn: conn, api_key: api_key} do
# This test would verify that logging doesn't capture sensitive data
# Implementation depends on your logging setup
assert true
end
endFinally, implement runtime monitoring in your Phoenix application to detect potential API key exposure:
defmodule MyApp.Monitoring do
use GenServer
def start_link(_opts) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
schedule_scan()
{:ok, %{}}
end
def handle_info(:scan_for_exposure, state) do
# Implement scanning logic here
# Check logs, responses, error messages for exposed keys
schedule_scan()
{:noreply, state}
end
defp schedule_scan do
Process.send_after(self(), :scan_for_exposure, 60_000) # Scan every minute
end
endRegister this monitoring process in your Phoenix application supervisor to continuously check for potential API key exposure.