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:latest

Until then, build from source:

git clone https://github.com/szaranger/security.git
cd security
pnpm docker:build
# image tag: security-scanner:local

Set 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 /repo

Scan with CI exit code (fail on high+)

docker run --rm -v "$(pwd):/repo:ro" $IMAGE \
  scan /repo --format json --fail-on high

Exit code 1 when findings meet the threshold; 0 otherwise.


What you get

ScannerFinds
TrivyDependency CVEs, misconfigurations in manifests/lockfiles
SemgrepCode patterns, insecure APIs, SAST-style issues
SyftSoftware bill of materials (CycloneDX JSON artifact)
GitleaksHardcoded secrets, API keys, tokens
CheckovTerraform, Kubernetes, Dockerfile, and other IaC misconfigurations

Output formats:

  • terminal (default) — human-readable summary + optional AI remediation
  • json — 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]
FlagDefaultDescription
path.Directory inside the container (usually /repo)
--formatterminalterminal, json, or sarif
--scannersall registered scannersComma-separated list or all
--changed-onlyoffLimit findings to files changed vs --base-ref
--base-refmainBase git ref for --changed-only
--fail-onnoneExit 1 if findings at or above this severity (overridden by .scantis/policy.yml)
--target-urlRequired for ZAP. DAST target URL (opt-in; authorized targets only)
--kubeconfigKubeconfig path for Kubescape cluster scans
--aws-profileAWS 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 trivy

JSON to a file:

docker run --rm -v "$(pwd):/repo:ro" $IMAGE \
  scan /repo --format json > scan-results.json

All scanners:

docker run --rm -v "$(pwd):/repo:ro" $IMAGE scan /repo --scanners all

SBOM 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 json

DAST (requires Docker for ZAP fallback; authorized URLs only):

docker run --rm --network host $IMAGE scan /repo --scanners zap --target-url https://staging.example.com

Kubernetes manifests (no cluster required):

docker run --rm -v "$(pwd):/repo:ro" $IMAGE scan /repo/k8s --scanners kubescape

AWS posture (read-only credentials via env):

docker run --rm -e AWS_PROFILE=readonly -v "$(pwd):/repo:ro" $IMAGE scan /repo --scanners prowler

AI remediation summary (optional):

docker run --rm -v "$(pwd):/repo:ro" -e OPENAI_API_KEY=sk-... $IMAGE scan /repo

SARIF for GitHub Advanced Security / IDE integration:

docker run --rm -v "$(pwd):/repo:ro" $IMAGE \
  scan /repo --format sarif > results.sarif

Incremental PR-style scan (changed files only):

pnpm security scan . --changed-only --base-ref main

Policy-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 high

Save 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.json

GitLab 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 high

Generic (any CI with Docker)

  1. Check out the repo.
  2. Mount the workspace at /repo:ro.
  3. Run scan /repo --format json --fail-on high.
  4. Use the exit code for pass/fail.

Requirements

RequirementNotes
DockerOnly dependency for users
Network (first run)Scanners download vulnerability rules and configs on first use
Volume mountTarget 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

IssueFix
Path does not existMount the repo at /repo and pass scan /repo
Slow first scanNormal — scanners download DBs; cache Docker layers in CI if possible
Permission denied on mountEnsure the path exists and Docker can read it
Empty findings on known-vulnerable projectTry --scanners all; check mount path
CI fails unexpectedlyConfirm --fail-on threshold; inspect JSON output

Platform (optional)

Need scan history, a dashboard, or GitHub PR comments? That’s a separate hosted platform tier:

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.0

See .github/workflows/docker-publish.yml.