OWASP DockSec

Dockerfile from OWASP DockSec, an AI-assisted Dockerfile security tool — a real-world example (notably with curl-pipe-sh, an unpinned latest binary download, and a copied-in entrypoint script).

← Examples

Source: GitHub

10 issues foundCritical: 1High: 3Medium: 2Low: 4Recommendations: 3
DockerfileLines with findings are highlighted
SHA-256:
1FROM python:3.12-slimCritical
2RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/*Medium
3RUN curl -sL -o /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 && chmod +x /usr/local/bin/hadolintHigh
4RUN curl -sfL https://raw.githubusercontent.com/aquasec/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/binHigh
5WORKDIR /github/workspace
6COPY . .Medium
7RUN pip install --no-cache-dir .Low
8COPY entrypoint.sh /entrypoint.sh
9RUN chmod +x /entrypoint.sh
10ENTRYPOINT ["/entrypoint.sh"]Low
Findings
10 issues found
FROM python:3.12-slim
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 curl -sL -o /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64 \
  && chmod +x /usr/local/bin/hadolint
Unverified executable downloaded at build time
A file is downloaded at build time (curl/wget) and made executable — written into a bin directory or chmod +x'd — without verifying its integrity. The contents are unverified, so a compromised or malicious source — or a silently changed artifact — substitutes an arbitrary executable that then runs with build-time (and often runtime) privileges. The risk is greatest when the URL is unpinned (e.g. a /latest/ release 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 use, or COPY a pre-verified binary into the image.
# Pin a version, verify the checksum, then install RUN curl -fsSL -o /tmp/hadolint \ https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64 \ && echo "<sha256> /tmp/hadolint" | sha256sum -c \ && install -m 0755 /tmp/hadolint /usr/local/bin/hadolint
HighPermissions
RUN curl -sfL https://raw.githubusercontent.com/aquasec/trivy/main/contrib/install.sh \
  | sh -s -- -b /usr/local/bin
Curl download piped directly to a shell
A curl download is piped directly into a shell interpreter (sh, bash, etc.) without any integrity check. The transport is normally HTTPS, so the risk is not interception but the source itself: a compromised or malicious server — or a change to the script between builds — runs arbitrary shell commands with full build-time privileges, with no chance to inspect them first. Download to a file, verify a checksum or GPG signature, then execute.
# Never pipe curl into a shell. Download, verify against a checksum you # have pinned in the Dockerfile (reviewed out-of-band), then execute. RUN curl -fsSL https://example.com/install.sh -o install.sh \ && echo "0a1b2c...<the sha256 you pinned> install.sh" | sha256sum -c - \ && sh install.sh \ && rm install.sh
HighNetwork
Container user
User root
The final container is running under the context of the root user.
# Create a dedicated non-root user and switch to it before CMD/ENTRYPOINT RUN useradd --create-home --shell /bin/bash appuser USER appuser
HighUser
RUN apt-get update \
  && apt-get install -y curl git \
  && rm -rf /var/lib/apt/lists/*
Apt-family install, but no upgrade
An apt-family tool (apt, apt-get, aptitude, dpkg) is used to install packages but upgrade was not called first to patch existing packages.
RUN apt-get update && apt-get upgrade -y \ && apt-get install -y --no-install-recommends <packages> \ && rm -rf /var/lib/apt/lists/*
MediumPackage Manager
COPY . .
Wildcard COPY
Using COPY with a wildcard can introduce unnecessary files into the image.
COPY src/ /app/src/ # or target a specific file COPY config.json /app/config.json
MediumFile Copy
FROM python:3.12-slim
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
RUN curl -sfL https://raw.githubusercontent.com/aquasec/trivy/main/contrib/install.sh \
  | sh -s -- -b /usr/local/bin
RUN pipe without pipefail
A RUN instruction contains a shell pipe (cmd1 | cmd2) but pipefail is not enabled. By default the pipeline exit status is that of the last command only, so a failure in an earlier stage (for example a failed download in curl ... | tar, or a failed fetch in wget ... | sh) is silently ignored and the build proceeds on partial or corrupt input. Enable pipefail so any stage failure aborts the build: set the shell once with SHELL ["/bin/bash", "-o", "pipefail", "-c"], or prefix the command with set -o pipefail &&.
LowPermissions
ENTRYPOINT ["/entrypoint.sh"]
Executes a copied-in script not analysed by DockerSec
A shell script is copied into the image (COPY/ADD) and then executed by a RUN, ENTRYPOINT, or CMD instruction. DockerSec analyses the Dockerfile only; the script contents are not inspected, so security-relevant operations inside it (package installs, credential handling, network configuration, privilege changes) are not covered by this scan. There is latent risk here: review the script manually. Where practical, inline the script logic into the Dockerfile so it can be analysed, or keep the script minimal and audited.
Low
RUN pip install --no-cache-dir .
pip used without upgrading pip itself
pip is used in this stage but pip itself is never upgraded. An outdated pip may resolve packages against a stale index and may itself contain known vulnerabilities. Add pip install --upgrade pip (or pip install -U pip) before installing other packages.
LowPackage Manager

RUN apt-get update \
  && apt-get install -y curl git \
  && rm -rf /var/lib/apt/lists/*
apt-get install without --no-install-recommends
apt-get install is called without --no-install-recommends. Without this flag, apt installs recommended packages in addition to those explicitly requested, unnecessarily increasing image size and attack surface. Add --no-install-recommends to install only what is needed: RUN apt-get install -y --no-install-recommends <packages>
Package Manager
RUN apt-get update \
  && apt-get install -y curl git \
  && rm -rf /var/lib/apt/lists/*
apt-get install without version pinning
apt-get install is used without pinning package versions. Without a version constraint (e.g. nginx=1.18.0-0ubuntu1), apt will install whatever is current in the package index at build time, making the build non-reproducible and potentially installing an unexpected newer version on a future rebuild. Pin package versions to ensure consistent, reproducible builds: apt-get install -y nginx=1.18.0-0ubuntu1.
Package Manager
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.