diff --git a/CHANGELOG.md b/CHANGELOG.md index ca9996d047..b970cc8efc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,16 @@ and this project adheres to than an application error. [#4947](https://github.com/OpenFn/lightning/issues/4947) +### Security + +- Enabled Hex's dependency cooldown (`hex: [cooldown: "1w"]`), so newly-resolved + dependency versions must have existed for at least a week before Hex will pull + them in, mitigating supply-chain attacks that get caught and retired within + days. Normal builds against an intact `mix.lock` are unaffected. + `bin/bootstrap` now enforces Hex `>= 2.5.0`, which is required for the + cooldown to take effect. + [#4971](https://github.com/OpenFn/lightning/pull/4971) + ## [2.16.8] - 2026-07-01 ## [2.16.8-pre] - 2026-06-18 diff --git a/bin/bootstrap.d/common.sh b/bin/bootstrap.d/common.sh index 48730a4776..30f987b330 100755 --- a/bin/bootstrap.d/common.sh +++ b/bin/bootstrap.d/common.sh @@ -151,6 +151,30 @@ ensure_tool_versions() { fi } +# Dependency cooldown (mix.exs `hex: [cooldown: ...]`) is silently ignored by +# Hex older than 2.5.0, giving false confidence. Enforce the minimum here. +ensure_hex_version() { + local required="2.5.0" + local have + have="$(mix hex.info 2>/dev/null | awk '/^Hex:/ {print $2}')" + + if [[ -n "$have" ]] && printf '%s\n%s\n' "$required" "$have" | sort -V -C; then + echo " Hex: $have (>= $required, dependency cooldown active)" + return + fi + + echo " Hex ${have:-} is older than $required; upgrading for dependency cooldown support..." + mix local.hex --force + + have="$(mix hex.info 2>/dev/null | awk '/^Hex:/ {print $2}')" + if [[ -z "$have" ]] || ! printf '%s\n%s\n' "$required" "$have" | sort -V -C; then + echo "Failed to obtain Hex >= $required (found ${have:-})." >&2 + echo "The dependency cooldown in mix.exs will be silently ignored without it." >&2 + exit 1 + fi + echo " Hex upgraded to $have" +} + run_bootstrap() { echo "Gathering environment information..." echo "Platform: $OS $ARCH" @@ -181,6 +205,7 @@ run_bootstrap() { echo "Setting up Elixir environment..." mix local.hex --if-missing --force mix local.rebar --if-missing --force + ensure_hex_version echo "Installing Elixir dependencies..." mix deps.get diff --git a/lib/lightning_web/live/sandbox_live/index.ex b/lib/lightning_web/live/sandbox_live/index.ex index ff284041c9..ee5c57546e 100644 --- a/lib/lightning_web/live/sandbox_live/index.ex +++ b/lib/lightning_web/live/sandbox_live/index.ex @@ -911,8 +911,10 @@ defmodule LightningWeb.SandboxLive.Index do # a merge never silently deletes them, and removal is opt-in. deleted_entries = target_workflows - |> Enum.reject(fn wf -> MapSet.member?(source_workflow_names, wf.name) end) - |> Enum.reject(fn wf -> workflow_added_after_fork?(wf, source) end) + |> Enum.reject(fn wf -> + MapSet.member?(source_workflow_names, wf.name) or + workflow_added_after_fork?(wf, source) + end) |> Enum.map(fn wf -> %MergeWorkflow{ id: wf.id, diff --git a/mix.exs b/mix.exs index 6fa1742f88..c2adcd7a4f 100644 --- a/mix.exs +++ b/mix.exs @@ -13,6 +13,7 @@ defmodule Lightning.MixProject do start_permanent: Mix.env() == :prod, aliases: aliases(), deps: deps(), + hex: [cooldown: "1w"], dialyzer: [ plt_add_apps: [:mix], plt_local_path: "priv/plts/",