Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
153 lines
4.7 KiB
Elixir
153 lines
4.7 KiB
Elixir
defmodule TarakanWeb.API.FindingSignalsApiTest do
|
|
@moduledoc """
|
|
The three worker submission endpoints that had no HTTP surface until now:
|
|
calibrated severity, code embeddings, and cluster detectors.
|
|
"""
|
|
use TarakanWeb.ConnCase, async: true
|
|
|
|
import Ecto.Query, only: [from: 2]
|
|
|
|
alias Tarakan.Accounts.ApiCredentials
|
|
alias Tarakan.Repo
|
|
alias Tarakan.Scans.CanonicalFinding
|
|
|
|
setup %{conn: conn} do
|
|
submitter = github_account_fixture()
|
|
repository = listed_github_repository_fixture(submitter)
|
|
|
|
findings =
|
|
Jason.encode!(%{
|
|
"tarakan_scan_format" => 1,
|
|
"findings" => [
|
|
%{
|
|
"file" => "lib/vulnerable.ex",
|
|
"severity" => "critical",
|
|
"title" => "Command injection #{System.unique_integer([:positive])}",
|
|
"description" => "User input reaches System.cmd without escaping."
|
|
}
|
|
]
|
|
})
|
|
|
|
scan_fixture(repository, submitter, %{"findings_json" => findings})
|
|
finding = Repo.one!(from f in CanonicalFinding, where: f.repository_id == ^repository.id)
|
|
|
|
reviewer = reviewer_account_fixture()
|
|
|
|
{:ok, token, _cred} =
|
|
ApiCredentials.create(reviewer, %{
|
|
name: "signals",
|
|
scopes: ["reviews:read", "reviews:verify"]
|
|
})
|
|
|
|
%{
|
|
conn: put_req_header(conn, "authorization", "Bearer #{token}"),
|
|
anon: build_conn(),
|
|
repository: repository,
|
|
finding: finding
|
|
}
|
|
end
|
|
|
|
defp signal_path(repository, finding, suffix) do
|
|
"/api/github.com/#{repository.owner}/#{repository.name}/findings/#{finding.public_id}/#{suffix}"
|
|
end
|
|
|
|
describe "POST .../severity" do
|
|
test "stores a rescore beside the submitter's claim", ctx do
|
|
conn =
|
|
post(ctx.conn, signal_path(ctx.repository, ctx.finding, "severity"), %{
|
|
"severity" => "low",
|
|
"rubric" => "rubric-v1"
|
|
})
|
|
|
|
assert %{"calibrated_severity" => "low"} = json_response(conn, 200)
|
|
assert Repo.get!(CanonicalFinding, ctx.finding.id).severity == "critical"
|
|
end
|
|
|
|
test "rejects a severity outside the scale", ctx do
|
|
conn =
|
|
post(ctx.conn, signal_path(ctx.repository, ctx.finding, "severity"), %{
|
|
"severity" => "catastrophic",
|
|
"rubric" => "rubric-v1"
|
|
})
|
|
|
|
assert %{"errors" => %{"calibrated_severity" => _}} = json_response(conn, 422)
|
|
end
|
|
end
|
|
|
|
describe "POST .../embedding" do
|
|
test "stores the vector and assigns a cluster", ctx do
|
|
conn =
|
|
post(ctx.conn, signal_path(ctx.repository, ctx.finding, "embedding"), %{
|
|
"embedding" => [0.1, 0.9, 0.2],
|
|
"embedding_model" => "test-embed-v1"
|
|
})
|
|
|
|
assert %{"code_pattern_key" => key} = json_response(conn, 200)
|
|
assert key =~ ~r/^code:/
|
|
end
|
|
|
|
test "rejects an all-zero vector, which would match everything", ctx do
|
|
conn =
|
|
post(ctx.conn, signal_path(ctx.repository, ctx.finding, "embedding"), %{
|
|
"embedding" => [0.0, 0.0, 0.0],
|
|
"embedding_model" => "test-embed-v1"
|
|
})
|
|
|
|
assert %{"errors" => %{"embedding" => _}} = json_response(conn, 422)
|
|
end
|
|
end
|
|
|
|
describe "POST /api/patterns/:key/rule" do
|
|
@rule """
|
|
rules:
|
|
- id: tarakan-cmd-injection
|
|
message: user input reaches System.cmd
|
|
severity: ERROR
|
|
languages: [elixir]
|
|
patterns:
|
|
- pattern: System.cmd($C, $A)
|
|
"""
|
|
|
|
test "accepts a validated detector and marks it servable", ctx do
|
|
conn =
|
|
post(ctx.conn, "/api/patterns/code:abc/rule", %{
|
|
"rule_yaml" => @rule,
|
|
"language" => "elixir",
|
|
"checked_count" => 3,
|
|
"matched_count" => 2,
|
|
"matched_finding_ids" => [Ecto.UUID.generate(), Ecto.UUID.generate()]
|
|
})
|
|
|
|
assert %{"servable" => true, "matched_count" => 2} = json_response(conn, 201)
|
|
end
|
|
|
|
test "a detector that matched nothing is stored but not servable", ctx do
|
|
conn =
|
|
post(ctx.conn, "/api/patterns/code:abc/rule", %{
|
|
"rule_yaml" => @rule,
|
|
"checked_count" => 3,
|
|
"matched_count" => 0,
|
|
"matched_finding_ids" => []
|
|
})
|
|
|
|
assert %{"servable" => false, "validated_at" => nil} = json_response(conn, 201)
|
|
end
|
|
|
|
test "rejects a match count with no receipts", ctx do
|
|
conn =
|
|
post(ctx.conn, "/api/patterns/code:abc/rule", %{
|
|
"rule_yaml" => @rule,
|
|
"checked_count" => 3,
|
|
"matched_count" => 2,
|
|
"matched_finding_ids" => []
|
|
})
|
|
|
|
assert %{"errors" => %{"matched_finding_ids" => _}} = json_response(conn, 422)
|
|
end
|
|
|
|
test "anonymous submissions are refused", ctx do
|
|
conn = post(ctx.anon, "/api/patterns/code:abc/rule", %{"rule_yaml" => @rule})
|
|
assert json_response(conn, 401)
|
|
end
|
|
end
|
|
end
|