Skip to content
Draft
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/dev.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ if Mix.env() == :dev do
copy(original_ets, new_ets, {:inner_checksum, @repo, package, version})
copy(original_ets, new_ets, {:outer_checksum, @repo, package, version})
copy(original_ets, new_ets, {:retired, @repo, package, version})
copy(original_ets, new_ets, {:advisories, @repo, package, version})
copy(original_ets, new_ets, {:timestamp, @repo, package, version})

[{_, dep_tuples}] = :ets.lookup(original_ets, {:deps, @repo, package, version})
Expand Down
27 changes: 25 additions & 2 deletions lib/hex/registry/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Hex.Registry.Server do
@name __MODULE__
@filename "cache.ets"
@timeout 60_000
@ets_version 3
@ets_version 4
@public_keys_html "https://hex.pm/docs/public_keys"

def start_link(opts \\ []) do
Expand Down Expand Up @@ -51,6 +51,10 @@ defmodule Hex.Registry.Server do
GenServer.call(@name, {:retired, repo, package, version}, @timeout)
end

def advisories(repo, package, version) do
GenServer.call(@name, {:advisories, repo, package, version}, @timeout)
end

def last_update() do
GenServer.call(@name, :last_update, @timeout)
end
Expand Down Expand Up @@ -203,6 +207,12 @@ defmodule Hex.Registry.Server do
end)
end

def handle_call({:advisories, repo, package, version}, from, state) do
maybe_wait({repo, package}, from, state, fn ->
lookup(state.ets, {:advisories, repo || "hexpm", package, version})
end)
end

def handle_call(:last_update, _from, state) do
time = lookup(state.ets, :last_update)
{:reply, time, state}
Expand Down Expand Up @@ -299,6 +309,7 @@ defmodule Hex.Registry.Server do
# {{:inner_checksum, ^repo, _package, _version}, _} -> true
# {{:outer_checksum, ^repo, _package, _version}, _} -> true
# {{:retired, ^repo, _package, _version}, _} -> true
# {{:advisories, ^repo, _package, _version}, _} -> true
# {{:registry_etag, ^repo, _package}, _} -> true
# {{:timestamp, ^repo, _package}, _} -> true
# {{:timestamp, ^repo, _package, _version}, _} -> true
Expand All @@ -312,6 +323,7 @@ defmodule Hex.Registry.Server do
{{{:inner_checksum, :"$1", :"$2", :"$3"}, :_}, [{:"=:=", {:const, repo}, :"$1"}], [true]},
{{{:outer_checksum, :"$1", :"$2", :"$3"}, :_}, [{:"=:=", {:const, repo}, :"$1"}], [true]},
{{{:retired, :"$1", :"$2", :"$3"}, :_}, [{:"=:=", {:const, repo}, :"$1"}], [true]},
{{{:advisories, :"$1", :"$2", :"$3"}, :_}, [{:"=:=", {:const, repo}, :"$1"}], [true]},
{{{:registry_etag, :"$1", :"$2"}, :_}, [{:"=:=", {:const, repo}, :"$1"}], [true]},
{{{:timestamp, :"$1", :"$2"}, :_}, [{:"=:=", {:const, repo}, :"$1"}], [true]},
{{{:timestamp, :"$1", :"$2", :"$3"}, :_}, [{:"=:=", {:const, repo}, :"$1"}], [true]},
Expand Down Expand Up @@ -359,17 +371,27 @@ defmodule Hex.Registry.Server do
end
end

defp write_result({:ok, {code, headers, %{releases: releases}}}, repo, package, %{ets: tid})
defp write_result({:ok, {code, headers, %{releases: releases} = result}}, repo, package, %{
ets: tid
})
when code in 200..299 do
delete_package(repo, package, tid)
now = :calendar.universal_time()
pkg_advisories = result[:advisories] || []

Enum.each(releases, fn %{version: version} = release ->
:ets.insert(tid, {{:timestamp, repo, package, version}, now})
:ets.insert(tid, {{:inner_checksum, repo, package, version}, release[:inner_checksum]})
:ets.insert(tid, {{:outer_checksum, repo, package, version}, release[:outer_checksum]})
:ets.insert(tid, {{:retired, repo, package, version}, release[:retired]})

release_advisories =
(release[:advisory_indexes] || [])
|> Enum.map(&Enum.at(pkg_advisories, &1))
|> Enum.reject(&is_nil/1)

:ets.insert(tid, {{:advisories, repo, package, version}, release_advisories})

deps =
Enum.map(release[:dependencies], fn dep ->
{dep[:repository] || repo, dep[:package], dep[:app] || dep[:package], dep[:requirement],
Expand Down Expand Up @@ -511,6 +533,7 @@ defmodule Hex.Registry.Server do
Enum.each(versions, fn version ->
:ets.delete(tid, {:checksum, repo, package, version})
:ets.delete(tid, {:retired, repo, package, version})
:ets.delete(tid, {:advisories, repo, package, version})
:ets.delete(tid, {:deps, repo, package, version})
end)
end
Expand Down
41 changes: 31 additions & 10 deletions lib/hex/remote_converger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,11 @@ defmodule Hex.RemoteConverger do

defp print_dependency_group(deps, mod) do
Enum.each(deps, fn {name, repo, previous_version, version, warning} ->
advisories = Registry.advisories(repo, name, version) || []

print_status(
Registry.retired(repo, name, version),
advisories,
mod,
name,
previous_version,
Expand All @@ -491,7 +494,7 @@ defmodule Hex.RemoteConverger do
end)
end

defp print_status(nil, mod, name, previous_version, version, warning) do
defp print_status(nil, advisories, mod, name, previous_version, version, warning) do
case mod do
:new ->
Hex.Shell.info(Hex.Shell.format([:green, " #{name} #{version}", :red, "#{warning}"]))
Expand Down Expand Up @@ -519,18 +522,36 @@ defmodule Hex.RemoteConverger do
])
)
end

Enum.each(advisories, fn advisory ->
version_string = version_string(mod, name, previous_version, version)

Hex.Shell.info(
Hex.Shell.format([:yellow, "#{version_string} has a security advisory!", :reset])
)

Hex.Shell.info(Hex.Shell.format([" " | Hex.Utils.format_advisory_ansi(advisory)]))
end)
end

defp print_status(retired, mod, name, previous_version, version, _warning) do
case mod do
mod when mod in [:eq, :new] ->
Hex.Shell.warn(" #{name} #{version} RETIRED!")
Hex.Shell.warn(" #{Hex.Utils.package_retirement_message(retired)}")
defp print_status(retired, advisories, mod, name, previous_version, version, _warning) do
version_string = version_string(mod, name, previous_version, version)

_ ->
Hex.Shell.warn(" #{name} #{previous_version} => #{version} RETIRED!")
Hex.Shell.warn(" #{Hex.Utils.package_retirement_message(retired)}")
end
Hex.Shell.warn("#{version_string} RETIRED!")
Hex.Shell.warn(" #{Hex.Utils.package_retirement_message(retired)}")

Enum.each(advisories, fn advisory ->
Hex.Shell.warn("#{version_string} has a security advisory!")
Hex.Shell.warn(" #{Hex.Utils.format_advisory(advisory)}")
end)
end

defp version_string(mod, name, _previous_version, version) when mod in [:eq, :new] do
" #{name} #{version}"
end

defp version_string(_mod, name, previous_version, version) do
" #{name} #{previous_version} => #{version}"
end

defp verify_prefetches(prefetches) do
Expand Down
41 changes: 41 additions & 0 deletions lib/hex/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,47 @@ defmodule Hex.Utils do
"(#{package_retirement_reason(reason_code)})"
end

def advisory_severity(:SEVERITY_NONE), do: "NONE"
def advisory_severity(:SEVERITY_LOW), do: "LOW"
def advisory_severity(:SEVERITY_MEDIUM), do: "MEDIUM"
def advisory_severity(:SEVERITY_HIGH), do: "HIGH"
def advisory_severity(:SEVERITY_CRITICAL), do: "CRITICAL"
def advisory_severity(other), do: other

def advisory_severity_color(:SEVERITY_NONE), do: :normal
def advisory_severity_color(:SEVERITY_LOW), do: :yellow
def advisory_severity_color(:SEVERITY_MEDIUM), do: :yellow
def advisory_severity_color(:SEVERITY_HIGH), do: :red
def advisory_severity_color(:SEVERITY_CRITICAL), do: [:bright, :red]
def advisory_severity_color(_), do: :normal

def format_advisory(%{summary: summary, severity: severity, html_url: url}) do
"(#{advisory_severity(severity)}) #{summary} - #{url}"
end

def format_advisory(%{summary: summary, html_url: url}) do
"#{summary} - #{url}"
end

def format_advisory(%{summary: summary, severity: severity}) do
"(#{advisory_severity(severity)}) #{summary}"
end

def format_advisory(%{summary: summary}) do
summary
end

def format_advisory_ansi(%{severity: severity} = advisory) do
color = advisory_severity_color(severity)
label = "(#{advisory_severity(severity)})"
rest = " " <> format_advisory(Map.delete(advisory, :severity))
List.flatten([color, label, :reset, rest])
end

def format_advisory_ansi(advisory) do
[format_advisory(advisory)]
end

# 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
49 changes: 40 additions & 9 deletions lib/mix/tasks/hex.audit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ defmodule Mix.Tasks.Hex.Audit do
use Mix.Task
alias Hex.Registry.Server, as: Registry

@shortdoc "Shows retired Hex deps for the current project"
@shortdoc "Shows retired Hex deps and security advisories for the current project"

@moduledoc """
Shows all Hex dependencies that have been marked as retired.
Shows all Hex dependencies that have been marked as retired or have
security advisories.

Retired packages are no longer recommended to be used by their
maintainers. The task will display a message describing
the reason for retirement and exit with a non-zero code
if any retired dependencies are found.

Security advisories indicate known vulnerabilities in a package version.
The task will display advisory details and exit with a non-zero code
if any packages with advisories are found.

> In a project, this task must be invoked before any other tasks
> that may load or start your application. Otherwise, you must
> explicitly list `:hex` as part of your `:extra_applications`.
Expand All @@ -31,22 +36,32 @@ defmodule Mix.Tasks.Hex.Audit do
|> Hex.Mix.packages_from_lock()
|> Registry.prefetch()

case retired_packages(lock) do
[] ->
Hex.Shell.info("No retired packages found")
retired = retired_packages(lock)
advisory = advisory_packages(lock)

packages ->
if retired == [] and advisory == [] do
Hex.Shell.info("No retired or security advisory packages found")
else
unless retired == [] do
header = ["Dependency", "Version", "Retirement reason"]
Mix.Tasks.Hex.print_table(header, packages)
Mix.Tasks.Hex.print_table(header, retired)
Hex.Shell.error("Found retired packages")
Mix.Tasks.Hex.set_exit_code(1)
end

unless advisory == [] do
header = ["Dependency", "Version", "Advisory"]
Mix.Tasks.Hex.print_table(header, advisory)
Hex.Shell.error("Found packages with security advisories")
end

Mix.Tasks.Hex.set_exit_code(1)
end
end

@impl true
def tasks() do
[
{"", "Shows retired Hex deps for the current project"}
{"", "Shows retired Hex deps and security advisories for the current project"}
]
end

Expand All @@ -66,4 +81,20 @@ defmodule Mix.Tasks.Hex.Audit do
defp retirement_status(nil) do
[]
end

defp advisory_packages(lock) do
Enum.flat_map(lock, fn {_app, lock} -> advisory_status(Hex.Utils.lock(lock)) end)
end

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

Enum.map(advisories, fn advisory ->
[package, version, Hex.Utils.format_advisory(advisory)]
end)
end

defp advisory_status(nil) do
[]
end
end
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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), do not edit manually

%% @doc
%% `hex_core' entrypoint module.
Expand Down
2 changes: 1 addition & 1 deletion src/mix_hex_core.hrl
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
%% Vendored from hex_core v0.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), do not edit manually

-define(HEX_CORE_VERSION, "0.15.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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), 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.15.0 (90f9f59), do not edit manually
%% Vendored from hex_core v0.15.0 (99c443b), do not edit manually

%% @doc
%% HTTP contract.
Expand Down
Loading
Loading