Security International Group — Hardened Docker Image
A security-hardened Debian 13 (Trixie) container image built for CIS Docker Benchmark 1.13.0, DISA STIG, and FIPS 140-3 aligned compliance. All hardening runs inside the container — no host daemon writes, no socket access, no kernel module loading.
git clone https://github.com/Security-International-Group/HARDN_DOCKER.git
cd HARDN_DOCKER
docker compose build --no-cachedocker compose up -d
docker compose ps # confirm hardn_docker is Up (healthy)
docker compose logs -f # watch build-time hardening outputdocker compose downThe hardened base image ships no listening service. It purges python3 to
eliminate that CVE surface, so the Flask compliance dashboard in src/app.py
does not run in the base image, and the base docker-compose.yml publishes
no ports.
If you want the dashboard, add it in an extension layer that re-introduces Python and publishes a loopback-bound port:
# Dockerfile.dashboard
FROM hardn-xdr:latest
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 python3-flask \
&& rm -rf /var/lib/apt/lists/*
COPY src/app.py /opt/hardn-xdr/app.py
USER 10001:10001
EXPOSE 5000
CMD ["python3", "/opt/hardn-xdr/app.py"]docker build -f Dockerfile.dashboard -t hardn-xdr:dashboard .
docker run --rm -p 127.0.0.1:8082:5000 hardn-xdr:dashboard
curl http://localhost:8082/healthAdding Python back re-introduces its CVE surface. Keep the dashboard on a separate image from the zero-CVE base; do not merge it into the base.
# Run the built-in health check manually:
docker compose exec hardn-xdr /usr/local/bin/health_check.shThis checks: non-root user, AppArmor status, privilege escalation protection, core dump settings, audit rules, SSH hardening, and key sysctl values.
# Run the full CIS Docker Benchmark 1.13.0 smoke test:
docker compose exec hardn-xdr /usr/local/bin/smoke_test.shOutput shows [PASS], [WARN], [INFO], and [FAIL] results across all CIS categories.
docker run --rm -u 0 hardn-xdr:latest bash -c '
echo "--- CIS / FIPS Controls ---"
echo -n "sysctl hardening file : "; test -f /etc/sysctl.d/99-hardening.conf && echo PASS || echo FAIL
echo -n "core dumps disabled : "; grep -q "hard core 0" /etc/security/limits.conf && echo PASS || echo FAIL
echo -n "TLS >= 1.2 enforced : "; grep -q "MinProtocol = TLSv1.2" /etc/ssl/openssl.cnf && echo PASS || echo FAIL
echo -n "FIPS AEAD ciphers : "; grep -q "AES256-GCM" /etc/ssl/openssl.cnf && echo PASS || echo FAIL
echo -n "password max days=90 : "; grep -q "^PASS_MAX_DAYS.*90" /etc/login.defs && echo PASS || echo FAIL
echo -n "non-root user hardn : "; id hardn >/dev/null 2>&1 && echo PASS || echo FAIL
echo -n "/tmp sticky 1777 : "; [ "$(stat -c %a /tmp)" = "1777" ] && echo PASS || echo FAIL
echo -n "python3 absent : "; command -v python3 >/dev/null && echo FAIL || echo PASS
echo -n "curl absent : "; command -v curl >/dev/null && echo FAIL || echo PASS
echo -n "wget absent : "; command -v wget >/dev/null && echo FAIL || echo PASS
echo -n "openssl CLI absent : "; command -v openssl >/dev/null && echo INFO || echo PASS
'# Scan for HIGH/CRITICAL CVEs
trivy image hardn-xdr:latest --severity HIGH,CRITICAL --scanners vuln
# Full scan
trivy image hardn-xdr:latest --scanners vulnExpected result:
┌────────────────────────────────┬────────┬─────────────────┐
│ Target │ Type │ Vulnerabilities │
├────────────────────────────────┼────────┼─────────────────┤
│ hardn-xdr:latest (debian 13) │ debian │ 0 │
└────────────────────────────────┴────────┴─────────────────┘
Image size: ~35.5 MB (down from 137 MB before the compliance hardening pass)
# View container logs
docker compose logs --tail 100
# Check container health status
docker inspect hardn_docker --format '{{.State.Health.Status}}'
# Open a shell as root (dev only)
docker run --rm -it -u 0 hardn-xdr:dev bash
# Check hardening completed during build
docker run --rm -u 0 hardn-xdr:dev bash -c 'test -f /opt/hardn-xdr/.hardening_complete && echo "hardening_complete" || echo "not_found"'All hardening runs inside the container at build time — no writes to the Docker daemon, host socket, or host kernel.
| Control | Implementation |
|---|---|
| CIS 4.1 | Non-root runtime user hardn (uid=10001) |
| CIS 4.6 | HEALTHCHECK defined — 12 checks, runs every 30 s |
| CIS 5.1 | AppArmor profile enforced at host runtime via --security-opt apparmor= |
| CIS 5.10/5.11 | Memory (512 MB) and CPU limits enforced in docker-compose.yml |
| CIS 5.25 | no-new-privileges: true |
| CIS 5.12 | Read-only root filesystem with tmpfs on /tmp, /run, /home/hardn |
| CIS 5.3 | All capabilities dropped |
| CIS 1.6.1 / STIG-V-230264 | Core dumps disabled in limits.conf |
| NIST SP 800-52 Rev 2 | TLS 1.2 minimum; AEAD-only ciphers in /etc/ssl/openssl.cnf |
| FIPS 140-3 | OPENSSL_FIPS=1 at runtime; fipsinstall self-test at build; GnuTLS pre-TLS-1.2 disabled |
| STIG | PASS_MAX_DAYS=90, UMASK=027, SHA_CRYPT rounds hardened |
| PAM | libpam-pwquality — minlen=14, requires upper/lower/digit/special |
| CVE surface | curl, wget, python3, openssl CLI, perl (restricted), tar (restricted), sqlite3 CLI all removed; 0 CVEs |
- Non-root execution — runs as
uid=10001, shell set to/usr/sbin/nologin - FIPS 140-3 aligned TLS — ECDHE-RSA-AES256-GCM-SHA384 / AES128-GCM-SHA256 only; TLS 1.2+
- openssl CLI removed — purged after the build-time FIPS self-test;
libssl3t64runtime kept - AppArmor / SELinux — host-enforced LSMs; profile assignment is a runtime/orchestration concern
- Seccomp profiles applied via Docker runtime
- Read-only root filesystem with
tmpfsmounts for writable paths - No new privileges capability enforced
- Kernel parameters hardened via
/etc/sysctl.d/99-hardening.confand the composesysctls:block
hardn-xdr/
├── Dockerfile # Hardened Debian 13 Trixie image definition
├── docker-compose.yml # CIS-compliant compose config (port 8082→5000)
├── deb.hardn.sh # Container-internal hardening script (build-time)
├── entrypoint.sh # Runtime entrypoint (runs hardening once, then execs)
├── health_check.sh # Docker HEALTHCHECK + CIS verification
├── smoke_test.sh # Full CIS Docker Benchmark 1.13.0 test suite
└── src/
├── app.py # Flask compliance dashboard (port 5000)
└── sources/
└── hardn_docker.png # Project logo (embedded in web UI)
debian:trixie-slim@sha256:1d3c811171a08a5adaa4a163fbafd96b61b87aa871bbc7aa15431ac275d3d430
Debian 13 (Trixie), pinned digest. For government/DoD production use, swap to Iron Bank:
FROM registry1.dso.mil/ironbank/opensource/debian/debian12:latestThis image targets CIS Docker Benchmark 1.13.0, DISA STIG, and FISMA compliance profiles.
- Iron Bank (
registry1.dso.mil) — free account, DoD-approved, zero-CVE base; use for classified/government environments - FIPS 140-3 — the image applies OpenSSL FIPS policy (AEAD-only ciphers, TLS 1.2+) and sets
OPENSSL_FIPS=1at runtime; for a fully certified FIPS environment use a FIPS-validated kernel and a FIPS-enabled OS base (e.g., RHEL 9 with FIPS mode enabled) - Deploy with
read_only: true,cap_drop: ALL,no-new-privileges: true(already set indocker-compose.yml) - Bind only to loopback in production:
127.0.0.1:8082:5000(already set) - The
opensslCLI is removed from the final image; uselibssl3t64APIs or a separate tooling container for certificate operations
Built with security and compliance in mind for production deployments.
