all systems operationalIndia

AppSec scanner suite: six scanners, one interface

SAST, DAST, SCA, SBOM, CSPM and secret detection, each wrapping a proven open-source scanner behind the same command line, the same Docker packaging and the same JSON output.

state
healthy
stage
evergreen
status
open source
started
2024.07
source
github ↗
topics

Application security needs several different scanners. One reads your code, one checks your dependencies, one looks for leaked keys, one attacks the running app, one audits your cloud account. Each has its own install steps, flags and output format, which makes them tedious to wire into a pipeline together.

This suite puts six industry-standard open-source scanners behind one interface. Every tool is a small Python package with the same command-line flags, shipped as a Docker image whose entrypoint is the tool itself, and each writes a JSON report to a path you choose. Swapping one scanner for another in a pipeline means changing the image name, not rewriting the step.

The six tools

PracticeWhat it findsScannerRepo
SBOM: software bill of materialsAn inventory of every package in an image or directorySyftsoftware-bill-of-materials
SCA: software composition analysisKnown vulnerabilities (CVEs) in your dependenciesTrivysoftware-composition-analysis
SecretsAPI keys, tokens and passwords committed to git, including in old commitsGitleakssecret-exposure-analysis
SAST: static analysisInsecure code patterns: injection, XSS, weak crypto and moreSemgrep with GitLab's SAST rulesstatic-application-security-testing
CSPM: cloud postureMisconfigurations and compliance gaps in AWS, Azure and GCPProwlercloud-security-posture-management
DAST: dynamic testingVulnerabilities in a running web app, found by crawling and attacking itOWASP ZAPdynamic-application-security-testing

How they're built

All six share one layout, so learning one teaches you all of them:

<tool>/
├── Dockerfile                 # scanner binary + this package; ENTRYPOINT = the tool
├── setup.py                   # installs the `<tool>` command (console_scripts)
└── <tool_package>/
    ├── main.py                # parse args → run the service → write the JSON report
    ├── core/input.py          # the shared CLI: -t / -ov / -o / -w / -l
    ├── core/models.py         # Response(success, data, message, timestamp, ...)
    ├── services/<scanner>.py  # builds the scanner command and runs it
    └── support/enums.py       # command templates, paths, messages

A run always goes the same way:

docker run … <tool> -t <target> -ov file -o /output/results.json
    │
    ▼
main.py ─▶ services/<scanner>.py
              fill the command template  e.g. "trivy {target_type} {target} --format json --output {output}"
              run the scanner, writing JSON to a temp file
              load it into a Response model
    ▼
report written to /output/results.json   (mounted from your machine)

Packaging tricks worth copying

  • Take the scanner from its official image. Instead of install scripts, the Dockerfiles copy the ready-built binary straight out of the vendor's image with a multi-stage COPY --from:

    COPY --from=aquasec/trivy:latest       /usr/local/bin/trivy /usr/local/bin/trivy
    COPY --from=anchore/syft:latest        /syft                /usr/local/bin/syft
    COPY --from=zricethezav/gitleaks:latest /usr/bin/           /usr/local/bin/
    
  • Bake rules in at build time. The SAST image has a first build stage that clones GitLab's sast-rules repository and extracts the Semgrep rules. The final image then scans offline, with a fixed, known rule set:

    FROM python:3.12-alpine AS rules_layer
    RUN apk add --no-cache git
    COPY . .
    RUN pip install -r ./sast_rules_scraper/requirements.txt && python3 ./sast_rules_scraper/main.py
    
    FROM python:3.12-alpine
    COPY --from=rules_layer /usr/src/app/rules /usr/src/app/rules
    
  • Build on the scanner's own image when it's heavyweight. ZAP needs Java and a browser for AJAX crawling, so the DAST image starts FROM ghcr.io/zaproxy/zaproxy:stable and adds the Python package on top.

  • Drive ZAP with a plan, not clicks. The DAST tool generates a ZAP automation framework YAML with six jobs: spider, AJAX spider, wait for passive scanning, passive scan config, active scan (capped at 60 minutes), and report. It then runs zap.sh -cmd -autorun plan.yaml.

Run them

Build any tool from its repo, with the image name matching the repo name:

git clone https://github.com/sumit-kumar-03/software-composition-analysis.git
cd software-composition-analysis
docker build -t software-composition-analysis:latest .

Then run it against a target. Results land in ./output on your machine:

# SBOM: list every package in an image
docker run --rm -v "$PWD/output:/output" software-bill-of-materials:latest \
  -t alpine:latest -ov file -o /output/sbom.json

# SCA: known CVEs in an image (or: -tt filesystem / repository / rootfs / config / kubernetes / sbom / vm)
docker run --rm -v "$PWD/output:/output" software-composition-analysis:latest \
  -tt image -t alpine:latest -ov file -o /output/sca.json

# Secrets: scan a local git repository, including its history
docker run --rm -v "$PWD/output:/output" -v /path/to/repo:/scan secret-exposure-analysis:latest \
  -t /scan -ov file -o /output/secrets.json

# SAST: scan source code
docker run --rm -v "$PWD/output:/output" -v /path/to/code:/scan static-application-security-testing:latest \
  -t /scan -ov file -o /output/sast.json

# CSPM: audit an AWS account (also: -cp azure, -cp gcp)
docker run --rm --dns 8.8.8.8 -v "$PWD/output:/output" -v /path/to/creds:/creds \
  cloud-security-posture-management:latest -t /creds/aws_credentials.json -cp aws -ov file -o /output/cspm.json

# DAST: crawl and attack a running web app. Only scan sites you own or may test.
docker run --rm -v "$PWD/output:/output" dynamic-application-security-testing:latest \
  -t https://your-staging-app.example.com -ov file -o /output/dast.json

The flags are the same everywhere:

FlagMeaning
-t, --targetWhat to scan: a path, image, URL or credentials file, depending on the tool
-ov, --output-viafile (webhook output isn't implemented yet, and -ov webhook exits with a clear message)
-o, --outputWhere to write the JSON report inside the container, usually under /output
-tt, --target-typeSCA only: image, filesystem, repository and more
-cp, --cloud-providerCSPM only: aws, azure or gcp
-l, --logLog level

Where each one fits in a pipeline

commit ──▶ Secrets + SAST ──▶ build image ──▶ SBOM + SCA on the image ──▶ deploy to staging ──▶ DAST
                                                                                    │
                                   cloud account ◀── CSPM on a schedule ────────────┘
  • On every commit (fast): secrets and SAST read only the code.
  • After the image is built: the SBOM records what's inside the image, and SCA checks those packages for known CVEs.
  • Against staging: DAST needs a running app and takes longer, so run it after deploying to a non-production environment.
  • On a schedule: CSPM audits the cloud account itself, independently of any single deploy.

Build your own scanner wrapper

The same recipe works for any command-line scanner. Here it is with Trivy.

1. One CLI for every tool

from argparse import ArgumentParser

def parse_args():
    p = ArgumentParser()
    p.add_argument("-t", "--target", required=True)
    p.add_argument("-ov", "--output-via", choices=["file"], default="file")
    p.add_argument("-o", "--output", required=True)
    return p.parse_args()

2. Keep scanner commands as templates

from enum import Enum

class Command(Enum):
    TRIVY = ["trivy", "image", "{target}", "--scanners", "vuln", "--format", "json", "--output", "{output}"]

3. Run the scanner safely

The repos build a string and call os.system. Passing a list to subprocess.run is safer: a target like x; rm -rf / can't be interpreted by a shell. You also get the exit code and stderr:

import json, subprocess, tempfile
from pydantic import BaseModel

class Response(BaseModel):
    success: bool = False
    data: dict | list | None = None
    message: str | None = None

def run(target: str) -> Response:
    with tempfile.NamedTemporaryFile(suffix=".json") as tmp:
        cmd = [part.format(target=target, output=tmp.name) for part in Command.TRIVY.value]
        proc = subprocess.run(cmd, capture_output=True, text=True, timeout=1800)
        if proc.returncode != 0:
            return Response(message=proc.stderr[-2000:])
        with open(tmp.name) as fp:
            return Response(success=True, data=json.load(fp))

4. Wire it together and install a command

# my_sca/main.py
import json
from my_sca.cli import parse_args
from my_sca.service import run

def main():
    args = parse_args()
    result = run(args.target)
    with open(args.output, "w") as fp:
        json.dump(result.model_dump(), fp, indent=2, default=str)
# setup.py
from setuptools import setup, find_packages

setup(
    name="my-sca",
    packages=find_packages(),
    install_requires=["pydantic>=2"],
    entry_points={"console_scripts": ["my-sca=my_sca.main:main"]},
)

5. Package it with the scanner

FROM python:3.12-alpine
COPY --from=aquasec/trivy:latest /usr/local/bin/trivy /usr/local/bin/trivy
WORKDIR /usr/src/app
COPY . .
RUN pip install --no-cache-dir .
ENTRYPOINT ["my-sca"]

docker run my-sca:latest -t alpine:latest -o /output/r.json now behaves exactly like the suite's tools.

Notes and ideas

  • Webhook output isn't implemented yet. The tools write the report to the file given with -o. Asking for -ov webhook stops right away with a clear message instead of running the scan first. Adding it is one requests.post(webhook, json=...) in each main.py, plus lifting that check in core/input.py.
  • The reports are each scanner's raw JSON. Converting all six to SARIF, the common static-analysis format, would let GitHub code scanning and most dashboards show them side by side.
  • Link SCA findings to CVE Trove. SCA reports contain CVE IDs, and CVE Trove has EPSS scores and "exploited in the wild" flags for them. Joining the two turns "412 vulnerabilities" into "these 9 are being exploited: fix them first".
  • One runner for all six. A small orchestrator that runs the right tools for a target type and merges the reports into one summary.

Connected

shares a topic with this project