Skip to main content

Private registries for dependency resolution

A source scan runs cdxgen against your cloned repository, and cdxgen shells into mvn, npm, or pip to resolve the project's dependency tree - the same way a build would. If any of those dependencies live in a private registry (an internal Nexus/Artifactory mirror, a scoped npm registry, a private PyPI index), the worker needs a credential for it, or resolution fails partway through and the scan's SBOM is missing whatever it could not reach.

This is a different feature from Private registries in the Scans page. That one (ER3) authenticates Trivy pulling a container image for a container scan. This page is about authenticating cdxgen resolving a project's dependencies for a source scan. The two credentials are stored differently and used by different tools.

The convention

Put whichever of these four files you need under one directory:

FileEcosystem
settings.xmlMaven
.npmrcnpm
pip.confpip
.netrcGeneric HTTP/Git basic-auth - for example a private Go-module VCS host cdxgen's Go analyzer shells git/go list against

You do not need all four - cdxgen tolerates the others being absent. The worker mounts this directory read-only at the fixed path /etc/trusca/registry. The path is fixed rather than configurable so it cannot become an arbitrary-file-read: nothing about a project or a scan request chooses what gets mounted there, only the operator's deployment configuration does.

Example file contents

settings.xml (Maven)

Maven decides which <server> credential to send for a given repository lookup by matching the repository's <id>, not its <url>. A pom.xml from the project being scanned can declare its own <repository> with whatever <id> and <url> it likes - if that <id> happens to match a <server> in your settings.xml, Maven cannot tell the declared repository apart from the real one and sends your credential to it. Do not rely on a bare <server> for this reason. Pin every lookup to one trusted URL with a <mirror mirrorOf="*"> instead, so a project's own <repository> declarations are ignored and nothing is left for an <id> to redirect:

<settings>
<mirrors>
<mirror>
<id>internal-mirror</id>
<mirrorOf>*</mirrorOf>
<url>https://nexus.internal.example.com/repository/maven-public/</url>
</mirror>
</mirrors>
<servers>
<server>
<id>internal-mirror</id>
<username>svc</username>
<password>S3cret</password>
</server>
</servers>
</settings>

See "Known limits" below for what happens if you skip the mirror and keep a bare <server>.

.npmrc (npm)

Scope the credential to the exact registry host, using the //host/path/:_authToken= line form. An unscoped _auth or _authToken line applies to every registry npm resolves against for the install - including one the scanned project's own package.json names in a publishConfig.registry field - so never use the unscoped form for a credential you mount into the worker:

@myorg:registry=https://registry.internal.example.com/
//registry.internal.example.com/:_authToken=${NPM_TOKEN}

Putting the files in

With Docker Compose

Set REGISTRY_CONFIG_HOST_PATH in .env (default ./secrets/registry) and place your files there:

mkdir -p ./secrets/registry
cp /path/to/settings.xml ./secrets/registry/settings.xml
cp /path/to/.npmrc ./secrets/registry/.npmrc

docker-compose.yml already mounts this directory read-only on the scan-pipeline worker (worker-scan in production, celery-worker in dev) - an absent or empty directory is harmless, the same as this repository's cosign key mount.

.netrc is the one file with no pointer variable (see below), so it needs a second, explicit mount to land at the right place. docker-compose.yml and docker-compose.dev.yml both carry this line commented out next to the directory mount - uncomment it only after you have created the file:

- ${REGISTRY_CONFIG_HOST_PATH:-./secrets/registry}/.netrc:/home/trustedoss/.netrc:ro

Uncommenting it while the host file does not exist is not a no-op: Docker creates the missing bind-mount source as an empty directory, which then shadows the path for anything expecting a file there. Create the file first.

With Helm

Create the Secret yourself, then let the chart mount it on the scan worker alone:

kubectl create secret generic trustedoss-registry-config \
--from-file=settings.xml=./settings.xml \
--from-file=.npmrc=./.npmrc
worker:
scan:
extraVolumes:
- name: registry-config
secret:
secretName: trustedoss-registry-config
extraVolumeMounts:
- name: registry-config
mountPath: /etc/trusca/registry
readOnly: true
extraEnv:
MVN_ARGS: "--settings /etc/trusca/registry/settings.xml"
NPM_CONFIG_USERCONFIG: /etc/trusca/registry/.npmrc
PIP_CONFIG_FILE: /etc/trusca/registry/pip.conf

worker.scan.* is scoped to the scan-pipeline worker alone - unlike the chart-wide env.extraVolumes/env.extraEnv used for a private certificate authority, which land on backend, beat and both workers. A registry credential a source scan needs has no reason to be readable from a pod that never runs cdxgen.

For .netrc, mount the same Secret a second time with an explicit subPath so it lands at $HOME/.netrc (this image's HOME is /home/trustedoss) instead of under /etc/trusca/registry:

extraVolumeMounts:
- name: registry-config
mountPath: /home/trustedoss/.netrc
subPath: .netrc
readOnly: true

Pointing the tools at it

Mounting the directory changes nothing by itself - nothing reads /etc/trusca/registry on its own. settings.xml, .npmrc, and pip.conf each need a matching variable that tells the relevant tool where to look:

VariableRead byEffect
MVN_ARGScdxgen's own Maven invocationExtra arguments appended to the mvn command line. Set it to --settings /etc/trusca/registry/settings.xml.
NPM_CONFIG_USERCONFIGnpm (standard npm environment variable)Alternate .npmrc path.
PIP_CONFIG_FILEpip (standard pip environment variable)Alternate pip.conf path.

Add whichever you need to .env:

MVN_ARGS=--settings /etc/trusca/registry/settings.xml
NPM_CONFIG_USERCONFIG=/etc/trusca/registry/.npmrc
PIP_CONFIG_FILE=/etc/trusca/registry/pip.conf

There is no MAVEN_SETTINGS environment variable, and setting one does nothing. Maven itself only ever reads ~/.m2/settings.xml or a -s / --settings command-line flag - there is no environment-variable form. MVN_ARGS is cdxgen's own mechanism for passing extra arguments to the mvn invocations it runs on your behalf, and --settings <path> is exactly the flag it needs. (cdxgen prints this same guidance itself, in its own console output, when it finds a settings.xml next to a pom.xml it is scanning.)

NPM_CONFIG_USERCONFIG and PIP_CONFIG_FILE are not cdxgen features - they are npm's and pip's own documented environment variables for an alternate config-file path. cdxgen does nothing special for them beyond inheriting the worker's environment when it shells out to npm install / pip install, the same way any subprocess would.

.netrc has no pointer variable of its own. Every tool that honours it - git, go, and curl-based fetchers - reads $HOME/.netrc unconditionally, which is why it needs the second, explicit mount described above rather than an environment variable.

Known limits

  • A bare <server> in settings.xml, without the <mirror mirrorOf="*"> from the example above, is matched by <id>, not by <url>. Maven looks at the repository's <id> alone to decide which <server> credential to send. A malicious pom.xml in the scanned project can declare <repository><id>internal-nexus</id><url>https://attacker.example.com/</url></repository>; if your settings.xml has a <server> whose id happens to be internal-nexus (a plausible, commonly-chosen name), Maven sends that credential to the attacker's URL instead of your real repository. The <mirror mirrorOf="*"> form pins every lookup to one URL regardless of what the project declares - use it, not a bare <server>.
  • One shared credential set per worker, not per project or team. Unlike the container-image registry credentials (ER3), which are scoped per organisation and matched against the image being pulled, everything under /etc/trusca/registry is visible to every source scan the scan-pipeline worker runs. If different teams need different private-registry credentials, they currently share one worker-wide set; per-project scoping is not implemented.
  • Read-only, and never derived from scan input. The mount path is fixed at deployment time by the operator. No project setting, API field, or scan-trigger parameter can change what gets mounted or read.
  • Air-gapped installs still need this page. An offline bundle of the worker images and the Trivy vulnerability database (tracked separately) is a different concern from authenticating to an internal mirror that is reachable but requires a credential - you may need both, or only this one, depending on your network.