Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/hex/remote_converger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ defmodule Hex.RemoteConverger do
end

advisories
|> :mix_hex_advisory.group_for_display()
|> Enum.with_index()
|> Enum.each(fn {advisory, index} ->
if retired || index > 0, do: Hex.Shell.info("")
Expand Down
15 changes: 14 additions & 1 deletion lib/hex/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -282,15 +282,28 @@ defmodule Hex.Utils do
[]
end

aliases =
case advisory do
%{aliases: [_ | _] = aliases} ->
ids = Enum.map_join(aliases, ", ", &alias_id/1)
["\n", line_prefix, "aka: ", ids]

_ ->
[]
end

url =
case advisory do
%{html_url: url} -> ["\n", line_prefix, :underline, url, :reset]
_ -> []
end

[id, severity, "\n", line_prefix, summary, url]
[id, severity, aliases, "\n", line_prefix, summary, url]
end

defp alias_id(%{id: id}), do: id
defp alias_id(id) when is_binary(id), do: id

# From https://github.com/fishcakez/dialyze/blob/6698ae582c77940ee10b4babe4adeff22f1b7779/lib/mix/tasks/dialyze.ex#L168
def otp_version do
major = :erlang.system_info(:otp_release) |> List.to_string()
Expand Down
5 changes: 4 additions & 1 deletion lib/mix/tasks/hex.audit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ defmodule Mix.Tasks.Hex.Audit do
end

defp advisory_status(%{repo: repo, name: package, version: version}) do
advisories = Registry.advisories(repo, package, version) || []
advisories =
(Registry.advisories(repo, package, version) || [])
|> :mix_hex_advisory.group_for_display()

Enum.map(advisories, fn advisory -> {package, version, advisory} end)
end

Expand Down
2 changes: 2 additions & 0 deletions scripts/vendor_hex_core.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ filenames="hex_api_auth.erl \
hex_api_short_url.erl \
hex_api_user.erl \
hex_api.erl \
hex_advisory.erl \
hex_core.hrl \
hex_core.erl \
hex_erl_tar.erl \
Expand Down Expand Up @@ -56,6 +57,7 @@ search_to_replace="hex_core: \
hex_http \
hex_repo \
hex_api \
hex_advisory \
safe_erl_term"

rm -f $target_dir/$prefix*
Expand Down
113 changes: 113 additions & 0 deletions src/mix_hex_advisory.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Display-time deduplication of security advisories.
%%
%% Multiple advisory sources (EEF, GHSA, NVD, ...) can publish the same
%% vulnerability under different identifiers and cross-reference each other
%% via the `aliases' field. `group_for_display/1' groups such advisories
%% and picks a deterministic primary so callers can render one entry per
%% vulnerability.
-module(mix_hex_advisory).
-export([group_for_display/1]).

-type advisory() :: map().
-type group() :: map().

-spec group_for_display([advisory()]) -> [group()].
group_for_display(Advisories) ->
%% Preserve input order of first member per group.
{GroupOrder, GroupsByKey} =
lists:foldl(
fun(Adv, {Order, Map}) ->
Key = group_key(Adv),
case maps:is_key(Key, Map) of
true ->
Existing = maps:get(Key, Map),
{Order, Map#{Key := Existing ++ [Adv]}};
false ->
{Order ++ [Key], Map#{Key => [Adv]}}
end
end,
{[], #{}},
Advisories
),
[merge_group(maps:get(Key, GroupsByKey)) || Key <- GroupOrder].

%%====================================================================
%% Grouping
%%====================================================================

%% Key is the first CVE-prefixed identifier across {id, aliases}, else id.
group_key(#{id := Id} = Adv) ->
Ids = [Id | maps:get(aliases, Adv, [])],
case lists:dropwhile(fun(I) -> not is_cve(I) end, Ids) of
[Cve | _] -> Cve;
[] -> Id
end.

is_cve(<<"CVE-", _/binary>>) -> true;
is_cve(_) -> false.

%%====================================================================
%% Merging
%%====================================================================

merge_group(Advisories) ->
Primary = pick_primary(Advisories),
Rest = [A || A <- Advisories, maps:get(id, A) =/= maps:get(id, Primary)],
Primary#{aliases => display_aliases(Primary, [Primary | Rest])}.

pick_primary(Advisories) ->
[Primary | _] = lists:sort(
fun(A, B) -> source_key(A) =< source_key(B) end,
Advisories
),
Primary.

source_key(#{id := Id}) -> {source_priority(Id), Id}.

source_priority(<<"EEF-", _/binary>>) -> 0;
source_priority(<<"GHSA-", _/binary>>) -> 1;
source_priority(<<"NVD-", _/binary>>) -> 2;
source_priority(_) -> 3.

%%====================================================================
%% Aliases
%%====================================================================

display_aliases(Primary, Advisories) ->
PrimaryId = maps:get(id, Primary),
AdvisoryIds = sets:from_list([maps:get(id, A) || A <- Advisories]),
AllIds = lists:flatmap(fun identifiers/1, Advisories),
Unique = uniq(AllIds),
[
#{
id => Id,
url => alias_url(Id, AdvisoryIds)
}
|| Id <- Unique, Id =/= PrimaryId
].

identifiers(Advisory) ->
[maps:get(id, Advisory) | maps:get(aliases, Advisory, [])].

alias_url(Id, AdvisoryIds) ->
case sets:is_element(Id, AdvisoryIds) of
true -> <<"https://osv.dev/vulnerability/", Id/binary>>;
false -> undefined
end.

uniq(List) ->
{_, Out} =
lists:foldl(
fun(X, {Seen, Acc}) ->
case sets:is_element(X, Seen) of
true -> {Seen, Acc};
false -> {sets:add_element(X, Seen), Acc ++ [X]}
end
end,
{sets:new(), []},
List
),
Out.
2 changes: 1 addition & 1 deletion src/mix_hex_api.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_api_auth.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API - Authentication.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_api_key.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API - Keys.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_api_oauth.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API - OAuth.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_api_organization.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API - Organizations.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_api_organization_member.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API - Organization Members.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_api_package.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API - Packages.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_api_package_owner.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API - Package Owners.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_api_release.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API - Releases.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_api_short_url.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API - Short URLs.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_api_user.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex HTTP API - Users.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_core.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% `hex_core' entrypoint module.
Expand Down
4 changes: 2 additions & 2 deletions src/mix_hex_core.hrl
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

-define(HEX_CORE_VERSION, "0.16.0").
-define(HEX_CORE_VERSION, "0.17.0").
2 changes: 1 addition & 1 deletion src/mix_hex_erl_tar.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% This file is a copy of erl_tar.erl from OTP with the following modifications:
%% 1. Module renamed from erl_tar to mix_hex_erl_tar
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_erl_tar.hrl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% This file is a copy of erl_tar.hrl from OTP with the following modifications:
%% 1. Added chunk_size field to #read_opts{} for streaming extraction to disk
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_http.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% HTTP contract.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_http_httpc.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% httpc-based implementation of {@link mix_hex_http} contract.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_licenses.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% @doc
%% Hex Licenses.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_pb_names.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% Vendored from hex_core v0.16.0 (0e332e5), do not edit manually
%% Vendored from hex_core v0.17.0 (cadf1b8), do not edit manually

%% -*- coding: utf-8 -*-
%% % this file is @generated
Expand Down
Loading
Loading