OWASP Dependency Check

OWASP Dependency Check Dockerfile (current upstream) — a multi-stage build: jlink-trimmed JDK and Go toolchain layered onto a .NET runtime base, running non-root.

← Examples

Source: GitHub

7 issues foundCritical: 1High: 1Medium: 2Low: 3Recommendations: 7
DockerfileLines with findings are highlighted
SHA-256:
1FROM golang:1.26.5-alpine AS goCritical
2FROM azul/zulu-openjdk-alpine:25 AS jlinkLow
3RUN "$JAVA_HOME/bin/jlink" --compress=zip-6 --module-path /opt/java/openjdk/jmods --add-modules java.base,java.compiler,java.datatransfer,jdk.crypto.ec,java.desktop,java.instrument,java.logging,java.management,java.naming,java.rmi,java.scripting,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.unsupported,jdk.net --output /jlinked
4FROM mcr.microsoft.com/dotnet/runtime:8.0-alpineLow
5ARG VERSION
6ARG POSTGRES_DRIVER_VERSION
7ARG MYSQL_DRIVER_VERSION
8ARG MAVEN_REPOSITORY_URL="https://repo1.maven.org/maven2"
9ARG UID=1000
10ARG GID=1000
11ENV user=dependencycheck
12ENV JAVA_HOME=/opt/jdk
13ENV JAVA_OPTS="-Danalyzer.assembly.dotnet.path=/usr/bin/dotnet -Danalyzer.bundle.audit.path=/usr/bin/bundle-audit -Danalyzer.golang.path=/usr/local/go/bin/go"
14ENV ODC_NAME=dependency-check-docker
15ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=false
16COPY --from=jlink /jlinked /opt/jdk/
17COPY --from=go /usr/local/go/ /usr/local/go/
18ADD cli/target/dependency-check-${VERSION}-release.zip /
19RUN apk upgrade --no-cache && apk add --no-cache --virtual .build-deps curl && apk add --no-cache git ruby npm && gem install --no-document bundler-audit && npm install --global --ignore-scripts corepack && unzip dependency-check-${VERSION}-release.zip -d /usr/share/ && rm dependency-check-${VERSION}-release.zip && cd /usr/share/dependency-check/plugins && curl -fSLO "${MAVEN_REPOSITORY_URL}/org/postgresql/postgresql/${POSTGRES_DRIVER_VERSION}/postgresql-${POSTGRES_DRIVER_VERSION}.jar" && curl -fSLO "${MAVEN_REPOSITORY_URL}/com/mysql/mysql-connector-j/${MYSQL_DRIVER_VERSION}/mysql-connector-j-${MYSQL_DRIVER_VERSION}.jar" && addgroup -S -g ${GID} ${user} && adduser -S -D -u ${UID} -G ${user} ${user} && mkdir /usr/share/dependency-check/data && chown -R ${user}:0 /usr/share/dependency-check && chmod -R g=u /usr/share/dependency-check && mkdir /report && chown -R ${user}:0 /report && chmod -R g=u /report && apk del .build-deps && rm -rf /tmp/* /root/.cache /root/.npmHigh
20RUN find / -path /proc -prune -perm +6000 -type f -exec chmod a-s {} \;
21USER ${UID}
22RUN bundle audit update && corepack prepare pnpm@latest yarn@latest --activate && printf "enableTelemetry: false\nenableScripts: false\n" >> ${HOME}/.yarnrc.yml && rm -rf /tmp/*
23VOLUME ["/src", "/report"]
24WORKDIR /src
25CMD ["--help"]
26ENTRYPOINT ["/usr/share/dependency-check/bin/dependency-check.sh"]
27FROM instruction
Findings
7 issues found
FROM golang:1.26.5-alpine AS go
Critical CVEs in base image
The base image has one or more critical CVEs according to Docker Scout. Critical vulnerabilities have a high likelihood of being exploitable and can lead to full system compromise. Update to a patched image version or switch to a more secure base image. Note: CVE data is sourced from Docker Scout at the time of the last data rebuild — coverage is not guaranteed to be complete, and a clean result does not confirm the image is free of CVEs.
CriticalBase Image
RUN apk upgrade --no-cache \
  && apk add --no-cache --virtual .build-deps curl \
  && apk add --no-cache git ruby npm \
  && gem install --no-document bundler-audit \
  && npm install --global --ignore-scripts corepack \
  && unzip dependency-check-${VERSION}-release.zip -d /usr/share/ \
  && rm dependency-check-${VERSION}-release.zip \
  && cd /usr/share/dependency-check/plugins \
  && curl -fSLO "${MAVEN_REPOSITORY_URL}/org/postgresql/postgresql/${POSTGRES_DRIVER_VERSION}/postgresql-${POSTGRES_DRIVER_VERSION}.jar" \
  && curl -fSLO "${MAVEN_REPOSITORY_URL}/com/mysql/mysql-connector-j/${MYSQL_DRIVER_VERSION}/mysql-connector-j-${MYSQL_DRIVER_VERSION}.jar" \
  && addgroup -S -g ${GID} ${user} \
  && adduser -S -D -u ${UID} -G ${user} ${user} \
  && mkdir /usr/share/dependency-check/data \
  && chown -R ${user}:0 /usr/share/dependency-check \
  && chmod -R g=u /usr/share/dependency-check \
  && mkdir /report \
  && chown -R ${user}:0 /report \
  && chmod -R g=u /report \
  && apk del .build-deps \
  && rm -rf /tmp/* /root/.cache /root/.npm
Unverified archive downloaded and extracted at build time
A remote archive (zip/tar) is downloaded at build time and unpacked into the image without verifying its integrity. The extracted contents usually become part of the runtime image or are executed later (for example as the ENTRYPOINT), so a compromised or substituted archive — or a download from a defunct or hijacked host — introduces arbitrary files into the image. The risk is greatest when the version or URL is resolved dynamically (e.g. from a remote current.txt or an ENV-built URL), since the artifact can change at any time. Pin a specific version and verify a published SHA-256 checksum or a GPG/cosign signature before extracting.
HighPermissions
FROM golang:1.26.5-alpine AS go
Base image moderately outdated
The base image is showing its age: for :latest and untagged images the threshold is 14 days; for pinned version tags (e.g. node:20, alpine:3.18) the threshold is 60 days. Regular rebuilds ensure OS-level security patches are applied. Rebuild with docker build --pull or set up an automated rebuild pipeline.
MediumBase Image
FROM golang:1.26.5-alpine AS go
Alpine base image — no apk commands in this stage
The base image is Alpine Linux but apk was not called anywhere in this stage. Packages baked into the base image at pull time may contain unpatched vulnerabilities. Consider running apk update && apk upgrade --no-cache to apply OS security patches, or use a regularly-rebuilt base image.
MediumBase Image
FROM golang:1.26.5-alpine AS go
FROM without image digest
The base image is referenced by tag rather than by digest. Tags are mutable — the same tag can be silently updated to point to a different image. Pinning to a digest (e.g. image:tag@sha256:...) guarantees you always build from exactly the image you reviewed. If you rebuild frequently with docker build --pull, the freshness risk is lower — though supply-chain reproducibility still benefits from digest pinning. Note: digest-pinning fixes the image layer but does not protect against newly-discovered CVEs in that layer — vulnerability scanning should still be performed regularly even when digests are in use.
LowBase Image
FROM azul/zulu-openjdk-alpine:25 AS jlink
FROM without image digest
The base image is referenced by tag rather than by digest. Tags are mutable — the same tag can be silently updated to point to a different image. Pinning to a digest (e.g. image:tag@sha256:...) guarantees you always build from exactly the image you reviewed. If you rebuild frequently with docker build --pull, the freshness risk is lower — though supply-chain reproducibility still benefits from digest pinning. Note: digest-pinning fixes the image layer but does not protect against newly-discovered CVEs in that layer — vulnerability scanning should still be performed regularly even when digests are in use.
LowBase Image
FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine
FROM without image digest
The base image is referenced by tag rather than by digest. Tags are mutable — the same tag can be silently updated to point to a different image. Pinning to a digest (e.g. image:tag@sha256:...) guarantees you always build from exactly the image you reviewed. If you rebuild frequently with docker build --pull, the freshness risk is lower — though supply-chain reproducibility still benefits from digest pinning. Note: digest-pinning fixes the image layer but does not protect against newly-discovered CVEs in that layer — vulnerability scanning should still be performed regularly even when digests are in use.
LowBase Image

ADD cli/target/dependency-check-${VERSION}-release.zip /
ADD used where COPY would suffice
The ADD instruction is used with a local source that is not a recognised archive format. ADD has two behaviours beyond COPY: it fetches remote URLs and auto-extracts tar archives. When neither is needed, COPY is clearer and safer — it does exactly what it says with no implicit side effects. Replace ADD with COPY for plain file and directory sources.
RUN apk upgrade --no-cache \
  && apk add --no-cache --virtual .build-deps curl \
  && apk add --no-cache git ruby npm \
  && gem install --no-document bundler-audit \
  && npm install --global --ignore-scripts corepack \
  && unzip dependency-check-${VERSION}-release.zip -d /usr/share/ \
  && rm dependency-check-${VERSION}-release.zip \
  && cd /usr/share/dependency-check/plugins \
  && curl -fSLO "${MAVEN_REPOSITORY_URL}/org/postgresql/postgresql/${POSTGRES_DRIVER_VERSION}/postgresql-${POSTGRES_DRIVER_VERSION}.jar" \
  && curl -fSLO "${MAVEN_REPOSITORY_URL}/com/mysql/mysql-connector-j/${MYSQL_DRIVER_VERSION}/mysql-connector-j-${MYSQL_DRIVER_VERSION}.jar" \
  && addgroup -S -g ${GID} ${user} \
  && adduser -S -D -u ${UID} -G ${user} ${user} \
  && mkdir /usr/share/dependency-check/data \
  && chown -R ${user}:0 /usr/share/dependency-check \
  && chmod -R g=u /usr/share/dependency-check \
  && mkdir /report \
  && chown -R ${user}:0 /report \
  && chmod -R g=u /report \
  && apk del .build-deps \
  && rm -rf /tmp/* /root/.cache /root/.npm
apk add without version pinning
apk add is used without pinning package versions. Without a version constraint (e.g. apk add --no-cache nginx=1.24.0-r15), Alpine installs whatever version the repository currently serves, so builds are not reproducible and an unexpected or regressed version can be pulled at build time. Pin each package: apk add --no-cache <pkg>=<version>.
Package Manager
RUN apk upgrade --no-cache \
  && apk add --no-cache --virtual .build-deps curl \
  && apk add --no-cache git ruby npm \
  && gem install --no-document bundler-audit \
  && npm install --global --ignore-scripts corepack \
  && unzip dependency-check-${VERSION}-release.zip -d /usr/share/ \
  && rm dependency-check-${VERSION}-release.zip \
  && cd /usr/share/dependency-check/plugins \
  && curl -fSLO "${MAVEN_REPOSITORY_URL}/org/postgresql/postgresql/${POSTGRES_DRIVER_VERSION}/postgresql-${POSTGRES_DRIVER_VERSION}.jar" \
  && curl -fSLO "${MAVEN_REPOSITORY_URL}/com/mysql/mysql-connector-j/${MYSQL_DRIVER_VERSION}/mysql-connector-j-${MYSQL_DRIVER_VERSION}.jar" \
  && addgroup -S -g ${GID} ${user} \
  && adduser -S -D -u ${UID} -G ${user} ${user} \
  && mkdir /usr/share/dependency-check/data \
  && chown -R ${user}:0 /usr/share/dependency-check \
  && chmod -R g=u /usr/share/dependency-check \
  && mkdir /report \
  && chown -R ${user}:0 /report \
  && chmod -R g=u /report \
  && apk del .build-deps \
  && rm -rf /tmp/* /root/.cache /root/.npm
apk add without version pinning
apk add is used without pinning package versions. Without a version constraint (e.g. apk add --no-cache nginx=1.24.0-r15), Alpine installs whatever version the repository currently serves, so builds are not reproducible and an unexpected or regressed version can be pulled at build time. Pin each package: apk add --no-cache <pkg>=<version>.
Package Manager
RUN apk upgrade --no-cache \
  && apk add --no-cache --virtual .build-deps curl \
  && apk add --no-cache git ruby npm \
  && gem install --no-document bundler-audit \
  && npm install --global --ignore-scripts corepack \
  && unzip dependency-check-${VERSION}-release.zip -d /usr/share/ \
  && rm dependency-check-${VERSION}-release.zip \
  && cd /usr/share/dependency-check/plugins \
  && curl -fSLO "${MAVEN_REPOSITORY_URL}/org/postgresql/postgresql/${POSTGRES_DRIVER_VERSION}/postgresql-${POSTGRES_DRIVER_VERSION}.jar" \
  && curl -fSLO "${MAVEN_REPOSITORY_URL}/com/mysql/mysql-connector-j/${MYSQL_DRIVER_VERSION}/mysql-connector-j-${MYSQL_DRIVER_VERSION}.jar" \
  && addgroup -S -g ${GID} ${user} \
  && adduser -S -D -u ${UID} -G ${user} ${user} \
  && mkdir /usr/share/dependency-check/data \
  && chown -R ${user}:0 /usr/share/dependency-check \
  && chmod -R g=u /usr/share/dependency-check \
  && mkdir /report \
  && chown -R ${user}:0 /report \
  && chmod -R g=u /report \
  && apk del .build-deps \
  && rm -rf /tmp/* /root/.cache /root/.npm
gem install without version pinning
gem install is used without pinning the gem version. Without a version constraint, RubyGems installs the latest available version, so builds are not reproducible and an unexpected or malicious release can be pulled at build time. Pin the version with gem install <gem> -v <version> (or <gem>:<version>).
Package Manager
RUN apk upgrade --no-cache \
  && apk add --no-cache --virtual .build-deps curl \
  && apk add --no-cache git ruby npm \
  && gem install --no-document bundler-audit \
  && npm install --global --ignore-scripts corepack \
  && unzip dependency-check-${VERSION}-release.zip -d /usr/share/ \
  && rm dependency-check-${VERSION}-release.zip \
  && cd /usr/share/dependency-check/plugins \
  && curl -fSLO "${MAVEN_REPOSITORY_URL}/org/postgresql/postgresql/${POSTGRES_DRIVER_VERSION}/postgresql-${POSTGRES_DRIVER_VERSION}.jar" \
  && curl -fSLO "${MAVEN_REPOSITORY_URL}/com/mysql/mysql-connector-j/${MYSQL_DRIVER_VERSION}/mysql-connector-j-${MYSQL_DRIVER_VERSION}.jar" \
  && addgroup -S -g ${GID} ${user} \
  && adduser -S -D -u ${UID} -G ${user} ${user} \
  && mkdir /usr/share/dependency-check/data \
  && chown -R ${user}:0 /usr/share/dependency-check \
  && chmod -R g=u /usr/share/dependency-check \
  && mkdir /report \
  && chown -R ${user}:0 /report \
  && chmod -R g=u /report \
  && apk del .build-deps \
  && rm -rf /tmp/* /root/.cache /root/.npm
npm install without cache cleanup in same RUN layer
A RUN instruction runs npm install but does not clean the npm cache in the same layer. The npm cache is committed to the image, increasing its size without providing any runtime value. Add npm cache clean --force to the end of the same RUN instruction: RUN npm install && npm cache clean --force. Alternatively, use a BuildKit cache mount (--mount=type=cache,target=/root/.npm) to avoid writing the cache into the layer at all.
FROM instruction
Full system upgrade in build
This Dockerfile runs a full OS package upgrade at build time. This reduces base-image CVE exposure at the moment the image is built, but the image will accumulate vulnerabilities over time as new CVEs are discovered after the last build. Combine with a scheduled rebuild policy for ongoing protection.
Base Image
No HEALTHCHECK defined
No HEALTHCHECK defined in final stage
The final image stage does not define a HEALTHCHECK instruction. Without one, container orchestrators and the Docker daemon cannot distinguish a running container from one that is alive but unhealthy (deadlocked, crashed dependencies, etc.), increasing the risk of traffic being routed to a broken container. Add a HEALTHCHECK CMD instruction that tests whether the application is functioning correctly. Note: if the base image already defines a HEALTHCHECK it will be inherited, but this cannot be verified from the Dockerfile alone.