fix: CSM-001 done
This commit is contained in:
@@ -285,6 +285,28 @@ defmodule BDS.AITest do
|
||||
:ok
|
||||
end
|
||||
|
||||
test "model_capabilities does not create atoms from unknown capability keys" do
|
||||
before = :erlang.system_info(:atom_count)
|
||||
|
||||
malicious_caps =
|
||||
Jason.encode!(%{
|
||||
"supports_attachment" => true,
|
||||
"supports_tool_calls" => false,
|
||||
"disables_reasoning" => false,
|
||||
"csm001_fictive_#{:erlang.unique_integer()}" => true,
|
||||
"another_unknown_#{:erlang.unique_integer()}" => 42
|
||||
})
|
||||
|
||||
:ok = BDS.AI.SettingsStore.put_setting("ai.model_capabilities.csm-test-model", malicious_caps)
|
||||
|
||||
result = BDS.AI.Catalog.model_capabilities("csm-test-model")
|
||||
assert result.supports_attachment == true
|
||||
assert result.supports_tool_calls == false
|
||||
|
||||
after_count = :erlang.system_info(:atom_count)
|
||||
assert after_count == before
|
||||
end
|
||||
|
||||
test "put_endpoint, get_endpoint, and delete_endpoint manage encrypted endpoint settings" do
|
||||
assert {:ok, endpoint} =
|
||||
BDS.AI.put_endpoint(
|
||||
|
||||
@@ -55,20 +55,28 @@ defmodule BDS.BoundedAtomsTest do
|
||||
assert BoundedAtoms.shell_command("unknown") == nil
|
||||
end
|
||||
|
||||
test "codebase does not use String.to_existing_atom rescues" do
|
||||
test "codebase does not use String.to_atom on dynamic data" do
|
||||
lib_dir = Path.expand("../../lib", __DIR__)
|
||||
|
||||
allowed_files = [
|
||||
"bounded_atoms.ex",
|
||||
"map_utils.ex"
|
||||
]
|
||||
|
||||
offenders =
|
||||
lib_dir
|
||||
|> Path.join("**/*.ex")
|
||||
|> Path.wildcard()
|
||||
|> Enum.reject(&String.ends_with?(&1, "bounded_atoms.ex"))
|
||||
|> Enum.filter(fn path ->
|
||||
path
|
||||
|> File.read!()
|
||||
|> String.contains?("String.to_existing_atom")
|
||||
|> Enum.reject(fn path ->
|
||||
Enum.any?(allowed_files, &String.ends_with?(path, &1))
|
||||
end)
|
||||
|> Enum.filter(fn path ->
|
||||
content = File.read!(path)
|
||||
String.contains?(content, "String.to_atom(")
|
||||
end)
|
||||
|> Enum.map(&Path.relative_to(&1, lib_dir))
|
||||
|
||||
assert offenders == []
|
||||
assert offenders == [],
|
||||
"Files still using String.to_atom (use String.to_existing_atom or BDS.MapUtils.safe_atomize_key): #{Enum.join(offenders, ", ")}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,6 +18,34 @@ defmodule BDS.ImportDefinitionsTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "decode_analysis_result does not create atoms from unknown keys" do
|
||||
unique_suffix = :erlang.unique_integer()
|
||||
unknown_key_1 = "csm001_fictive_#{unique_suffix}"
|
||||
unknown_key_2 = "csm001_nested_#{unique_suffix}"
|
||||
|
||||
malicious_json =
|
||||
Jason.encode!(%{
|
||||
unknown_key_1 => "val",
|
||||
"site_info" => %{unknown_key_2 => "nested_val"}
|
||||
})
|
||||
|
||||
result = ImportDefinitions.decode_analysis_result(malicious_json)
|
||||
|
||||
assert is_map(result)
|
||||
assert Map.get(result, unknown_key_1) == "val" or Map.get(result, "csm001_fictive_#{unique_suffix}") == "val"
|
||||
assert_raise ArgumentError, fn -> String.to_existing_atom(unknown_key_1) end
|
||||
assert_raise ArgumentError, fn -> String.to_existing_atom(unknown_key_2) end
|
||||
end
|
||||
|
||||
test "decode_analysis_result converts known keys to atoms" do
|
||||
json = Jason.encode!(%{"site_info" => %{"title" => "My Blog"}})
|
||||
|
||||
result = ImportDefinitions.decode_analysis_result(json)
|
||||
|
||||
assert is_map(result)
|
||||
assert Map.get(result, :site_info) != nil or Map.get(result, "site_info") != nil
|
||||
end
|
||||
|
||||
test "get, update, and delete round-trip import definition editor state", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
|
||||
@@ -22,6 +22,39 @@ defmodule BDS.ImportExecutionTest do
|
||||
%{project: project, temp_dir: temp_dir}
|
||||
end
|
||||
|
||||
test "execute_import does not create atoms from malicious report keys", %{
|
||||
project: project
|
||||
} do
|
||||
unique_suffix = :erlang.unique_integer()
|
||||
unknown_key_1 = "csm001_malicious_#{unique_suffix}"
|
||||
unknown_key_2 = "csm001_nested_#{unique_suffix}"
|
||||
|
||||
malicious_report = %{
|
||||
"items" => %{
|
||||
"categories" => [],
|
||||
"tags" => [],
|
||||
"posts" => [],
|
||||
"pages" => [],
|
||||
"media" => []
|
||||
},
|
||||
"details" => %{
|
||||
"posts" => [],
|
||||
"pages" => [],
|
||||
"media" => []
|
||||
},
|
||||
unknown_key_1 => "attack",
|
||||
"extra" => %{unknown_key_2 => "nested_attack"}
|
||||
}
|
||||
|
||||
assert {:ok, _result} =
|
||||
ImportExecution.execute_import(project.id, malicious_report,
|
||||
default_author: "Test Author"
|
||||
)
|
||||
|
||||
assert_raise ArgumentError, fn -> String.to_existing_atom(unknown_key_1) end
|
||||
assert_raise ArgumentError, fn -> String.to_existing_atom(unknown_key_2) end
|
||||
end
|
||||
|
||||
test "execute_import creates tags, posts, pages, and media from the analysis report", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
|
||||
@@ -35,6 +35,70 @@ defmodule BDS.MapUtilsTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "safe_atomize_key/1" do
|
||||
test "converts known string keys to existing atoms" do
|
||||
_ = :title
|
||||
_ = :status
|
||||
assert MapUtils.safe_atomize_key("title") == :title
|
||||
assert MapUtils.safe_atomize_key("status") == :status
|
||||
end
|
||||
|
||||
test "leaves unknown string keys as strings without creating new atoms" do
|
||||
unique_keys = for i <- 1..100, do: "csm001_fictive_#{i}_#{:erlang.unique_integer()}"
|
||||
|
||||
Enum.each(unique_keys, fn key ->
|
||||
result = MapUtils.safe_atomize_key(key)
|
||||
assert is_binary(result)
|
||||
assert result == key
|
||||
assert_raise ArgumentError, fn -> String.to_existing_atom(key) end
|
||||
end)
|
||||
end
|
||||
|
||||
test "passes atoms through unchanged" do
|
||||
assert MapUtils.safe_atomize_key(:title) == :title
|
||||
end
|
||||
|
||||
test "safe_atomize_keys recursively converts map keys safely" do
|
||||
input = %{
|
||||
"title" => "Hello",
|
||||
"status" => "draft",
|
||||
"nested" => %{"title" => "Inner", "completely_unknown_key" => "val"},
|
||||
"items" => [%{"title" => "One"}, %{"title" => "Two"}]
|
||||
}
|
||||
|
||||
_ = :title
|
||||
_ = :status
|
||||
_ = :nested
|
||||
_ = :items
|
||||
|
||||
result = MapUtils.safe_atomize_keys(input)
|
||||
|
||||
assert result.title == "Hello"
|
||||
assert result.status == "draft"
|
||||
assert result.nested.title == "Inner"
|
||||
assert Map.get(result.nested, "completely_unknown_key") == "val"
|
||||
assert length(result.items) == 2
|
||||
end
|
||||
|
||||
test "safe_atomize_keys does not create atoms for malicious payloads" do
|
||||
unique_suffix = :erlang.unique_integer()
|
||||
|
||||
malicious = for i <- 1..500, into: %{} do
|
||||
{"csm001_malicious_#{i}_#{unique_suffix}", "val"}
|
||||
end
|
||||
|
||||
result = MapUtils.safe_atomize_keys(malicious)
|
||||
|
||||
assert map_size(result) == 500
|
||||
|
||||
Enum.each(1..500, fn i ->
|
||||
key = "csm001_malicious_#{i}_#{unique_suffix}"
|
||||
assert Map.get(result, key) == "val"
|
||||
assert_raise ArgumentError, fn -> String.to_existing_atom(key) end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "atom/string key duality" do
|
||||
test "shared attr helper is used for same-name atom and string reads" do
|
||||
root = File.cwd!()
|
||||
|
||||
Reference in New Issue
Block a user