Supply Chain Attack - Lock Files
Need
Reproducible and verifiable dependency resolution with a committed mix.lock
Context
• Usage of Elixir 1.15+ with Mix for building application services
• Usage of Hex packages as dependencies
Description
1. Non compliant code
defmodule MyApp.MixProject do
use Mix.Project
def project do
[app: :my_app, version: "1.0.0", elixir: "~> 1.15", deps: deps()]
end
defp deps do...The `mix.exs` file below declares Hex dependencies with open requirements such as `">= 0.0.0"`, and the project does not commit a `mix.lock` file, for example because it is listed in `.gitignore`. Without the lock file, every `mix deps.get` resolves the requirements again against the current state of Hex. Each developer machine, CI run and release build can install a different version of every dependency, including versions published after the code was reviewed. If a maintainer account is compromised, or a malicious version of a transitive dependency is published, the next build installs and runs it, since Mix compiles and executes dependency code during the build. The lock file also records a checksum of every package. Without it, nothing detects that the content of a version has changed.
2. Steps
• Commit `mix.lock` to version control next to every `mix.exs` that declares Hex dependencies, and remove it from `.gitignore`.
• Constrain dependencies with the `~>` operator instead of open requirements such as `">= 0.0.0"`.
• Run `mix deps.get --check-locked` in CI so builds fail when the lock file is out of date.
• Update dependencies deliberately with `mix deps.update` and review the changes to `mix.lock`.
• Run `mix hex.audit` regularly to detect retired packages.
3. Secure code example
defmodule MyApp.MixProject do
use Mix.Project
def project do
[app: :my_app, version: "1.0.0", elixir: "~> 1.15", deps: deps()]
end
defp deps do...The corrected project constrains each dependency with the `~>` operator, which accepts compatible updates within a major version, and commits the `mix.lock` file generated by `mix deps.get` next to `mix.exs`. `mix.lock` pins the exact version and the checksum of every direct and transitive dependency, so every machine and every build installs the same, reviewed code, and Mix refuses a package whose content no longer matches its checksum. Updates happen deliberately with `mix deps.update`, and the resulting change to the lock file is reviewed like any other code. In CI, `mix deps.get --check-locked` fails the build when `mix.exs` and `mix.lock` are out of sync, and `mix hex.audit` reports retired packages.
References
• 431. Supply Chain Attack - Lock Files