Fix vulnerabilities
How to close the findings of a project: with an upgrade to a fixed version, or with a TuxCare build of the version that you use now. The CLI makes the change, checks it, and keeps it current.
Two ways to fix #
| Upgrade the package | Use a TuxCare build | |
|---|---|---|
| What changes | The version: you move to the lowest version that fixes the advisory. | The source: the same version, built by TuxCare from source with the fix added. Its version ends in -tuxcare.N (Python: +tuxcare.N). |
| Use it when | The fixed version is close, and its changes do not break your code. | The fix is in a new major version, or the project that made the package no longer fixes your version. |
| How | securechain fix writes the change for you. By hand, for a direct dependency: your package manager, with the flag that pins the exact version, for example npm install --save-exact lodash@4.17.21. For pnpm the flag is also --save-exact, and for Yarn and Bun it is --exact. Without it, package.json gets a range such as ^4.17.21. A named install also sorts the dependency list of package.json again: that is your package manager, not harden. For a transitive package, see Upgrade a package. | securechain harden. See TuxCare builds. |
| Needs | Nothing more. securechain fix needs a portal token and no TuxCare registry. | A subscription with TuxCare builds. |
The portal tells you which fix applies:
- The Fix plan tab of a repository lists each package to change, the version to move to, and where the fix comes from:
upstream
or a TuxCare build. - The page of a finding shows How to fix: the recommended fix, the alternatives, and the command to run.
- The TuxCare coverage tab of Packages counts the packages of the workspace that a TuxCare build can fix.
This page is about the packages of a project. An OS package of a server is not a dependency of a project: securechain harden and securechain fix do not change it. Its How to fix card gives the update command of the package manager of the server, for example sudo yum update nettle nettle-devel. See Update an OS package.
For a language package, the portal recommends one fix in this order:
- A TuxCare build of the version that you use now, for example
7.29.3-tuxcare.4for 7.29.3. A TuxCare build fixes only the version that it was built from. - Else the lowest upstream version above yours that fixes the finding.
- Else, when no upstream version has the fix, the TuxCare build of the nearest higher version.
Upgrade a package #
- Open the Fix plan tab of the repository. The packages with the worst severity come first. Inside one severity, a package with a known-exploited finding comes first.
- Run
securechain fix, or change the version of the package with your package manager. For a transitive package, upgrade the direct dependency that pulls it in, or pin it with the mechanism of your tool:overridesfor npm, pnpm, and Bun,resolutionsfor Yarn, a constraints file for pip,dependencyManagementfor Maven, aconstraintsblock for Gradle, a directPackageReferencefor NuGet, ordependency_overridesfor Dart. With Go,go getmoves it. With Cargo, Composer, and Bundler,cargo update,composer update, andbundle updatemove it inside the ranges of its parents; outside them, a parent must move. The How to fix card gives the line or the command when one fits your tool. On npm and PyPI, do not install a transitive package by name: that adds a new direct dependency, and the copy under its parent can stay vulnerable. For a TuxCare build,securechain hardenwrites the override. - Run your tests, and commit the manifest and the lockfile.
- Run
securechain sca, or let the CI job run it. The finding closes when the portal gets a scan that does not have it.
The fix plan marks an upgrade to a new major version with major version
. Such an upgrade can break your code. A TuxCare build avoids it.
Write the upgrade: securechain fix #
securechain fix (CLI 0.1.81 or later) writes the upstream fix for each open finding, runs the install command of your package manager, and checks the result. It needs a portal token and no TuxCare registry. It also needs no .securechain.yaml. On Maven and Gradle, it needs no BOM file: the BOM file is for TuxCare builds.
securechain fix --dry-run # print each change; write nothing
securechain fix
--dry-run prints each change that the write makes, and writes nothing in the project: your package manager runs on a temporary copy. It reads the findings as securechain sca does, so it sends the package list to your workspace when the portal has no scan of that list.
For each package, fix writes the first change that applies:
- The lockfile entry, when the fix fits each range that requests the package. Your package manager chooses the version, which can be above the fix. On Bun, the lock moves to the fix, not to the newest version.
- The range in the manifest, with the same operator, when that range refuses the fix.
- A new release of the parent, when a transitive range refuses the fix and a release of each parent takes it. Except on renv,
fixdoes not move a parent to a new major version: that is your choice. Where your tool has an override,fixwrites the override, and a note names the release. Where it has none,fixleaves the finding. - An override, where your tool has one, scoped so that it does not move another version of the package.
The rest of what fix does:
- It moves a package to an upstream version, never to a TuxCare build. It does not change a TuxCare build or a pin that
hardenwrote: for these, runsecurechain update. - It does not replace an override that you wrote.
- When it cannot write one change for a finding, it leaves the finding, and prints the reason for each one. The run then ends with exit code 1, and the changes that it wrote stay.
- When your package manager refuses one change, and another change of the same tool lands, the refused change becomes a left finding with the code
not_movedand the line of the tool.fixwrites and checks the other changes, and the run ends with exit code 1. When no change of the tool lands, the run stops with exit code 5 and writes nothing, so a tool that cannot run still stops the run. Two refusals are left findings also for a single change:failed to select a version
of Cargo, and a version thatgo getrefuses. A tool that solves all its changes in one call gets at most 24 solves to find the refused change. After that, the run stops with exit code 5. - When the check after the install names packages that did not land,
fixputs your files back and runs again without those changes, at most two times. Each such package becomes a left finding with the codenot_moved, and the run ends with exit code 1. Each other failure of the check ends with exit code 6, also a failure after the last try, andfixputs your files back. - On npm, pnpm, Yarn, and Bun, a package can leave the dependency tree with another change. Its finding closes,
fixkeeps the write, and its change readsremoved. - These rules need a CLI release after 0.1.84. With CLI 0.1.84 or earlier, a refused change other than these two refusals stops the run with exit code 5. A package that did not land, or that left the tree, ends the run with exit code 6. In both cases
fixputs your files back. - When the install fails,
fixputs your files back and exits with code 5. See Exit codes. - The CLI release that holds these rules also warns in four cases where the release after it gives another exit code. The run keeps exit code 0 and the write, and the warning names the new exit code: a part of the project that
scadid not read, a Gradle runtime that Gradle reports empty over declared packages, a part that the check after the install could not read, and a finding with the codeunplacedwhen it is the only open finding. - It calls no package registry: your package manager asks its own registry. On GitHub Actions, your
gitreads the tags from github.com, also with--dry-run, and sends only the name of the repository. - It sends nothing to the portal after the write. Run your tests, commit the manifest and the lockfile, and run
securechain sca. The finding closes when the portal gets a scan that does not have it.
The tools of securechain fix #
fix covers each package manager that securechain reads: npm, pnpm, Yarn classic, Yarn Berry, Bun, Deno, uv, PDM, Poetry, Pipenv, pip, pip-tools, conda, Maven, Gradle, Cargo, NuGet, Bundler, Composer, Stack, Cabal, CocoaPods, Julia, renv, CPAN, Pub, Mix, SwiftPM, Paket, Bazel rules_jvm_external, Go modules, GitHub Actions, and Conan.
CLI 0.1.81 covers the JavaScript tools, the Python tools, Maven, Gradle, and Cargo. Each other tool needs a CLI release after 0.1.81. CLI 0.1.81 leaves the findings of such a tool with the code no_fixer, names the tool and the file to edit by hand, and ends with exit code 1.
| Tools | What fix writes | The override |
|---|---|---|
| npm, pnpm, Yarn classic, Yarn Berry, Bun, Deno | The lockfile, with the tool, and the range in package.json. On Deno: the imports of deno.json. | overrides on npm and Bun, the override block that your version of pnpm reads, and resolutions on Yarn. Bun also takes resolutions when the manifest declares only that block. Deno has no override. |
| uv, PDM, Poetry, Pipenv, pip-tools | A lower bound on the fix, name>=version, and the tool chooses the version in its lockfile. On pip-tools, the lockfile is the compiled requirements.txt. | tool.uv.override-dependencies on uv and tool.pdm.resolution.overrides on PDM. The others have no override. |
| pip | pip has no lockfile: a line in the constraints file that requirements.txt reads, or in a new constraints.txt with a new -c line. | None. |
| conda | conda has no lockfile: a line of the pip: list, also for a package that a conda spec names. fix does not edit a conda spec. | The pip: list. |
| Maven | The <version>, the property, or the dependencyManagement entry that the report names. | dependencyManagement in the pom of the module. It outranks a BOM and a parent pom outside the project, and fix moves neither of them. |
| Gradle | The coordinate, the variable, or the key of the version catalog that the report names. Then --update-locks of each gradle.lockfile. | The block upstream-fix in the settings file, before the block of harden. update and unharden leave it. |
| Bazel rules_jvm_external | The version in the module file, then a repin, which writes the lock file. | A new entry in the artifacts list, for a transitive artifact. |
| Cargo, Bundler, Composer, SwiftPM, CocoaPods, Julia, CPAN | The update command of the tool for the package and its parents, for example cargo update. Else the version in the declaration, with the same operator. | None: when no release of a parent on its line takes the fix, fix leaves the finding. |
| renv | renv::install of the fix, then renv::snapshot of the package. Else the version in DESCRIPTION, with the same operator. The package whose record refuses the fix moves to its newest release, also to a new major version. | None: when such a move does not take the fix, fix leaves the finding. |
| NuGet | The Version of the PackageReference, its VersionOverride, or the PackageVersion of Directory.Packages.props. fix does not search the releases of a parent. | A direct PackageReference at the fix. Under central package management, a PackageVersion in Directory.Packages.props, and a PackageReference with no version where the PackageVersion alone moves nothing. |
| Pub, Mix, Paket | The update command of the tool for the package and its parents. Else the version in pubspec.yaml, mix.exs, or paket.dependencies, with the same operator. | dependency_overrides on Pub, override: true on Mix, and nuget <id> == <version> on Paket. |
| Stack, Cabal | The extra-dep line of stack.yaml, or the line of cabal.project.freeze. fix does not widen a range of your project. | None. |
| Go modules | go get of the module at the fix, in each module that requires it, then the vendor step of a vendored project. | None. fix does not change a replace: the reason gives the edit. |
| GitHub Actions | The uses: line of the workflow or of the composite action: a release tag at or above the fix. A tag of the major or the minor stays in that form when it stands at such a release. A full commit stays a full commit. | None. |
| Conan | conan.lock, the line of conanfile.txt or conanfile.py that refuses the fix, or the line of a parent on its major version (0.x: on its minor version). | override=True in conanfile.py, and the key overrides of conan.lock for conanfile.txt. |
fix runs your package manager, so the tool must be on the computer, also for --dry-run. securechain sca reads many lockfiles with no tool, and fix does not. So Cargo needs cargo, NuGet needs dotnet, Conan needs Conan 2, Bazel needs bazel or bazelisk, and GitHub Actions needs git. The same holds for bundle, composer, stack, cabal, pod, julia, Rscript, carton, dart, mix, swift, paket, and go. With no such tool, the run stops with exit code 5, and the message names the tool. With no npm or uv, fix leaves the finding with the code no_file.
The findings that fix leaves #
fix leaves a finding that has no single edit. It prints the reason for each one, and the file to edit by hand when there is one. --output json gives the same in fix.left, with a code. A left finding never reads as fixed. Each code gives exit code 1, with these exceptions. fixed_by_tuxcare, no_fix_planned, and live_patched do not change the exit code. unplaced keeps exit code 0 with a warning when it is the only open finding. The CLI release after the one that adds this code gives exit code 1.
Each finding of sca is a finding of fix, with one exception. A lockfile beside the tree of your tool can count the same package again, for example a gradle.lockfile. fix then keeps only the finding of the tree. It does this only when the tree covers the directory of the lockfile. A lockfile in a directory that the tree does not cover is a project of its own, also when the root holds the same package. For example, a website/package-lock.json below a root npm project gets the code no_file, and the reason names securechain fix --dir website. This rule needs CLI 0.1.84 or later. On a Gradle build of several projects, the finding of the gradle.lockfile of a subproject is a copy of the finding of the tree, on Gradle 8 and on Gradle 9: fix moves the package in that lockfile, and the run ends with exit code 0 (measured on Gradle 8.14.3 and 9.7.0). A subproject whose directory is outside the root is a project of its own, and its finding gets the code no_file.
| Code | The finding |
|---|---|
no_upstream_fix | The portal names no upstream version. |
several_fixes | The portal names several versions, and no comparison chooses one. |
no_file | The report names no file for the package: no lockfile, or a lockfile that no tool of this run read. For example, its tool is not on the computer, or the lockfile is a second project below the directory. The reason names the directory. Run securechain fix --dir <directory> where the tool is installed, or edit the file by hand. |
pinned | The file of the tool states the version with no range that fix can move. On NuGet: a packages.config entry. |
environment | The package is the tooling of a Python environment, or the Go toolchain. Upgrade the environment, or build with the fixed Go. |
range_assumed | The change rests on a range that the CLI assumed. fix writes no edit on a guess. |
manifest_range | A declaration that fix cannot move and keep its meaning: a compound range, a tag, a URL, or an alias. On Stack and Cabal, a range of your project. On Go, a fix in another major version. On GitHub Actions, a branch or a short commit. |
customer_override | An override that you wrote already matches the package. This includes a Go replace. fix does not replace your line. |
hardened | The project holds a TuxCare build of the package, or a pin that harden wrote. Run securechain update. |
override_scope | No key moves this copy and leaves another version as it is. |
no_override | The tool has no override, and no parent that the project declares moves the package: Deno. On Mix and Pub: a file with no place for a new line. |
no_parent_release | No release on the line of the parent takes the fix, and the tool has no override for it. On NuGet: the range of a parent stops below the fix. |
not_moved | The tool ran, and the old version stayed. This includes a change that your package manager refused and a package that did not land: the reason keeps the line of the tool. |
no_ref | GitHub Actions: git cannot read the repository, or the repository has no release tag at or above the fix. |
residual | The new version landed, and a copy stayed on the old version. The run keeps the write. |
fixed_by_tuxcare | The build in the project already closes the finding. fix has nothing to write. |
no_fix_planned | TuxCare stated that it will not fix the finding on the build, so no version closes it. fix has nothing to write. This code needs a CLI release after 0.1.84. |
live_patched | A live patch that is loaded on the host closes the finding. fix has nothing to write. This code needs a CLI release after 0.1.84. |
unplaced | No step of the run placed the finding: no change and no other left finding names it. A defect of securechain. Move the package by hand. This code needs a CLI release after 0.1.84. |
TuxCare builds #
TuxCare builds the open-source packages that your projects use from their source, and serves the builds from the TuxCare registry. When a vulnerability is found in a version, TuxCare adds the fix to that version and publishes a new build. TuxCare also fixes versions that the project that made them no longer maintains. A new upstream version reaches the registry only after TuxCare checks it, for example for malicious code.
The CLI moves a project to the builds in three steps: init one time for each repository, registry login one time for each computer, and harden each time you want to apply the builds.
If Renovate or Dependabot updates the dependencies of the repository, give it the rule of Renovate and Dependabot. Without the rule, the bot can replace a TuxCare build with an upstream release.
What you need #
- A subscription with TuxCare builds.
securechain auth statusshows it in the linesubscription.SecureChain no, ELS nomeans that your workspace has none. Contact TuxCare. - A registry token in the workspace. An admin saves it one time under Settings › Registry token.
- The setting Use the TuxCare registry on for the workspace or the repository. It is on by default.
- A committed lockfile, for example
package-lock.json. - A supported package manager: npm, pnpm, Yarn, Bun, pip, uv, Poetry, PDM, Pipenv, or conda. Deno: the
importsofdeno.jsononly. Maven and Gradle need a BOM file from TuxCare (--bom-file).
Use the TuxCare registry, or your own repositories #
The setting Use the TuxCare registry says whether the CLI uses the TuxCare registry for the projects of the workspace. It is on by default.
- On: the portal gives the CLI the address of the TuxCare registry and the registry token. The CLI points the package managers of the project at the TuxCare registry, and
securechain auth loginandregistry loginwrite the registry token into the user files of your package managers, for example~/.npmrc. - Off: the portal gives the CLI no registry address and no registry token.
auth loginandregistry loginthen write no registry and no token into the files of your package managers. Turn it off when your builds use only your own package repositories, for example Artifactory or Nexus. A mirror that serves the TuxCare builds is not such a case: keep the setting on (see Your own mirror).
The TuxCare registry needs a SecureChain or an ELS subscription. A workspace with neither gets no registry, whatever the setting says: the portal gives the CLI no registry address and no registry token, also for a repository with the value On. The CLI then gets the reason the TuxCare registry needs a SecureChain or an ELS subscription, and this workspace has neither
, and the repository page shows Use the TuxCare registry: Not available with the same reason. The values of the setting stay as they are. When TuxCare adds a subscription, the values apply again: with the default, every repository uses the TuxCare registry. Contact TuxCare to add a subscription.
An admin sets the value of the workspace under Settings › Use the TuxCare registry. A repository can have a value of its own: open the repository, select More, and under Use the TuxCare registry select Workspace default, On, or Off. In a clone of the repository, securechain registry use on, off, or default does the same, and prints the value that applies now. When the workspace has neither SecureChain nor ELS, it prints the reason and stops with exit code 3. The value of the repository wins in both directions: a repository with On uses the TuxCare registry in a workspace with the value off. Only an admin can change the value of the workspace. A member or an admin of a repository can change the value of that repository: a person with the role viewer on the repository, a token of one repository or of one department (a CI token), and a TuxCare staff session cannot. A token of a person needs the permission write. The other people see the values on the repository page. The Audit log records each change as settings.registry_use.
For a token of one repository, the portal uses the value of that repository. For another token, it uses the value of a repository only when the CLI names the repository by its git remote. Else it uses the value of the workspace.
After an admin turns the setting off. The files that an earlier auth login, registry login, or init wrote still point your package managers at the TuxCare registry. Remove those lines. Each line names the registry host: the registry: line of .securechain.yaml names it, and on sc.tuxcare.cloud it is artifacts.tuxcare.com. git grep -n artifacts.tuxcare.com finds the lines in a repository. A registry: line that names your own mirror is your choice: keep it. While the setting is on, a new auth login writes the lines again.
- On each computer where you ran
auth loginorregistry login: runsecurechain auth logoutwith no--tenant, thensecurechain auth loginagain.auth logoutremoves the registry lines from the user files of your package managers, for example~/.npmrc,~/.yarnrc.yml,~/.netrc,pip.conf,uv.toml, and~/.m2/settings.xml, with the securechain header line at the top of each file, and it removes the kept registry credential. It changes no other line. It also removes every sign-in of the computer. The newauth loginwrites no registry line. Ifauth logoutcannot find the registry, it changes no file and a warning names the files: run it in a repository whereinitran, or remove the lines by hand. If you also use a workspace where the setting is on, runsecurechain registry loginin a repository of that workspace after the new sign-in.auth logoutcannot find apip.confthat a relativePIP_CONFIG_FILEnames: it warns, and you remove thatindex-urlline by hand. - In a repository where
harden,update, ormigrateran: runsecurechain unharden. It removes the TuxCare builds and the registry lines ofinit. Then remove theregistry:line of.securechain.yamlwhen it names the TuxCare registry. Do not remove it beforeunharden:unhardenfinds the registry lines through it. - In a repository where only
initran: remove the registry lines ofinitby hand, for example in.npmrc,.yarnrc.yml,pnpm-workspace.yaml,pyproject.toml,requirements.inorrequirements.txt,Pipfile,environment.yml,.github/dependabot.yml, andrenovate.json. Each of these files exceptrenovate.jsonalso holds the securechain header line ofinit:# securechain wrote the TuxCare registry lines in this file. Run "securechain unharden" to remove them.Remove that line too. Also remove theregistry:line of.securechain.yamlwhen it names the TuxCare registry. Whereinitchanged a line that was there before, theunharden:block of.securechain.yamlholds the old text: put that text back, then remove the block. Commit the result.
The command reference lists the options of auth logout, auth login, and registry login.
Set up a project #
cd path/to/your/project
securechain init # one time for each repository
securechain registry login # one time for each computer
initwrites.securechain.yamland makes the TuxCare registry the package source of the project, for example in.npmrc. It writes no credential. Commit the files that it names.init --dry-runshows the changes and writes nothing.registry logingets the registry credential from your workspace and writes it into the configuration of your package managers in your home directory, for example~/.npmrc. It lists the files that it changed, andsecurechain auth logoutremoves its lines: see The files that auth login and registry login write. Each person who installs the project runs it one time. Run it again when an admin changes the registry token.- If the project already uses your own registry, such as Artifactory or Nexus,
initandregistry loginkeep it and tell you so.--replace-sourcemakes the TuxCare registry the source. To get the TuxCare builds through your mirror, see Your own mirror: Artifactory or Nexus.
Move the project to the builds: harden #
securechain harden --dry-run # show the plan and the changes; write nothing
securechain harden
hardenasks the portal which TuxCare builds your subscription has for the dependencies of the project.- It writes those builds into the manifest and the lockfile.
- It can also change
.yarnrc.yml,pnpm-workspace.yaml, orbunfig.toml, for example for the release-age excludes. Each such file gets the securechain header line as its first line.package.jsonanddeno.jsonget no header, and a warning ofhardennames each one.unhardenremoves the header with the lines. - It runs the install command of your package manager.
- It checks that each planned build is installed. If the check fails, it puts your files back and exits with code 6.
- It sends the new package list to your workspace.
Then run your tests, and commit the manifest, the lockfile, and each other file that harden changed.
hardenkeeps the version of each package.1.3.1moves to the newest build of1.3.1, for example1.3.1-tuxcare.2.--allow-minor-upgradeslets a package move to a later version with the same major and minor numbers, when only that version has a build that closes the findings.1.3.1can move to1.3.2-tuxcare.1, never to1.4.0.--includeand--excludelimit the run to the packages that you name.
Keep the builds current: update #
securechain update --check-only # exit code 1 when a newer build exists; write nothing
securechain update
TuxCare publishes a new build when it fixes a new vulnerability in a version. update moves each TuxCare build of the project to the newest build of the same version, for example from 1.3.1-tuxcare.2 to 1.3.1-tuxcare.3. It installs and checks the result, as harden does. Run it on a schedule, or when the portal shows a finding that a newer build fixes.
The gate: check #
securechain check reads the installed dependencies and changes nothing. It exits with code 1 when the project is not in the state that harden made:
- A package has a finding, your subscription has a TuxCare build that fixes it, and the project does not use the build.
- A newer TuxCare build exists for a build that the project uses.
- The lockfile points at the public registry for a package that
hardenmoved to a TuxCare build.
--explain prints the evidence and the fix for each problem.
TuxCare builds in CI #
A job that installs TuxCare builds needs the registry credential before the install step. The job does three things in this order: get the credential, install, run check.
GitHub Actions:
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: TuxCare/securechain-action@v0
with:
command: registry
portal-token: ${{ secrets.SECURECHAIN_PORTAL_TOKEN }}
- run: npm ci
- uses: TuxCare/securechain-action@v0
if: always()
with:
portal-token: ${{ secrets.SECURECHAIN_PORTAL_TOKEN }}
registry-login: none
- The first use of the action gets the credential for the install step.
- The second use runs
securechain checkand sends its result to GitHub code scanning.if: always()runs it after a failed install too: its last step removes the credential of the job.
GitLab CI:
securechain:
image: node:22 # any image that has curl and your package manager
script:
- curl -fsSL https://securechain.tuxcare.com/get/securechain | sh
- out="$(securechain registry env --job-dir "/tmp/securechain-job-$CI_JOB_ID" --format shell)"; eval "$out"
- env -u SECURECHAIN_PORTAL_TOKEN npm ci
- securechain check --output junit > report.xml
after_script:
- rm -rf "/tmp/securechain-job-$CI_JOB_ID"
artifacts:
reports:
junit: report.xml
registry env --job-dirwrites the credential into a directory for this job, and prints the variables that point npm, pnpm, Yarn 1, and the Python tools at it. It changes no file in the home directory of the runner.env -u SECURECHAIN_PORTAL_TOKENruns the install without the access token, so the install scripts of your dependencies cannot read it.- On gitlab.com, the CI/CD component does these steps for you, also for Yarn 2 and later and for Bun. When the
installcommand fails, the job exits 5, not with the code of the install (from the CLI release after version 0.1.72):
include:
- component: $CI_SERVER_FQDN/tuxcare/securechain-ci/check@0
inputs:
stage: test
install: npm ci
Your own mirror: Artifactory or Nexus #
Many companies install packages only through their own repository manager, for example JFrog Artifactory or Sonatype Nexus Repository: a mirror. The mirror can serve the TuxCare builds. Add a remote repository of the TuxCare registry to the mirror (Nexus calls it a proxy repository), and put it first in the group that your package managers use (Artifactory calls the group a virtual repository). The mirror keeps the registry token. Your package managers keep the address of the mirror, and they need no credential of TuxCare.
The address of the TuxCare registry is artifacts.tuxcare.com. Your mirror connects to it, so allow the mirror to reach that host on port 443.
Keep the setting Use the TuxCare registry on. With the setting on, securechain registry env gives you the registry token, and securechain registry check reads the hash of each TuxCare build from the TuxCare registry. TuxCare does not publish these hashes in its feed yet. With the setting off, the check cannot compare the build of the mirror with the TuxCare build, and it reports not checked. The same applies to a portal that you install yourself and that names the mirror as its registry: the check does not compare the mirror with itself, so it reports not checked until TuxCare publishes the hashes.
The three rules for each remote repository #
- The registry token is the user name. The remote repository signs in to the TuxCare registry with the registry token of your workspace as the user name. The TuxCare registry does not read the password: put any value, for example
x, when the mirror asks for one. To get the token, runsecurechain registry env --format dotenvin a repository of the workspace: the lineTUXCARE_TOKEN=holds it. A member or an admin can read the token; a viewer cannot. The portal records each read in the Audit log. Keep the token only in the mirror, and do not paste it into a ticket or a log. When an admin replaces the registry token, put the new token into the remote repository. - Put the remote repository of TuxCare first, above the proxy of the public registry. A group serves the build of its first member that has the version. A TuxCare build often has the same version as the public package, with other bytes: for example,
braces@3.0.2of TuxCare is 15,590 bytes, and the public build is 15,637 bytes. When the public proxy is first, each install gets the public build and ends with exit code 0. Nothing tells you. TuxCare measured this on Nexus Repository 3.98.1 with npm, Maven, and pip: the order decided the result in each test, and the state of the caches changed nothing. Artifactory is different: on Artifactory OSS 7.161.26 with Maven, a copy that a remote repository has cached is served before the order of the virtual repository applies. So on Artifactory, after you move the remote repository of TuxCare first, delete the cached copies of the public remote repository for the packages that TuxCare rebuilds.securechain registry checkfinds this mistake: see Check the mirror. - Set the cache time of the remote repository of TuxCare to minutes, and turn its cache of missing items off. With the default settings, the mirror shows a new TuxCare build up to one day after TuxCare publishes it. Until then, an install of a version that
hardenwrote stops withno matching version
. On Nexus, the defaults are 1440 minutes forMaximum metadata age
and 1440 minutes for the not found cache. SetMaximum metadata age
to 30 minutes, and clearNot found cache enabled
. On Artifactory, setMetadata Retrieval Cache Period
to 1800 seconds andMissed Retrieval Cache Period
to 0. TuxCare did not measure these two settings on Artifactory. Change only the remote repository of TuxCare. Keep the proxy of the public registry as your own policy says: a quarantine of new public packages belongs there.
npm, pnpm, Yarn, and Bun #
On Nexus:
- Create a repository of the type
npm (proxy)
.Remote storage
:https://artifacts.tuxcare.com/npm/. UnderHTTP
, set the authentication typeUsername
, the user name to the registry token, and the password tox. Set the cache times of the third rule. - Create a repository of the type
npm (group)
, or open the group that you use. Put the new proxy first in the list of members, above the proxy ofregistry.npmjs.org. - Point the project at the group. In
.npmrc:registry=https://nexus.example.com/repository/npm-group/.
On Artifactory:
- Create a remote repository of the type npm. URL:
https://artifacts.tuxcare.com/npm/. User name: the registry token. Password:x. Set the cache times of the third rule. - Create a virtual repository of the type npm, or open the one that you use. Put the new remote repository above the remote repository of
registry.npmjs.orgin the list of repositories. - Point the project at the virtual repository. In
.npmrc:registry=https://artifactory.example.com/artifactory/api/npm/npm-virtual/.
Use the path /npm/ of the TuxCare registry, not /npm-upstream/: only /npm/ has the TuxCare builds (see the two paths of the registry). Do not add /npm to the address of the mirror: the address of the group is the registry. TuxCare did not measure an npm virtual repository of Artifactory: the free edition of Artifactory serves no npm.
pip, uv, Poetry, PDM, and Pipenv #
- Nexus: create a repository of the type
pypi (proxy)
, with theRemote storage
https://artifacts.tuxcare.com/pypi/. Nexus then askshttps://artifacts.tuxcare.com/pypi/simple/project/for each project. Artifactory: create a remote repository of the type PyPI with the same URL. Set the user name, the password, and the cache times as for npm. - Put it first in the group (Nexus:
pypi (group)
; Artifactory: a virtual repository of the type PyPI), above the proxy ofpypi.org. - Point the tools at the simple index of the group, for example
index-url = https://nexus.example.com/repository/pypi-group/simple/inpip.conf. The address ends in/simple/.
Maven and Gradle #
- Nexus: create a repository of the type
maven2 (proxy)
, with theRemote storage
https://artifacts.tuxcare.com/maven/. Artifactory: create a remote repository of the type Maven with the same URL. Set the user name, the password, and the cache times as for npm. - Put it first in the group (Nexus:
maven2 (group)
; Artifactory: a virtual repository of the type Maven), above the proxy of Maven Central. - Point Maven at the group with a
<mirror>in~/.m2/settings.xml, and Gradle with amaven { url = uri("…") }entry inrepositories. Maven and Gradle also need the BOM file of TuxCare (see What you need).
On Artifactory, delete the cached copies of the public remote repository for the packages that TuxCare rebuilds after you put the remote repository of TuxCare first: a cached copy wins over the order (the second rule). Nexus follows the order alone.
TuxCare measured the order of the Python and Maven groups on Nexus 3.98.1 and Artifactory OSS 7.161.26 with a test server in the place of the TuxCare registry. The free edition of Artifactory serves no PyPI and no npm, so the Python rules rest on Nexus alone. securechain registry check checks a Python index and a Maven or Gradle mirror (CLI 0.1.86 or later): see Check the mirror.
Check the mirror: registry check #
cd path/to/your/project
securechain registry check
registry check asks the registry that the files of the project name whether it serves the TuxCare builds. It changes no file. It takes one package of the project that has a TuxCare build at the same version as the public package, and asks the registry for the hash of that version. Then it compares the hash with the hash of the TuxCare build. It asks with your own tools and your own credential for the mirror: npm with the .npmrc for npm, pnpm, Yarn 1, and Bun, Maven with ~/.m2/settings.xml, Gradle with the credentials of the repository, and pip for pip, uv, Poetry, and PDM, with the configuration of pip, ~/.netrc, or the keyring. pip does not read a credential that only uv, Poetry, or PDM holds: when the mirror then refuses pip in such a project, the check reports not checked. The mirror holds the token of TuxCare. Run it after you set up the mirror, and after each change of the group. It prints one block for each package manager:
npm: registry https://nexus.example.com/repository/npm-group/
✖ it serves upstream's build of braces@3.0.2, not TuxCare's (sha512-b8um+L1RzM3W…, TuxCare's is sha512-xdS7zoiSHyo3…)
cause: the group lists the TuxCare remote repository below the public proxy, so the public build wins
fix: in Nexus or Artifactory, move the remote repository of artifacts.tuxcare.com above the public proxy in the group or the virtual repository, then run securechain registry check again
| The line | What it means | What to do | Exit code |
|---|---|---|---|
✓ it serves the TuxCare build | The mirror serves the TuxCare build. | Nothing. | 0 |
✖ it serves upstream's build, and the cause names the order | The group has the TuxCare builds, but the proxy of the public registry is above the remote repository of TuxCare. On Maven and Gradle, the cause also names a cached copy of the public remote repository on Artifactory. | Move the remote repository of TuxCare above the public proxy (the second rule). On Artifactory, also delete the cached copies of the public remote repository. | 5 |
✖ it serves upstream's build, and the cause names no remote repository | The group has no TuxCare version: it has no remote repository of TuxCare, or the TuxCare registry refuses the token of that remote repository. A client of the mirror cannot see which of the two it is. On a subscription with no ELS, the cause can also be the order: TuxCare did not measure whether the mirror then lists TuxCare versions, and the cause on the line names the order too. | Add the remote repository of the format of the tool at the top of the group (https://artifacts.tuxcare.com/npm/, https://artifacts.tuxcare.com/maven/, or https://artifacts.tuxcare.com/pypi/), with the registry token as the user name, or put the new token into it (the first rule). On a subscription with no ELS, also check the order (the second rule). | 5 |
✖, and the cause says that the registry has no copy of the version | The mirror answers that it does not have the version: no remote repository of TuxCare and no public proxy that holds it, or a refused token. | The same as in the row above. | 5 |
✖ it did not accept the credential of this machine | The mirror refused your own credential for the mirror. This is not the token of TuxCare. | Sign in to the mirror: npm: npm login --registry address of the mirror; Maven: a <server> with the id of the mirror in ~/.m2/settings.xml; Gradle: a credentials block of the repository; pip: ~/.netrc, the keyring, or the index URL. Or ask the administrator of the mirror for read access. | 5 |
✖, and the cause says that the files name the public registry | The project installs from the public registry (registry.npmjs.org, registry.yarnpkg.com, Maven Central, the Gradle Plugin Portal, or pypi.org), not from a mirror or the TuxCare registry. | Point the files of the project at the mirror, or run securechain init. | 5 |
✖ could not ask | The registry did not answer in time, two times, or it gave another error. | Run the check again when the registry answers. | 5 |
· not checked | The check could not judge this package manager: the project has no package with such a TuxCare build, the tree did not resolve, the package manager is Yarn 2 or later, Pipenv, or a tool of another ecosystem, the Python files name an index that the check does not judge (for example PyPI beside the mirror, more than one index, or a find-links), the mirror refused pip in a uv, Poetry, or PDM project, the index serves only the source archive of the version and no wheel, a Gradle build lists no Maven repository, or the check has no TuxCare build to compare with (the setting off, or a portal of your own that names the mirror). | Read the reason on the line. | 2 |
The exit code is the highest code of the blocks, and 5 is above 2. It is 0 only when the mirror served the TuxCare build to each package manager. With no portal token, or with no registry token in the workspace, the command stops with exit code 3. When the portal does not answer, it stops with exit code 4. The command needs CLI 0.1.85 or later. The check of Maven, Gradle, pip, uv, Poetry, and PDM needs CLI 0.1.86 or later.
Rules for a project with TuxCare builds #
- Commit the lockfile, and install from it. In CI, use the install command that follows the lockfile, for example
npm ci. - Move TuxCare builds with
securechain updateonly. Give your update bot the rule of Renovate and Dependabot. A version that ends in-tuxcare.Nis not in the public registry.npm update,npm audit fix, and other tools that select versions by themselves do not know these builds. They stop withETARGET, or they put the public version back. - Add a new dependency as usual, for example with
npm install --save-exact name. Then runsecurechain hardenagain, so that the new packages get their builds. - On Bun, record each edit of the override block with one install on the builds path.
bun.lockkeeps a copy of the block that Bun reads:overridesinpackage.json, elseresolutions. When you add a key of your own, or change a key thathardenwrote, the nextbun installresolves every override again, not only the changed key. The registry of the project is/npm-upstream/, which has no TuxCare builds (see the two paths of the registry). So the install stops with exit code 1 and does not changebun.lock. Bun shows a line such asNo version matching "2.1.3-tuxcare.2" found for specifier "ms"
, andms@^2.1.3 failed to resolve
for each build.bun install --frozen-lockfilestops in the same way. TuxCare measured this on Bun 1.3.14 and 1.4.4. A change of a nested value, for example"debug": {"ms": "2.1.2"}, stops the install on Bun 1.4.4 only. To fix it, run this command once in the directory ofbun.lock, and then commitbun.lock:BUN_CONFIG_REGISTRY=https://registry host/npm/ bun install. The registry host is in theregistry:line of.securechain.yaml. Keep the slash at the end. After that, each install from the lockfile passes. Whenregistry:names your own mirror, which serves the builds at the address of the project, run a plainbun installonce and commitbun.lock.securechain checkreports this state asbun-lockfile-overrides, at high severity, and its fix gives the command. For some forms of key, for example a path such asdebug>ms, the two Bun versions record the block in different ways:checkthen does not judge the block, and a warning names the key. npm and Yarn 1 keep the other builds after such an edit.
npm: the two paths of the registry #
For npm, the TuxCare registry has two paths on one host:
/npm-upstream/serves the packages of the public npm registry, byte for byte.initandregistry loginwrite it as the registry of the project and of your computer./npm/serves the TuxCare builds.hardenandupdatewrite it into the lockfile lines of the builds, with the hash of each build.
So a new TuxCare build never changes what an existing lockfile installs, and the install does not fail with EINTEGRITY. pnpm and Yarn 2 or later also use /npm-upstream/ as the source: harden writes /npm/ into the lockfile entry of each build. A project that a CLI before 0.1.21 set up names /npm/ as the source of pnpm and Yarn 2 or later. The next securechain init changes it, and keeps the lockfile.
A tool that selects a version for a TuxCare build against /npm-upstream/ stops, because that path has no TuxCare builds: npm update and an install with no lockfile stop with ETARGET. On Bun, an edit of the override block has the same effect: see the rule for Bun.
Renovate and Dependabot #
An update bot opens pull requests that move your dependencies to newer versions. A TuxCare build has the version of the upstream release with -tuxcare.N added, for example 3.3.1-tuxcare.1. Without a rule, a bot can replace the build with an upstream release that does not have the TuxCare fix. The finding then comes back.
What the bots propose with no rule #
We gave each bot a project with TuxCare builds and no rule. The table shows the update that each bot proposed.
| Bot | npm: jquery 3.3.1-tuxcare.1 | Maven: commons-lang3 3.12.0-tuxcare.1 |
|---|---|---|
| Renovate | 3.7.1 and 4.0.0: upstream releases | 3.21.0: an upstream release |
| Dependabot | 4.0.0: an upstream release | 3.14.0-tuxcare.1: a TuxCare build of a later version |
Renovate reads -tuxcare.1 as a prerelease (npm) or a qualifier (Maven), and moves to the newest release. Dependabot for Maven stays on TuxCare builds, but it moves to a later version. Dependabot for npm reads the versions from the registry of the project, which has no TuxCare builds, and moves to the newest release.
Renovate #
Add the rule of your package manager to the Renovate configuration of the repository, for example renovate.json. For a repository with npm and Maven projects, put both rules into one file. Renovate updates the other dependencies as before.
From CLI 0.1.73, securechain init writes the rule for npm and the rule for Maven into renovate.json, and unharden removes them. init and unharden find each rule by its description, so keep the description when you change a rule. A second init does not add a rule again. If your project keeps its Renovate configuration in another file, for example .renovaterc.json or the renovate key of package.json, init does not write renovate.json: Renovate does not run when it finds two configuration files. init then prints the rule, and you add it to your file. With a CLI before 0.1.73, see the steps below for each package manager.
npm (also pnpm, Yarn, and Bun): Renovate opens no pull request for a package on a TuxCare build. Move these packages with securechain update, for example in a scheduled CI job.
{
"packageRules": [
{
"description": "TuxCare hardened builds: stay in the -tuxcare series, never jump to upstream",
"matchDatasources": ["npm"],
"matchCurrentValue": "/-tuxcare\\./",
"enabled": false
}
]
}
Renovate can find a newer build, but it cannot update the lockfile for it. To update the lockfile, Renovate runs npm install with the registry of the project, /npm-upstream/ (see npm: the two paths of the registry). That path has no TuxCare builds, so npm stops with ETARGET. securechain update writes the new build and its lockfile line together.
The npm rule of an init before 0.1.73. An init before 0.1.73 wrote a rule with the same description and the keys ignoreUnstable, respectLatest, allowedVersions, and registryUrls in place of enabled. With that rule, Renovate proposes a newer TuxCare build, also of a later version, for example 3.4.1-tuxcare.2 for 3.3.1-tuxcare.1, and the lockfile update of the pull request fails. Run securechain init of CLI 0.1.73 or later: it replaces that rule with the rule above, and keeps the rest of the file. If you changed that rule, init keeps your rule, and prints the rule above. If you changed that rule, or if you keep a CLI before 0.1.73, replace those four keys with the line "enabled": false yourself, and keep the description.
Maven: a Maven project has no lockfile, so Renovate can move the builds. With this rule, Renovate proposes only a newer TuxCare build of the same version: 3.12.0-tuxcare.2 for 3.12.0-tuxcare.1. It proposes no upstream release and no build of another version. securechain update moves the builds in the same way. init of CLI 0.1.73 or later writes this rule. With a CLI before 0.1.73, add the rule yourself, and remove it yourself when you stop using TuxCare builds.
{
"packageRules": [
{
"description": "TuxCare builds for Maven: propose only a newer build of the same version",
"matchDatasources": ["maven"],
"matchCurrentValue": "/-tuxcare\\./",
"versioning": "regex:^(?<compatibility>.+)-tuxcare\\.(?<patch>\\d+)$",
"registryUrls": ["https://artifacts.tuxcare.com/maven/"]
}
]
}
matchCurrentValue: the rule applies only to a dependency on a TuxCare build.versioning: Renovate compares only the versions with the same base version, the part before-tuxcare.N, and selects the highest N. An upstream release does not match the expression, so Renovate does not propose it. The expression accepts each form of base version, for example1.15,32.1.3-jre, andr09. The base is the whole part before-tuxcare.N, and N is the number that Renovate compares. If you write an expression that accepts only1.2.3, Renovate skips the other builds and shows no message.registryUrls: Renovate reads the builds from the TuxCare registry.initwrites the Maven path of the registry of the project.
The registry token for Maven. Renovate reads the Maven builds with the registry token, from a hostRules entry. init does not write this entry: when the configuration names a secret that Renovate does not have, Renovate stops for the whole repository. init prints the entry in a note. Do these steps in this order:
- Save the token as the Renovate secret
TUXCARE_TOKEN. Use the registry token that TuxCare gave you, the token that an admin saves under Settings › Registry token. For the Mend Renovate app, save it in the settings of the organization or the repository in the Mend developer portal. For a Renovate that you run yourself, save it insecretsof its configuration. Do not write the token into the file. - Add the
hostRulesentry to the file that holds the rule. The Maven registry takes the token as the user name and as the password.
{
"hostRules": [
{
"hostType": "maven",
"matchHost": "artifacts.tuxcare.com",
"password": "{{ secrets.TUXCARE_TOKEN }}",
"username": "{{ secrets.TUXCARE_TOKEN }}"
}
]
}
Without the entry, Renovate cannot read the TuxCare builds: it proposes no update for them, and it updates the other Maven dependencies as before.
Dependabot #
Dependabot has no setting that keeps a package on the builds of one version. So keep Dependabot away from the packages on TuxCare builds with an ignore entry for each package, and move these packages with securechain update, for example in a scheduled CI job. Dependabot updates the other dependencies as before. In our test, Dependabot opened no pull request for a package in the ignore list, for npm and for Maven.
npm: securechain init writes the TuxCare registry into .github/dependabot.yml, and harden adds an ignore entry for each package that it moves to a TuxCare build. Add the registry token as the Dependabot secret TUXCARE_TOKEN in the settings of the repository. For a project on npm, the file is then:
version: 2
registries:
tuxcare:
type: npm-registry
url: https://artifacts.tuxcare.com/npm
token: ${{ secrets.TUXCARE_TOKEN }}
tuxcare-upstream:
type: npm-registry
url: https://artifacts.tuxcare.com/npm-upstream
token: ${{ secrets.TUXCARE_TOKEN }}
updates:
- package-ecosystem: npm
directory: /
registries: [tuxcare, tuxcare-upstream]
schedule: {interval: weekly}
# One entry per package `securechain harden` pinned.
ignore:
- dependency-name: jquery
- dependency-name: lodash
Dependabot sends a token only to the URL of its registry entry, so each of the two npm paths has an entry. This is also true for pnpm and Yarn 2 or later. A project that a CLI before 0.1.21 set up for these tools has only the entry tuxcare: the next securechain init adds the entry tuxcare-upstream.
Maven: from CLI 0.1.74, harden adds an ignore entry for each package on a TuxCare build to each package-ecosystem: maven block of .github/dependabot.yml. Each entry has the comment # securechain harden. harden keeps the entries that you wrote, and writes no registry, no new block, and no new file: without a maven block, Dependabot does not update the Maven project. init writes nothing for Maven into this file. For a project on Maven, the file is then:
version: 2
updates:
- package-ecosystem: maven
directory: /
schedule: {interval: weekly}
ignore:
- dependency-name: org.springframework:spring-core
versions: ["7.x"]
- dependency-name: org.apache.commons:commons-lang3 # securechain harden
Here the entry for spring-core is your entry, and harden wrote the entry for commons-lang3. harden also puts a comment block at the top of the file. unharden removes only the entries with the comment # securechain harden. securechain check reports each package on a TuxCare build that has no entry in a maven block (maven-dependabot-ignore). An entry with versions or update-types stops only some updates, so harden adds its own entry beside it.
The entries keep the builds also when Dependabot reads Maven Central: the file needs no TuxCare registry. If your file has a TuxCare registry with replaces-base: true, harden and unharden keep it.
With a CLI before 0.1.74, harden writes nothing for Maven. Add an ignore entry for each package on a TuxCare build yourself, and add an entry each time that harden moves another package to a TuxCare build.
Dependabot security updates may not follow the ignore list. Do not merge a pull request that replaces a -tuxcare version with an upstream release.
Remove TuxCare builds: unharden #
securechain unharden --dry-run # show the changes; write nothing
securechain unharden
unharden undoes init and harden. Use it when you stop the subscription, or to go back to the public packages.
- It puts back each line that
init,harden, andupdatechanged. These commands keep the original text underunharden:in.securechain.yaml. For a line with no record,unhardenwrites the base version of the build:4.4.3for4.4.3-tuxcare.5. - It removes the registry lines, the overrides, and the bot rules that the CLI added, with the securechain header line of each file, and the
unharden:block of.securechain.yaml. It keeps the rest of.securechain.yaml, also theregistry:line. - It installs again from the public registry and checks that no TuxCare build is left. If the check fails, it puts your files back and exits with code 6.
- It needs no token, so it works after the subscription ends.
Then run your tests, and commit the changed files.
Next: Reference