Skip to content

feat: harden container with non-root execution and smoke tests - #201

Open
ashhadali10 wants to merge 6 commits into
OWASP:mainfrom
ashhadali10:feat/non-root-hardening
Open

ashhadali10 wants to merge 6 commits into
OWASP:mainfrom
ashhadali10:feat/non-root-hardening

Conversation

@ashhadali10

Copy link
Copy Markdown

Description

Implements the non-root container hardening portion of #109.

This PR makes the container drop privileges to a dedicated non-root user
(docksec, UID/GID 10001) for standalone docker run, while keeping root
for GitHub Docker container actions (required for GITHUB_WORKSPACE access).

  • Dockerfile: installed pinned Docker CLI static binary (Debian trixie no
    longer ships docker.io, so the previous install silently produced a broken
    image with docker: not found); created docksec user (UID/GID 10001);
    matched container docker group GID to the host socket GID at build time;
    pre-created /home/docksec/.docksec/results with correct ownership to fix
    PermissionError when writing reports as non-root; installed gosu for
    safe privilege dropping. USER is intentionally not set because GitHub
    Docker container actions must run as root to access GITHUB_WORKSPACE.
  • entrypoint.sh: drops privileges to docksec via gosu when running
    standalone; stays root when GITHUB_WORKSPACE is set (GitHub Action
    compatibility); users can override with docker run --user ....
  • tests/smoke-test-nonroot.sh: verifies standalone run drops privileges,
    CLI (--version / --help) works as non-root, GitHub Action mode stays
    root, and --user 0 override works.

Closes #109 (non-root + smoke tests portion)

Type of Change

  • New feature
  • Security fix
  • Test update
  • Build / CI configuration

How Has This Been Tested?

  • Unit tests
  • Manual testing

Test Configuration:

  • Python version: 3.12.15
  • Operating System: Ubuntu 22.04 (x86_64)
  • DockSec version: 2026.8.19

Test evidence:

  1. pytest tests/ — 546 passed, 153 subtests passed in 22.91s
  2. bash tests/smoke-test-nonroot.sh — all 4 tests pass:
    • Test 1: standalone run drops privileges ✅
    • Test 2: CLI works as non-root ✅
    • Test 3: GH Action mode stays root ✅
    • Test 4: --user 0 override works ✅
  3. Manual image build and scan of vulnerables/web-dvwa:latest succeeds,
    producing HTML and JSON reports under the mounted output directory.

Behaviour matrix:

Invocation Effective UID
docker run ... docksec 10001 (non-root, auto-drop)
docker run -e GITHUB_WORKSPACE=... docksec (Action) 0 (root)
docker run --user 0 docksec 0 (explicit override)
docker run --user 10001:10001 docksec 10001

Checklist

  • Code follows the style guidelines of this project
  • Self-review completed
  • Hard-to-understand areas are commented
  • Documentation updated where needed (no user-facing docs changed)
  • No new warnings or errors introduced
  • Tests added that prove the fix or feature works
  • All existing tests pass
  • Dependent changes have been merged and published (n/a)
  • Spelling checked

Screenshots (if applicable)

N/A — no UI changes.

Related Issues / PRs

Note on make check-test: Local black --check . reports 46 pre-existing
files that black would reformat. This is a version mismatch (black is
unpinned in pyproject.toml, so pip install -e ".[dev]" pulls a newer
version than the code was formatted with). No .py files are modified in
this PR, so this is unrelated to these changes.


By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

@github-actions github-actions Bot added docker Changes to Docker/container assets tests Changes to the test suite labels Oct 19, 2026
@pfarikrispy

Copy link
Copy Markdown

@ashhadali10 I think is really great work! Thanks. You were very thorough and structured. The UID/GUID handling is well thought out and marked with clear variables. The smoke test is excellent! very very nice.

My only remark would be that you are pulling in a binary from docker to install the docker CLI from a curl.
That's good but you do not verify the source nor the integrity of the binary archive.

Since this is a security tool, I think the source and integrity of any deps should be verified before installing. Right? This is often overlooked but these days that can become a costly mistake. Granted, docker.io is not just any source, but still.

@pfarikrispy

pfarikrispy commented Oct 25, 2026 •

Copy link
Copy Markdown

While searching for the docker cli tar.gz files, I found the official docker cli image. I couldn't find a clear way to check the source (via signatures) or integrity (via checksums).

@ashhadali10

Copy link
Copy Markdown
Author

While searching for the docker cli tar.gz files, I found the official docker cli image. I couldn't find a clear way to check the source (via signatures) or integrity (via checksums).

* Wouldn't it greatly simplify the Dockerfile by using a multi-layer build and simply copy the binary from the official image into the final layer?
  
  * See https://hub.docker.com/layers/library/docker/cli/images/sha256-98776017a505b6d89c0adff8c63b0d8701ca10280c41315dbb0638fe3b5cf6e5

* Alternatively, why don't you just install the docker CLI through `apt-get` from the official Ubuntu pkg repos, since these are signed with GPG.

Thanks @pfarikrispy for the kind words and for catching this you re right, and I appreciate the careful review.

You correctly identified a real supply-chain risk the previous Dockerfile fetched the Docker CLI as a tarball over curl without any signature or checksum verification. If the download had been tampered with, the image would have shipped a malicious binary a costly mistake for a security tool.

Fixed in 70bc9c4: the Docker CLI is now installed from Docker's official GPG-signed APT repository (docker-ce-cli), so apt verifies both source and integrity automatically. I went with your apt-get suggestion (Solution 2) because it gives us end-to-end GPG verification and keeps the build simple. I also looked at the multi-stage docker/cli image (Solution 1), but the apt-get path felt cleaner here.

Verified locally:

  • docker --version works as non root
  • dpkg -l docker-ce-cli confirms the package came from the APT repo (not a tarball)
  • pytest tests/ 546 passed
  • bash tests/smoke-test-nonroot.sh 4/4 pass (standalone drops privileges, GH Action mode stays root, --user 0 override works)

The CI workflows are still awaiting maintainer approval would you be able to approve them so the checks can run?

Thanks again for the thorough review.

@pfarikrispy

pfarikrispy commented Oct 26, 2026 •

Copy link
Copy Markdown

Hi @ashhadali10 , great work again! Yes, using apt-get is the most straightforward path. you're already using it anyway. Good of you to also verify the test results and make sure it all still works as desired.

I'm not an approver or maintainer, just someone like you. I'm sorry. :(

One suggestion: if you do all the work to install the GPG key first, you can skip the apt-get in the beginning and combine all the package installs in a single apt-get. Running apt-get update again is useless anyway, so short in a row.

@ashhadali10

Copy link
Copy Markdown
Author

Hi @advaitpatel @arkid15r friendly ping on this PR.

@pfarikrispy has reviewed the changes and the supply-chain concern he raised
(curl-downloaded Docker CLI without integrity verification) has been addressed
in 70bc9c4 by installing docker-ce-cli from Docker's GPG-signed APT repo.

Could a maintainer:

  1. Approve the 9 pending CI workflows so the checks can run, and
  2. Take a look when you have a moment???

Thanks

@github-actions

Copy link
Copy Markdown

DockSec

No container files changed in this pull request.

@codecov-commenter

codecov-commenter commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.02%. Comparing base (9496293) to head (70bc9c4).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #201      +/-   ##
==========================================
+ Coverage   86.01%   86.02%   +0.01%     
==========================================
  Files          53       53              
  Lines        8117     8117              
==========================================
+ Hits         6982     6983       +1     
+ Misses       1135     1134       -1     
Flag Coverage Δ
unittests 86.02% <ø> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread Dockerfile
# page to the destination, producing a "successful" build with a text file where
# the binary should be.
# Docker CLI (GPG-signed APT repository)
RUN apt-get update && apt-get install -y --no-install-recommends \
Comment thread Dockerfile
RUN rm -rf /src

# Copy and set permissions for entrypoint
RUN groupadd -g ${DOCKER_USER_GID} docksec && \
Comment thread Dockerfile
RUN groupadd -g ${DOCKER_USER_GID} docksec && \
useradd -m -u ${DOCKER_UID} -g docksec -s /sbin/nologin -c "DockSec App User" docksec

RUN if getent group ${DOCKER_GID} >/dev/null; then \
@advaitpatel

Copy link
Copy Markdown
Collaborator

Hello @ashhadali10 can you please fix the github security findings?

@ashhadali10

ashhadali10 commented Oct 29, 2026 via email •

Copy link
Copy Markdown
Author

- Bake HOME and DOCKSEC_RESULTS_DIR into the image via ENV so the
  non-root CLI always resolves to /home/docksec/.docksec/results,
  regardless of how privileges are dropped.
- Keep entrypoint.sh's standalone privilege-drop block self-contained
  so tests that run entrypoint.sh without a Docker daemon still
  forward CLI args correctly.
- Move ARG HADOLINT_VERSION and ARG TRIVY_VERSION next to their
  respective install blocks so the packaging test can find the
  arm64 case within its scan window.

Verified locally:
- Non-root scan drops to UID 10001 and writes reports to ~/.docksec/results
- Docker CLI installed from GPG-signed APT repo (dpkg -l docker-ce-cli)
- GITHUB_WORKSPACE mode stays root (no privilege drop)
- CI case (root-owned /work) completes without PermissionError
- pytest tests/ -> 546 passed

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docker Changes to Docker/container assets tests Changes to the test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Improve container security and harden your supply chain

5 participants