Summary
- container image is not using multi-layers
- packages (hadolint, trivy) are pulled insecurely thru an install Bash script
- python dependencies are not handled very well
- deps are pinned by version, not pinned by SHA digest
- you're running a full (albeit Slim) Debian version instead of just a runtime for python
Problem Statement
The current setup is susceptible to an easy supply chain attack, the container can be tainted when remote Bash scripts are compromised, and the final container image is bloated when only a python executable is needed.
Proposed Solution
I would like to suggest a Dockerfile like this:
Security could be further improved by relying on hardened images from echo.ai, dhi.io or chainguard and their competitors.
# STAGE 1: Extract Hadolint Binary (Pinned by Digest)
FROM hadolint/hadolint@sha256:7f999c0b784a95777a102c0b64d1f2e915155f86640523e1003666f7f2b988f5 AS hadolint-stage
# STAGE 2: Extract Trivy Binary (Pinned by Digest)
FROM aquasec/trivy@sha256:4d60c4a4521759600e12d46e8c7512d7c07e997455a297924d55b084937a4e6e AS trivy-stage
# STAGE 3: Secure Python Builder (Pinned by Digest)
FROM python@sha256:7b57b98150499e9842f2ed73602f357591c0e3c8868bf030432bc13346cf6e15 AS builder
# Install build-time dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \ # when needed
git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir --upgrade pip setuptools wheel
# Supply Chain Protection: Install dependencies with strict hash verification
# Create hashes using: pip-compile --generate-hashes <setup.py or pyproject.toml>
COPY requirements.txt .
RUN pip install --no-cache-dir --require-hashes -r requirements.txt
COPY . .
RUN pip install --no-cache-dir --no-deps .
# STAGE 4: Final Distroless Runtime (Pinned by Digest)
FROM gcr.io/distroless/python3-debian12@sha256:5646cf896dafe95def30420defa8077fc8ee71ef5578e2c018c2572aae0541e5
COPY --from=builder /opt/venv /opt/venv
COPY --from=hadolint-stage /usr/bin/hadolint /usr/local/bin/hadolint
COPY --from=trivy-stage /usr/local/bin/trivy /usr/local/bin/trivy
ENV PATH="/opt/venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1
WORKDIR /github/workspace
ENTRYPOINT ["python", "-m", "docksec"]
Alternatives Considered
Looking at the entrypoint script, you may need to have that present and you can't simply run docksec.
- implement the parameter handling cleanly in your python program
- revert the distroless final image to a python Slim image but use a non-root (!) user
# Alternative Final Stage if entrypoint.sh is mandatory:
FROM python:3.12-slim AS final
# Create a dedicated non-root user/group
RUN groupadd -g 10001 appuser && \
useradd -u 10001 -g appuser -s /sbin/nologin -c "App User" appuser
COPY --from=hadolint-stage /usr/bin/hadolint /usr/local/bin/hadolint
COPY --from=trivy-stage /usr/local/bin/trivy /usr/local/bin/trivy
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh && chown appuser:appuser /entrypoint.sh
# Drop privileges completely
USER appuser
ENTRYPOINT ["/entrypoint.sh"]
Even better for python, would be to switch using uv for dependency mgmt:
# STAGE 1: Extract Binaries (Pinned)
FROM hadolint/hadolint@sha256:7f999c0b784a95777a102c0b64d1f2e915155f86640523e1003666f7f2b988f5 AS hadolint
FROM aquasec/trivy@sha256:4d60c4a4521759600e12d46e8c7512d7c07e997455a297924d55b084937a4e6e AS trivy
FROM ghcr.io/astral-sh/uv:0.5@sha256:4624738479e0f6f059e09d57a22ef45b78f4477b949f91a5ec407a51357f8976 AS uv
# STAGE 2: Builder
FROM python@sha256:7b57b98150499e9842f2ed73602f357591c0e3c8868bf030432bc13346cf6e15 AS builder
# Copy uv from its official image
COPY --from=uv /uv /bin/uv
WORKDIR /app
# Enable bytecode compilation and optimization
ENV UV_COMPILE_BYTECODE=1
# Prevent uv from using the internet for deps; ensure we rely on the lockfile
ENV UV_FROZEN=1
COPY pyproject.toml uv.lock ./
# Install dependencies into /opt/venv
RUN uv venv /opt/venv && \
. /opt/venv/bin/activate && \
uv sync --frozen --no-dev
# Copy source code and install
COPY . .
RUN . /opt/venv/bin/activate && uv pip install --no-deps .
# STAGE 3: Final Distroless Runtime
FROM gcr.io/distroless/python3-debian12@sha256:5646cf896dafe95def30420defa8077fc8ee71ef5578e2c018c2572aae0541e5
COPY --from=builder /opt/venv /opt/venv
COPY --from=hadolint /usr/bin/hadolint /usr/local/bin/hadolint
COPY --from=trivy /usr/local/bin/trivy /usr/local/bin/trivy
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /github/workspace
ENTRYPOINT ["python", "-m", "docksec"]
Use Case
Who would benefit from this feature?
Acceptance Criteria
What does done look like?
[tool.uv]
# Ignore any package version published within the last 5 days
exclude-newer = "5 days"
I'm more than happy to create a PR but I'm not a python developer, mostly a security engineer 😊 but I'm able to lend a hand if you get stuck on the security aspects when the python part works .
Summary
Problem Statement
The current setup is susceptible to an easy supply chain attack, the container can be tainted when remote Bash scripts are compromised, and the final container image is bloated when only a python executable is needed.
Proposed Solution
I would like to suggest a Dockerfile like this:
Security could be further improved by relying on hardened images from echo.ai, dhi.io or chainguard and their competitors.
Alternatives Considered
Looking at the
entrypointscript, you may need to have that present and you can't simply run docksec.Even better for python, would be to switch using
uvfor dependency mgmt:Use Case
Who would benefit from this feature?
Acceptance Criteria
What does done look like?
uvand add a 5-day back-off for newer updatesI'm more than happy to create a PR but I'm not a python developer, mostly a security engineer 😊 but I'm able to lend a hand if you get stuck on the security aspects when the python part works .