A Node.js CLI tool that scans Docker images for hardcoded secrets, API keys, private keys, tokens, and sensitive files. It inspects the full image — layer filesystems, Dockerfile build history, and image config metadata — to catch secrets that other tools miss.
- 66 detection rules — AWS, GCP, Azure, Stripe, GitHub, GitLab, Slack, OpenAI, Anthropic, HuggingFace, and 50+ more
- 3-layer deep scanning — scans layer filesystems,
docker history(ENV/ARG/RUN instructions), and image config (environment variables & labels) - Streaming output — results render as each image completes, not after all finish
- Parallel scanning — scan multiple images concurrently with configurable concurrency
- Disk-safe for large batches —
--cleanupremoves each image after scanning so you never run out of storage - Private registry support — authenticate with Docker Hub, GHCR, ECR, or any registry via token
- Low false positives — 18 allow rules skip known-noisy paths (vendor dirs, runtime libs, docs, test fixtures) plus placeholder detection
- Shannon entropy detection — catches high-entropy strings that don't match known patterns
- Rich terminal output — colored severity tables, spinners with progress, and summary stats
- Config file support — define scan targets in YAML or JSON
- Secret redaction — output shows first 4 characters +
****to avoid leaking secrets in logs
- Node.js >= 18
- Docker — daemon must be running
git clone <repo-url>
cd security-scanner
npm install
npm link # makes 'scanner' available globallyscanner scan nginx:latest myapp:v1 python:3.12-slimscanner scan myapp:latest \
--severity HIGH \
--format table \
--max-file-size 5 \
--concurrency 4# Via CLI flags
scanner scan mycompany/private-app:latest \
--registry-user myuser \
--registry-token dckr_pat_xxxxxxxxxxxxx
# Via environment variables
export DOCKER_USER=myuser
export DOCKER_TOKEN=dckr_pat_xxxxxxxxxxxxx
scanner scan mycompany/private-app:latest
# Via config file
scanner scan-config scan-config.yamlscanner scan ghcr.io/myorg/myapp:latest \
--registry ghcr.io \
--registry-user myuser \
--registry-token ghp_xxxxxxxxxxxxxWhen scanning many images (GBs/TBs), use --cleanup to remove each image from disk after scanning and redirect output to a file:
# Table output — stream results to a log file
nohup scanner scan img1:latest img2:latest img3:latest ... \
--cleanup \
--concurrency 2 \
--severity MEDIUM \
> scan-results.log 2>&1 &
# JSON output — one JSON object per image (JSONL format), ideal for pipelines
nohup scanner scan img1:latest img2:latest img3:latest ... \
--cleanup \
--format json \
> scan-results.jsonl 2>&1 &
# Markdown output — clean tables that render in GitHub, Notion, wikis, etc.
nohup scanner scan img1:latest img2:latest img3:latest ... \
--cleanup \
--format markdown \
> scan-report.md 2>&1 &
# Monitor progress
tail -f scan-results.logWith --cleanup enabled, only --concurrency images (default 3) exist on disk at any given time. The pipeline per image is:
pull → scan → output results → docker rmi → next image
You can also define all images in a config file:
# large-scan.yaml
images:
- mycompany/service-a:latest
- mycompany/service-b:latest
- mycompany/service-c:latest
# ... hundreds of images
cleanup: true
concurrency: 2
severity: MEDIUMnohup scanner scan-config large-scan.yaml > results.log 2>&1 &scanner scan-config scan-config.example.yaml| Flag | Description | Default |
|---|---|---|
-s, --severity <level> |
Minimum severity: LOW, MEDIUM, HIGH |
LOW |
-f, --format <type> |
Output format: table, json, markdown |
table |
--max-file-size <mb> |
Skip files larger than this (MB) | 10 |
-c, --concurrency <n> |
Max parallel image scans | 3 |
--cleanup |
Remove images from disk after scanning | off |
--registry <url> |
Registry server URL | Docker Hub |
--registry-user <user> |
Registry username | env DOCKER_USER |
--registry-token <token> |
Registry token/password | env DOCKER_TOKEN |
| Code | Meaning |
|---|---|
0 |
No secrets found |
1 |
Secrets detected |
2 |
Runtime error (Docker not available, invalid image, etc.) |
Results stream to the terminal as each image finishes scanning:
Docker Secret Scanner v1.0.0
── myapp:latest ──────────────────────────────────────────────
┌──────────┬──────────────────────┬────────────────────────────┬──────┬──────────────────┐
│ Severity │ Rule │ File │ Line │ Match │
├──────────┼──────────────────────┼────────────────────────────┼──────┼──────────────────┤
│ HIGH │ AWS Access Key ID │ /app/config.py │ 12 │ AKIA**** │
│ HIGH │ OpenAI Project Key │ [Image Config] ENV │ - │ sk-p**** │
│ │ │ OPENAI_API_KEY │ │ │
│ HIGH │ Stripe Secret Key │ [Dockerfile] Layer 3 │ - │ sk_l**** │
│ MEDIUM │ Database Connection │ /app/.env │ 3 │ post**** │
│ │ String │ │ │ │
│ MEDIUM │ Hardcoded Password │ [Dockerfile] Layer 5 │ - │ MyS3**** │
└──────────┴──────────────────────┴────────────────────────────┴──────┴──────────────────┘
⠋ Scanning... (1/3 done)
── nginx:latest ──────────────────────────────────────────────
No secrets found.
⠋ Scanning... (2/3 done)
── redis:latest ──────────────────────────────────────────────
No secrets found.
────────────────────────────────────────────────────────────
Scanned 3 images
Summary: Found 5 secrets in 1 image (3 HIGH, 2 MEDIUM, 0 LOW)
With --format json, each image outputs one JSON object per line (JSONL), making it easy to process with jq:
# Find all HIGH severity findings
scanner scan myapp:latest --format json | jq '.findings[] | select(.severity == "HIGH")'With --format markdown, output is clean markdown that renders in GitHub, Notion, or any wiki. Ideal for logging to a file:
scanner scan myapp:latest nginx:latest --format markdown > report.mdProduces a file like:
# Docker Secret Scanner v1.0.0
## myapp:latest
| Severity | Rule | File | Line | Match |
|----------|------|------|------|-------|
| **HIGH** | Stripe Secret Key | [Dockerfile] Layer 2 | - | `sk_l****` |
| **HIGH** | OpenAI Project Key | [Image Config] ENV OPENAI_API_KEY | - | `sk-p****` |
| *MEDIUM* | Hardcoded Password | /app/config.py | 5 | `MyS3****` |
## nginx:latest
No secrets found.
---
*Scanned 2 images*
**Summary:** Found **3** secrets in 1 image (**2** HIGH, **1** MEDIUM, **0** LOW)Extracts the image via docker save and streams through each layer tar, scanning every text file for secrets using regex pattern matching and entropy analysis.
Runs docker history --no-trunc to inspect the full build instructions. Catches secrets passed via ENV, ARG, or embedded in RUN commands — even if the files were deleted in later layers.
Runs docker inspect to read environment variables and labels baked into the image config. Secrets set via ENV in a Dockerfile persist here permanently.
| Category | Rules |
|---|---|
| Cloud Providers | AWS Access Key, AWS Secret Key, GCP Service Account, Azure Storage Key, Alibaba Access Key |
| LLM Providers | OpenAI API Key, OpenAI Project Key, Anthropic API Key, Google AI (Gemini) Key, Cohere API Key, HuggingFace Token, Replicate Token |
| Git Platforms | GitHub PAT, GitHub OAuth, GitHub App Token, GitHub Refresh Token, GitHub Fine-Grained PAT, GitLab PAT |
| Payment / SaaS | Stripe Secret Key, Shopify Token, SendGrid Key |
| Communication | Slack Token |
| Package Registries | NPM Access Token, PyPI Upload Token |
| CI/CD | Heroku API Key, Pulumi API Token |
| Infrastructure | Docker Config Auth, Atlassian API Token, Dropbox API Token |
| Crypto | Private Key (PEM) |
| Generic | Generic API Key Assignment |
| Sensitive Files | SSH Private Key, .env File, PEM File, Docker Config, .npmrc, .pypirc |
Database Connection String, Hardcoded Password, JWT Token, Bearer Token, Twilio Key, Mailchimp Key, Mailgun Key, Age Secret Key, Slack Webhook, Discord Token, HashiCorp Terraform Token, Databricks Token, Doppler Token, PlanetScale Password/Token, Grafana Token, New Relic Keys, Dynatrace Token, Linear Token, Postman Token, Mapbox Token, Fastly Token, RubyGems Token, Generic Secret Assignment
Stripe Publishable Key, Facebook Token, Twitter/X Token, High-Entropy String (Shannon entropy > 4.5)
Files in these paths are automatically skipped:
| Rule | Skipped Path |
|---|---|
| Python dist-info | .dist-info/ |
| Test files | test/, __tests__/, spec/, fixtures/ |
| Examples | examples/ |
| Vendor | /vendor/ |
| System dirs | /usr/share/, /usr/include/, /usr/lib/ |
| Locales | /locale/, /locales/ |
| Markdown | *.md |
| Node runtime | /opt/yarn-v*/ |
| Go runtime | /usr/local/go/ |
| Python runtime | /usr/local/lib/python*/ |
| Ruby runtime | /usr/lib/gems/ |
| WordPress | /usr/src/wordpress/ |
| Anaconda logs | /var/log/anaconda/ |
| Docs | docs/ |
| Changelogs | CHANGELOG, CHANGES, HISTORY |
| Licenses | LICENSE, COPYING, NOTICE |
| Man pages | /man/man*/ |
| Go modules | /go/pkg/mod/ |
Additionally, binary files (images, archives, compiled objects) and directories like node_modules, .git, and __pycache__ are always skipped.
# scan-config.yaml
images:
- nginx:latest
- myapp:v1
- node:18-alpine
severity: LOW # LOW | MEDIUM | HIGH
format: table # table | json
maxFileSize: 10 # Max file size in MB
concurrency: 3 # Max parallel scans
cleanup: false # Remove images after scanning
# Registry authentication (for private images)
# Can also use env vars: DOCKER_REGISTRY, DOCKER_USER, DOCKER_TOKEN
# registry: https://index.docker.io/v1/
# registryUser: myuser
# registryToken: dckr_pat_xxxxxxxxxxxxxBoth YAML and JSON config files are supported. CLI flags override config file values.
security-scanner/
├── package.json
├── bin/
│ └── cli.js # CLI entry point (Commander.js)
├── src/
│ ├── scanner.js # Parallel image scanning orchestration
│ ├── docker.js # docker save/history/inspect, tar extraction
│ ├── detector.js # Secret detection engine (regex + entropy)
│ ├── rules.js # 66 detection rules
│ ├── output.js # Colored table output (chalk, cli-table3, ora)
│ ├── config.js # YAML/JSON config file parsing
│ └── utils.js # Binary detection, allow rules, entropy, helpers
└── scan-config.example.yaml
┌─────────────────────┐
│ scanner scan img │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ Registry Login │ (if --registry-token)
└──────────┬──────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────────┐ ┌───────────┐ ┌──────────────┐
│ docker │ │ docker │ │ docker save │
│ history │ │ inspect │ │ + tar extract│
│ --no-trunc │ │ .Config │ │ per layer │
└──────┬───────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────┐
│ Detection Engine (detector.js) │
│ 66 regex rules + entropy + allow rules │
└──────────────────┬───────────────────────┘
│
▼
┌─────────────────────┐
│ Stream results to │
│ terminal / file │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ docker rmi │ (if --cleanup)
└──────────┬──────────┘
│
▼
Next image...
MIT