Scan product guide
Core + opt-in scanners, SARIF, policies, and CI examples
Scan any repository for vulnerabilities with one Docker image. No account, database, or platform required.
Trivy (dependencies/CVEs) + Semgrep (code/SAST) + Syft (SBOM) + Gitleaks (secrets) + Checkov (IaC), optional AI remediation summary, CI-friendly JSON output.
Quick start
Pull the image
After a release is published:
docker pull ghcr.io/szaranger/security-scanner:latestUntil then, build from source:
git clone https://github.com/szaranger/security.git
cd security
pnpm docker:build
# image tag: security-scanner:localSet IMAGE=ghcr.io/szaranger/security-scanner:latest or IMAGE=security-scanner:local below.
Scan the current directory
docker run --rm -v "$(pwd):/repo:ro" $IMAGE scan /repoScan with CI exit code (fail on high+)
docker run --rm -v "$(pwd):/repo:ro" $IMAGE \
scan /repo --format json --fail-on highExit code 1 when findings meet the threshold; 0 otherwise.
What you get
| Scanner | Finds |
|---|---|
| Trivy | Dependency CVEs, misconfigurations in manifests/lockfiles |
| Semgrep | Code patterns, insecure APIs, SAST-style issues |
| Syft | Software bill of materials (CycloneDX JSON artifact) |
| Gitleaks | Hardcoded secrets, API keys, tokens |
| Checkov | Terraform, Kubernetes, Dockerfile, and other IaC misconfigurations |
Output formats:
terminal(default) — human-readable summary + optional AI remediationjson— structured results for CI dashboards or parsers
CLI flags
scan [path] [--format terminal|json|sarif] [--scanners trivy,semgrep,syft,gitleaks,checkov,zap,kubescape,prowler|all] [--target-url URL] [--kubeconfig path] [--aws-profile name] [--changed-only] [--base-ref main] [--fail-on critical|high|medium|low|none]| Flag | Default | Description |
|---|---|---|
path | . | Directory inside the container (usually /repo) |
--format | terminal | terminal, json, or sarif |
--scanners | all registered scanners | Comma-separated list or all |
--changed-only | off | Limit findings to files changed vs --base-ref |
--base-ref | main | Base git ref for --changed-only |
--fail-on | none | Exit 1 if findings at or above this severity (overridden by .scantis/policy.yml) |
--target-url | — | Required for ZAP. DAST target URL (opt-in; authorized targets only) |
--kubeconfig | — | Kubeconfig path for Kubescape cluster scans |
--aws-profile | — | AWS profile for Prowler (credentials never persisted) |
Sprint 7 opt-in scanners: zap (DAST), kubescape (Kubernetes), prowler (AWS). They are registered but excluded from --scanners all (which runs the five core repo scanners). Select them explicitly when needed.
Environment variables: ZAP_BASELINE_TIMEOUT (default 300), KUBESCAPE_FRAMEWORK (default nsa), AWS_PROFILE.
Examples
Trivy only:
docker run --rm -v "$(pwd):/repo:ro" $IMAGE scan /repo --scanners trivyJSON to a file:
docker run --rm -v "$(pwd):/repo:ro" $IMAGE \
scan /repo --format json > scan-results.jsonAll scanners:
docker run --rm -v "$(pwd):/repo:ro" $IMAGE scan /repo --scanners allSBOM only (writes .scantis/sbom.cyclonedx.json inside the scan target — use a read-write mount if you need the file on disk):
docker run --rm -v "$(pwd):/repo" $IMAGE scan /repo --scanners syft --format jsonDAST (requires Docker for ZAP fallback; authorized URLs only):
docker run --rm --network host $IMAGE scan /repo --scanners zap --target-url https://staging.example.comKubernetes manifests (no cluster required):
docker run --rm -v "$(pwd):/repo:ro" $IMAGE scan /repo/k8s --scanners kubescapeAWS posture (read-only credentials via env):
docker run --rm -e AWS_PROFILE=readonly -v "$(pwd):/repo:ro" $IMAGE scan /repo --scanners prowlerAI remediation summary (optional):
docker run --rm -v "$(pwd):/repo:ro" -e OPENAI_API_KEY=sk-... $IMAGE scan /repoSARIF for GitHub Advanced Security / IDE integration:
docker run --rm -v "$(pwd):/repo:ro" $IMAGE \
scan /repo --format sarif > results.sarifIncremental PR-style scan (changed files only):
pnpm security scan . --changed-only --base-ref mainPolicy-as-code
Add .scantis/policy.yml in the repo root to configure fail thresholds, scanner selection, CVE allowlists, and ignored paths. See scantis-policy.example.yml.
CI integration
GitHub Actions
name: Security scan
on:
pull_request:
push:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run security scanner
run: |
docker run --rm -v "${{ github.workspace }}:/repo:ro" \
ghcr.io/szaranger/security-scanner:latest \
scan /repo --format json --fail-on highSave JSON as an artifact:
- name: Run security scanner
run: |
docker run --rm -v "${{ github.workspace }}:/repo:ro" \
ghcr.io/szaranger/security-scanner:latest \
scan /repo --format json --fail-on high > scan-results.json
- uses: actions/upload-artifact@v4
if: always()
with:
name: security-scan
path: scan-results.jsonGitLab CI
security-scan:
image: docker:24
services:
- docker:24-dind
script:
- docker pull ghcr.io/szaranger/security-scanner:latest
- |
docker run --rm -v "${CI_PROJECT_DIR}:/repo:ro" \
ghcr.io/szaranger/security-scanner:latest \
scan /repo --format json --fail-on highGeneric (any CI with Docker)
- Check out the repo.
- Mount the workspace at
/repo:ro. - Run
scan /repo --format json --fail-on high. - Use the exit code for pass/fail.
Requirements
| Requirement | Notes |
|---|---|
| Docker | Only dependency for users |
| Network (first run) | Scanners download vulnerability rules and configs on first use |
| Volume mount | Target code must be mounted read-only into the container |
No Postgres, API keys, or signup.
JSON output shape
With --format json, stdout includes:
{
"result": {
"findings": [...],
"scannedAt": "...",
"scanners": ["trivy", "semgrep", "syft", "gitleaks", "checkov"],
"scannerVersions": { "trivy": "0.58.2" },
"sbom": {
"path": ".scantis/sbom.cyclonedx.json",
"format": "cyclonedx-json",
"componentCount": 42
}
},
"severityCounts": { "critical": 0, "high": 4, "medium": 3, "low": 0, "unknown": 0 },
"summary": {
"summary": "...",
"recommendedRemediation": "...",
"estimatedEffort": "..."
}
}sbom is present when Syft ran. scannerVersions is included when scanners expose a version. summary is omitted when there are no findings or no OPENAI_API_KEY is set.
Future enhancement: run Trivy against the Syft SBOM for dependency CVE coverage from the generated bill of materials (not implemented yet).
Troubleshooting
| Issue | Fix |
|---|---|
Path does not exist | Mount the repo at /repo and pass scan /repo |
| Slow first scan | Normal — scanners download DBs; cache Docker layers in CI if possible |
| Permission denied on mount | Ensure the path exists and Docker can read it |
| Empty findings on known-vulnerable project | Try --scanners all; check mount path |
| CI fails unexpectedly | Confirm --fail-on threshold; inspect JSON output |
Platform (optional)
Need scan history, a dashboard, or GitHub PR comments? That’s a separate hosted platform tier:
- vercel-neon-local-worker.md — dashboard + API on Vercel, Neon Postgres, local/Docker worker
- docker.md — same image also runs
workermode for the platform
Scanner-only users can ignore the platform docs entirely.
Publishing releases
Maintainers tag a release to publish to GitHub Container Registry:
git tag v0.1.0
git push origin v0.1.0See .github/workflows/docker-publish.yml.