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 ↗
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
| Practice | What it finds | Scanner | Repo |
|---|---|---|---|
| SBOM: software bill of materials | An inventory of every package in an image or directory | Syft | software-bill-of-materials |
| SCA: software composition analysis | Known vulnerabilities (CVEs) in your dependencies | Trivy | software-composition-analysis |
| Secrets | API keys, tokens and passwords committed to git, including in old commits | Gitleaks | secret-exposure-analysis |
| SAST: static analysis | Insecure code patterns: injection, XSS, weak crypto and more | Semgrep with GitLab's SAST rules | static-application-security-testing |
| CSPM: cloud posture | Misconfigurations and compliance gaps in AWS, Azure and GCP | Prowler | cloud-security-posture-management |
| DAST: dynamic testing | Vulnerabilities in a running web app, found by crawling and attacking it | OWASP ZAP | dynamic-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-rulesrepository 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:stableand 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:
| Flag | Meaning |
|---|---|
-t, --target | What to scan: a path, image, URL or credentials file, depending on the tool |
-ov, --output-via | file (webhook output isn't implemented yet, and -ov webhook exits with a clear message) |
-o, --output | Where to write the JSON report inside the container, usually under /output |
-tt, --target-type | SCA only: image, filesystem, repository and more |
-cp, --cloud-provider | CSPM only: aws, azure or gcp |
-l, --log | Log 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 webhookstops right away with a clear message instead of running the scan first. Adding it is onerequests.post(webhook, json=...)in eachmain.py, plus lifting that check incore/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 projectSentinel Sidecar: a learning security layer for any HTTP service
A security sidecar you put in front of any HTTP service. OWASP rules and small ML models decide each request in about a millisecond, and an out-of-band LLM analyst turns what it learns into short-lived blocks.
Streamlit Portfolio Template
A portfolio and resume website written entirely in Python. Edit one data file, run one command, and you have a dark-themed, animated personal site with a working contact form.
Botly: a local RAG chatbot
A private chatbot that answers from your own PDFs. It runs entirely on your machine with Ollama, LangChain, FAISS and Streamlit, packaged in one Docker image.
CVE Trove: a vulnerability intelligence pipeline
Pulls 15 public vulnerability feeds on a schedule, merges them into one record per CVE, and streams the result into MongoDB through Celery workers.