From f4f6c969475b80a45cd2a48445c966fc2f08c3bd Mon Sep 17 00:00:00 2001 From: deepfates Date: Fri, 29 May 2026 00:45:07 -0700 Subject: [PATCH] fix: trim whitespace from env-provided secrets CI-injected secrets often carry a trailing newline. An untrimmed api_key smuggled into the x-api-key HTTP header is rejected as a non-printable character, failing every live LLM test. Trim values read from environment variables in both env helpers. --- lib/cantrip/llm.ex | 10 ++++++++-- test/real_llm_config_test.exs | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/cantrip/llm.ex b/lib/cantrip/llm.ex index c5fa94b..eaddc35 100644 --- a/lib/cantrip/llm.ex +++ b/lib/cantrip/llm.ex @@ -108,7 +108,7 @@ defmodule Cantrip.LLM do defp env(opts, key, env_key, default \\ nil) do case fetch_option(opts, key) do {:ok, value} -> value - :error -> System.get_env(env_key) || default + :error -> trim_env(System.get_env(env_key)) || default end end @@ -131,7 +131,7 @@ defmodule Cantrip.LLM do defp env_first(keys) do Enum.find_value(keys, fn key -> - case System.get_env(key) do + case trim_env(System.get_env(key)) do nil -> nil "" -> nil val -> val @@ -139,6 +139,12 @@ defmodule Cantrip.LLM do end) end + # Env-provided secrets (especially CI-injected ones) frequently carry a + # trailing newline. An untrimmed value smuggled into an HTTP header like + # `x-api-key` is rejected as a non-printable character, so normalize here. + defp trim_env(nil), do: nil + defp trim_env(value) when is_binary(value), do: String.trim(value) + defp maybe_put(map, _key, nil), do: map defp maybe_put(map, _key, ""), do: map defp maybe_put(map, key, value), do: Map.put(map, key, value) diff --git a/test/real_llm_config_test.exs b/test/real_llm_config_test.exs index 02ab9fb..3da4571 100644 --- a/test/real_llm_config_test.exs +++ b/test/real_llm_config_test.exs @@ -98,6 +98,24 @@ defmodule Cantrip.RealLLMConfigTest do assert state.api_key == "sk-test" end + test "LLM.from_env trims whitespace from env-provided secrets" do + anthropic_model = System.get_env("ANTHROPIC_MODEL") + anthropic_api_key = System.get_env("ANTHROPIC_API_KEY") + + on_exit(fn -> + restore_env("ANTHROPIC_MODEL", anthropic_model) + restore_env("ANTHROPIC_API_KEY", anthropic_api_key) + end) + + System.put_env("CANTRIP_LLM_PROVIDER", "anthropic") + System.put_env("ANTHROPIC_MODEL", " claude-test \n") + System.put_env("ANTHROPIC_API_KEY", "sk-ant-secret\n") + + assert {:ok, {_module, state}} = Cantrip.LLM.from_env() + assert state.model == "anthropic:claude-test" + assert state.api_key == "sk-ant-secret" + end + defp restore_env(key, nil), do: System.delete_env(key) defp restore_env(key, value) do