Aller au contenu

Open source / Sentinel / dockerfile-sentinel

dockerfile-sentinel

Dockerfile and Docker Compose security linter: root users, unpinned or EOL base images, hardcoded secrets, curl | sh, disabled TLS/GPG checks, sensitive COPY, missing .dockerignore, privileged containers, docker.sock mounts, host namespaces and dangerous capabilities. Zero deps.

Sur npm
$ npm install dockerfile-sentinel

README

dockerfile-sentinel

npm version CI Node License: MIT provenance

Security linter for Dockerfiles and Docker Compose files: root containers, baked-in secrets, curl | sh, unpinned and end-of-life base images, privileged services, Docker socket mounts and exposed databases.

dockerfile-sentinel reads Dockerfiles, Containerfiles and Compose files and reports security problems with a line number and a fix. CLI, library and GitHub Action. Node.js 18+, one runtime dependency (bundle-secret-guard, for its shared credential patterns). Nothing is built or pulled; files are only parsed. Secrets are masked in every output.

What it checks

  • Dockerfiles: final stage running as root, secrets in ENV/ARG/RUN (AWS, GitHub, Slack, Stripe, private keys and more), curl … | sh, disabled TLS verification, ADD of remote URLs without --checksum, HTTP downloads, chmod 777, base images that are end-of-life (Node, Python, Debian, Ubuntu, Alpine, CentOS…) or not pinned by digest, COPY . with no .dockerignore, copying .env/keys/.git into the image, SSH servers, sudo, apt-get upgrade, uncleaned package caches, missing HEALTHCHECK, deprecated MAINTAINER.
  • Compose: privileged, Docker/containerd socket mounts, host network/PID/IPC namespaces, dangerous capabilities (SYS_ADMIN, ALL…), seccomp/apparmor:unconfined, sensitive host paths (/, /etc, /proc…), literal secrets in environment, databases and admin APIs published on all interfaces, :latest/untagged images.

Directories are searched for Dockerfile, Dockerfile.*, *.Dockerfile, Containerfile, docker-compose*.yml and compose*.yaml (skipping .git and node_modules). A .dockerignore next to each Dockerfile, or in --context, is taken into account.

Rules

RuleDefaultMeaning
hardcoded-secretcriticalHardcoded credential or private key
secret-in-envhighSecret-looking ENV value baked into the image
secret-in-argmediumSecret passed as a build ARG (visible in image history)
copy-sensitive-filehighCOPY/ADD of keys, .env, cloud credentials or .git
missing-dockerignoremediumBuild context copied without a .dockerignore (or it misses .git/.env)
run-as-roothighFinal stage runs as root
sudo-usagemediumsudo installed or used
base-image-latestmediumBase image uses :latest or no tag
base-image-unpinnedlowBase image not pinned by digest
eol-base-imagemediumEnd-of-life base image
curl-pipe-shellhighRemote script piped into a shell
insecure-tlshighTLS certificate verification disabled
gpg-check-disabledhighPackage signature checks disabled
add-remote-urlmediumADD from a URL without --checksum
add-instead-of-copylowADD used for local files (use COPY)
http-downloadmediumPlain-HTTP download during build
chmod-777mediumWorld-writable permissions (chmod 777 / a+w)
ssh-servermediumSSH server installed or port 22 exposed
apt-upgradelowapt-get upgrade / dist-upgrade in image build
pkg-cache-not-cleanedinfoPackage manager cache left in the layer
no-healthcheckinfoNo HEALTHCHECK in final stage
maintainer-deprecatedinfoMAINTAINER is deprecated (use LABEL)
parse-errormediumFile could not be parsed
compose-privilegedcriticalCompose service runs privileged
compose-docker-socketcriticalDocker socket mounted into a container
compose-host-namespacehighHost network/PID/IPC/UTS/userns namespace shared
compose-dangerous-caphighDangerous Linux capability added
compose-security-opthighseccomp/AppArmor/SELinux disabled
compose-sensitive-mounthighSensitive host path mounted
compose-secret-envhighLiteral secret in service environment
compose-exposed-portmediumDatabase/admin port published on all interfaces
compose-image-latestlowService image uses :latest or no tag

Install and run

npx dockerfile-sentinel                      # scan the current directory
npx dockerfile-sentinel Dockerfile docker-compose.yml --fail-on medium
npx dockerfile-sentinel services/ --format sarif > docker.sarif

Library

import { lint, exitCode } from 'dockerfile-sentinel';
const report = lint(['.'], { severity: { 'no-healthcheck': 'off' } });
process.exitCode = exitCode(report, 'high');

Configuration

Put a .dockerfile-sentinel.json in the working directory (or pass --config <file>). Every key is optional; CLI flags override the file.

{
  "failOn": "high",
  "severity": { "run-as-root": "high" },
  "allow": [
    { "rule": "run-as-root", "match": "docker/debug.Dockerfile*", "reason": "Accepted risk, ticket SEC-123", "expires": "2027-03-31" }
  ]
}
  • severity changes the severity of any rule, or turns it off.
  • allow suppresses findings. Each entry needs a rule (globs such as run-* work) and a reason, and can be narrowed with target and match (a glob tested against the target, location and value). Suppressed findings are listed in the JSON report. When expires (YYYY-MM-DD) has passed, the finding comes back together with an allow-expired finding, so exceptions can't be forgotten. Entries that match nothing are reported as allow-unused (info).

Output and exit codes

  • --format text|json|sarif|markdown for stdout, plus --json, --sarif and --markdown <file> to write extra copies in one run. SARIF 2.1.0 works with GitHub code scanning.
  • --fail-on critical|high|medium|low|info|none (default high).
  • Exit code 0: passed. 1: at least one finding at or above --fail-on. 2: usage or config error, or a target could not be checked.
  • --list-rules prints every rule with its default severity.

GitHub Action

permissions:
  contents: read
  security-events: write   # only for the SARIF upload
steps:
  - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
    with: { persist-credentials: false }
  - uses: MrLouisWright/dockerfile-sentinel@v1
    with:
      targets: '.'
      fail-on: high
      sarif: dockerfile-sentinel.sarif
  - uses: github/codeql-action/upload-sarif@v3
    if: always()
    with: { sarif_file: dockerfile-sentinel.sarif }

Inputs: targets, fail-on, config, sarif, json, job-summary (Markdown report in the job summary, default true), args (extra CLI flags). Outputs: markdown-file, exit-code.

Requirements

Node.js 18 or newer. No runtime dependencies.

Support

If this project is useful to you, you can support its development with a donation: Buy me a coffee. It helps keep these tools maintained, documented and free for everyone.

Credit (optional)

Free to use under MIT, no attribution required. If dockerfile-sentinel helps you, a link back is appreciated:

<a href="https://louiswright.co.uk">Built with dockerfile-sentinel by Louis Wright</a>
[![Built with dockerfile-sentinel by Louis Wright](https://img.shields.io/badge/built%20with-dockerfile--sentinel-blue)](https://louiswright.co.uk)

RUN shell checks, BuildKit, CIS and hadolint compatibility

RUN commands, including heredoc bodies, get ShellCheck/hadolint-style checks: pipes without pipefail, ; chains, cd instead of WORKDIR, apt-get install without -y / --no-install-recommends / version pins, apt-get update on its own, pip install without --no-cache-dir or pins, npm install instead of npm ci, and unquoted variables in rm -rf. Each rule lists its hadolint code (RULES[id].hadolint).

BuildKit-aware: RUN --mount=type=secret is the recommended fix for secrets, and copying the mounted secret into the image is flagged (secret-mount-persisted). Using --mount or heredocs without a # syntax=docker/dockerfile:1 line is reported as info.

Findings carry the matching CIS Docker Benchmark recommendation ("cis": "4.1" in JSON, CIS-Docker-4.1 tags in SARIF).

Existing hadolint comments keep working:

# hadolint ignore=DL3008,DL3015
RUN apt-get update && apt-get install -y curl
# dockerfile-sentinel ignore=npm-install-not-ci
RUN npm install

# hadolint global ignore=DL3006 (or # dockerfile-sentinel global ignore=<rule>) applies to the whole file. Kubernetes and Helm manifests are out of scope.

Hardcoded credentials are detected with the shared provider patterns from bundle-secret-guard.

Module formats and types

Ships ESM (import) and a bundled CommonJS build (require), with TypeScript declarations generated from the source:

import * as lib from 'dockerfile-sentinel';      // ESM
const lib = require('dockerfile-sentinel');      // CommonJS

Tests live in test/ and run on Node 18, 20 and 22 in GitHub Actions, including a require+import check. Releases are published with npm publish --provenance.

License

MIT © Louis Wright (Louis Wright)