Maven-Lockfile
ActionsTags
(2)This plugin is a state-of-the-art solution for validating the integrity of a maven build and guarding the build against malicious actors that might tamper with the artifacts. Features:
- generating a lock file that contains the checksums of all the artifacts and dependencies.
- validate the integrity of a build environment prior to building.
- rebuild old versions with the pinned versions from the lockfile
This plugin is available on maven central. See https://search.maven.org/artifact/io.github.chains-project/maven-lockfile for the latest version.
To generate a lock file, run the following command:
mvn io.github.chains-project:maven-lockfile:generate
This generates a lockfile.json file in each module of the repository, in readable JSON. This file contains the checksums of all the artifacts in the repository. The complete dependency tree, with transitive dependencies, is stored in the lockfile (akin a sbom). For multi-module projects, there is one lockfile per module.
Run the following command to validate the repository:
mvn io.github.chains-project:maven-lockfile:validate
If this runs successfully, the repository is valid. All dependencies defined are still the same as when the lock file was generated. If the command fails, this means a dependency has changed.
First create pom.lockfile.xml
mvn io.github.chains-project:maven-lockfile:freeze
This creates a new pom file with the default name pom.lockfile.xml
. A custom name can be passed with the flag pomLockfileOutput
.
In the new pom file, every version of direct dependencies in the original pom will be replaced with the versions from the lockfile. Also, every transitive dependency is added to the pom inside the dependencyManagement
section with the version and scope from the lockfile.
Then, invoke maven with the -f flag
mvn -f pom.lockfile.xml
reduced
will reduce the lockfile only containing the dependencies after dependency resolution conflicts are resolved. This format is smaller, and easier to review and read. Only use this if you do not need the full dependency tree.includeMavenPlugins
will include the maven plugins in the lockfile. This is useful if you want to validate the Maven plugins as well.allowValidationFailure
(default=false) allow validation failures, printing a warning instead of an error. This is useful if you want to only validate the Maven lockfile, but do not need to fail the build in case the lockfile is not valid. Use with caution, you loose all guarantees.includeEnvironment
will include the environment metadata in the lockfile. This is useful if you want to have warnings when the environment changes.checksumAlgorithm
will set the checksum algorithm used to generate the lockfile. The default depends on your checksum mode.checksumMode
will set the checksum mode used to generate the lockfile. See Checksum Modes for more information.skip
will skip the execution of the plugin. This is useful if you would like to disable the plugin for a specific module.lockfileName
(default="lockfile.json") will set the name of the lockfile file to be generated/read.getConfigFromFile
will read the configuration of maven lockfile from the existing lockfile.
An example lockfile is shown below: For a full example, see the lockfile.json file in this repository.
{
"artifactID":"my-app",
"groupID":"com.mycompany.app",
"version":"1",
"lockFileVersion":1,
"dependencies":[
{
"groupId":"org.junit.platform",
"artifactId":"junit-platform-engine",
"version":"1.9.2",
"checksumAlgorithm":"SHA-256",
"checksum":"25f23dc535a091e9dc80c008faf29dcb92be902e6911f77a736fbaf019908367",
"id":"org.junit.platform:junit-platform-engine:1.9.2",
"parent":"org.junit.jupiter:junit-jupiter-engine:5.9.2",
"children":[
{
"groupId":"org.apiguardian",
"artifactId":"apiguardian-api",
"version":"1.1.2",
"checksumAlgorithm":"SHA-256",
"checksum":"b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38",
"id":"org.apiguardian:apiguardian-api:1.1.2",
"parent":"org.junit.platform:junit-platform-engine:1.9.2",
"children":[
]
},
{
"groupId":"org.junit.platform",
"artifactId":"junit-platform-commons",
"version":"1.9.2",
"checksumAlgorithm":"SHA-256",
"checksum":"624a3d745ef1d28e955a6a67af8edba0fdfc5c9bad680a73f67a70bb950a683d",
"id":"org.junit.platform:junit-platform-commons:1.9.2",
"parent":"org.junit.platform:junit-platform-engine:1.9.2",
"children":[
{
"groupId":"org.apiguardian",
"artifactId":"apiguardian-api",
"version":"1.1.2",
"checksumAlgorithm":"SHA-256",
"checksum":"b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38",
"id":"org.apiguardian:apiguardian-api:1.1.2",
"parent":"org.junit.platform:junit-platform-commons:1.9.2",
"children":[
]
}
]
},
{
"groupId":"org.opentest4j",
"artifactId":"opentest4j",
"version":"1.2.0",
"checksumAlgorithm":"SHA-256",
"checksum":"58812de60898d976fb81ef3b62da05c6604c18fd4a249f5044282479fc286af2",
"id":"org.opentest4j:opentest4j:1.2.0",
"parent":"org.junit.platform:junit-platform-engine:1.9.2",
"children":[
]
}
]
}
]
}
This is close to the format of the lock file in the npm package-lock.json file.
We made some java-specific changes to the format, e.g., we added the groupId
field.
For each artifact, we store the hashes of all transitive dependencies in the children
field.
This allows us to validate the integrity of the transitive dependencies as well.
We have created a GithubAction that can be used to validate the integrity of your maven
repository.
A sample workflow is shown below:
Usage:
name: Lockfile
on:
pull_request:
permissions:
contents: read
jobs:
check-lockfile:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: run maven-lockfile
uses: chains-project/maven-lockfile@bdabb56b82feb242cd543af007b333bd8276e44e # v5.3.5
with:
github-token: ${{ secrets.JRELEASER_GITHUB_TOKEN }}
include-maven-plugins: true
If a pom.xml or lockfile.json file is changed, this action will add a commit with the updated lockfile to the pull request. Otherwise, it will validate the lockfile and fail if the lockfile is incorrect. A lockfile is incorrect if any dependency has changed since the lockfile was generated. This includes versions and checksums.
github-token
(required): The GitHub token used to commit the lockfile to the repository.commit-lockfile
(optional, default=true): Whether to commit an updated lockfile to the repository. The action can be used to update lockfiles automatically in e.g. pull requests (se warning about pull-requests from forks). If this is true and the pom.xml or workflow-file has updated it will create and commit the new lockfile - the action will not fail if the lockfile is outdated or invalid and only push the correct version. If this is false or the pom.xml and workflow-file remain unchanged, the action be used to verify the lockfile is correct - the action will fail in case of an outdated or invalid lockfile.commit-message
(optional, default='chore: update lockfile'): The commit message for the lockfile ifcommit-lockfile
is true.commit-author
(optional, default='github_actions'): The author for the lockfile commit ifcommit-lockfile
is true. GitHub provides three values for this field.- github_actor ->
UserName <UserName@users.noreply.github.com>
- user_info ->
Your Display Name <your-actual@email.com>
- github_actions ->
github-actions <email associated with the github logo>
- github_actor ->
include-maven-plugins
(optional, default='false'): Whether to include Maven plugins in the lockfile.lockfile-name
(optional, default="lockfile.json"): The name of the lockfile to generate/validate.workflow-filename
(optional, default='Lockfile.yml'): The name of the workflow file, to automatically trigger lockfile generation when the workflow is updated.
If you are updating your POM.xml during your release (e.g. by using mvn version:set
) to remove -SNAPSHOT
suffixes or increase the version during the release process the lockfile will need to be regenerated as well or the commit tagged with the release will contain a lockfile with the wrong version.
If you are setting the -SNAPSHOT
version in the release action/script as well it is a good idea to update the lockfile to avoid a separate chore: lockfile
commit.
As an example, the steps for the CI in maven-lockfile is:
- set the version from
X.Y.Z-SNAPSHOT
toX.Y.Z
inpom.xml
- run maven-lockfile using
mvn io.github.chains-project:maven-lockfile:5.3.5:generate
- build and release
- create
Releasing version X.Y.Z
commit and tag it withvX.Y.Z
- set the version to
X.Y.(Z+1)-SNAPSHOT
inpom.xml
- run maven-lockfile using
mvn io.github.chains-project:maven-lockfile:5.3.5:generate
- create
Setting SNAPSHOT version X.Y.(Z+1)-SNAPSHOT
commit
Here we list some related work that we found while researching this topic.
- Maven: https://github.com/vandmo/dependency-lock-maven-plugin
- Gradle: For Gradle, there exists a built-in solution: https://docs.gradle.org/current/userguide/dependency_locking.html. This solution only works for Gradle builds and is deeply connected to the Gradle build system. The Gradle ecosystem is fast changing and so is its dependency resolution. Our lockfile is independent of the build system and can be used to validate the integrity of a maven repository.
- NPM: https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json
- Ruby: Bundler has built-in checksum verification since 2.6, see doc
Maven-Lockfile is not certified by GitHub. It is provided by a third-party and is governed by separate terms of service, privacy policy, and support documentation.