Custom Security Toolbox Container for Post-Build Tests
Purpose: show how a team can package several security tools into one reproducible container image and run it as a post-build job in CI/CD. This is not a replacement for a full platform. It is a practical demonstration of how teams standardize scanning, reduce runner drift, and make interview or workshop exercises feel closer to real engineering work.
Why teams do this
A small all-in-one toolbox image helps when you want to:
- keep scanner versions consistent across repositories;
- avoid hand-installing tools on ephemeral runners;
- run the same checks locally and in CI;
- attach one signed build artifact to a release gate;
- prove what exact toolchain produced a finding.
A good "mini pack" composition
This example bundles 7 tools with strong day-to-day value:
- Gitleaks - secret detection in Git history and working tree.
- Trivy - container, filesystem, secret, and IaC scanning. It can scan Dockerfiles and configuration files and supports vulnerability, misconfiguration, and secret scanners.
- Hadolint - Dockerfile linter with best-practice checks.
- Semgrep CE - code-pattern and security-rule scanning across many languages. The Community Edition docs describe it as an open source SAST tool.
- Checkov - IaC policy checks for Terraform, Kubernetes manifests, and cloud configs.
- Syft - SBOM generation.
- Grype - vulnerability matching against packages and SBOMs.
That mix covers the most common post-build needs:
- first-party code smells and security bugs;
- secrets in code and config;
- Dockerfile and image posture;
- IaC misconfigurations;
- SBOM and vulnerable package visibility.
How to think about where it fits
Use this image after build, before promotion:
source -> build -> unit/integration tests -> security-toolbox job -> gate aggregation -> sign/publish -> deploy approval
The toolbox image should not hold production credentials. It should read the built repo, image, or manifest set and emit machine-readable artifacts such as SARIF, JSON, CycloneDX, or plain reports for later aggregation.
Example Dockerfile
See the companion snippet: All-in-One Security Toolbox Dockerfile.
Key design choices:
- non-root runtime user;
- pinned versions through build args;
- single wrapper entrypoint so every repository uses the same invocation pattern;
- no secrets baked into layers;
- cache-clean build to keep the image smaller and easier to reproduce.
Example entrypoint behavior
A simple entrypoint can run a default pack like this:
#!/usr/bin/env bash
set -euo pipefail
TARGET_DIR="${1:-/workspace}"
mkdir -p /out
# code
semgrep scan --config auto --json --output /out/semgrep.json "$TARGET_DIR" || true
# secrets
if git -C "$TARGET_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
gitleaks git --source "$TARGET_DIR" --report-format json --report-path /out/gitleaks.json || true
else
gitleaks dir --source "$TARGET_DIR" --report-format json --report-path /out/gitleaks.json || true
fi
# docker / image / config
hadolint "$TARGET_DIR"/Dockerfile -f json > /out/hadolint.json || true
trivy fs --scanners vuln,misconfig,secret --format json --output /out/trivy-fs.json "$TARGET_DIR" || true
checkov -d "$TARGET_DIR" -o json > /out/checkov.json || true
# sbom + package vulns
syft "$TARGET_DIR" -o cyclonedx-json=/out/sbom.cdx.json || true
grype sbom:/out/sbom.cdx.json -o json > /out/grype.json || true
Example CI usage
GitLab CI
security_toolbox_scan:
stage: security
image: registry.example.com/product-security/security-toolbox:2026-03
script:
- /usr/local/bin/security-toolbox /builds/$CI_PROJECT_PATH
artifacts:
when: always
paths:
- out/
GitHub Actions
jobs:
security-toolbox:
runs-on: ubuntu-latest
container:
image: ghcr.io/example-org/security-toolbox:2026-03
steps:
- uses: actions/checkout@v4
- run: /usr/local/bin/security-toolbox "$GITHUB_WORKSPACE"
- uses: actions/upload-artifact@v4
with:
name: security-toolbox-out
path: out/
What interviewers like to hear about this design
A strong DevSecOps answer is not "I would install seven scanners." It is:
"I would package the scanner chain into a reproducible, signed container so the runner does not need mutable snowflake setup. The job should run with no production secrets, emit versioned artifacts, and feed a gate aggregator. That gives us consistency, provenance, easier local reproduction, and cleaner rollback when a scanner update causes noise."
Operational cautions
1. Avoid giant, slow, everything-everywhere scans
Do not block every commit on every scanner if the runtime cost becomes unreasonable. Common mature patterns are:
- fast subset on pull requests;
- deeper scan on default branch or nightly;
- exception path for noisy checks with expiry and owner.
2. Separate detection from decision
The toolbox should produce findings. A separate gate step should decide whether to fail the pipeline based on severity, reachability, diff awareness, or repository policy.
3. Keep tool updates controlled
Scanner updates can cause finding spikes. Treat the toolbox image like product code:
- changelog;
- canary repositories;
- rollback tag;
- signed image and provenance;
- compatibility tests against known-good fixtures.
4. Do not put cloud deploy credentials in the scan image
Build/test scanners and deployment credentials should be separated. Otherwise a toolchain compromise turns into a release-plane compromise.
5. Archive outputs in standard formats
Prefer JSON, SARIF, and SBOM formats that can be reprocessed later by dashboards, defect trackers, or audit evidence workflows.
Quick review checklist for this pattern
- Is the toolbox image pinned and versioned?
- Does it run as non-root?
- Is it free of embedded secrets?
- Are the tools scoped to post-build analysis, not deployment privileges?
- Are outputs machine-readable and archived?
- Is there a separate aggregation/gating step?
- Are scanner updates treated like controlled change?
References
- Trivy docs:
https://trivy.dev/docs/latest/ - Semgrep docs:
https://semgrep.dev/docs/ - Hadolint project:
https://github.com/hadolint/hadolint - Checkov docs:
https://www.checkov.io/ - Syft project:
https://github.com/anchore/syft - Grype project:
https://github.com/anchore/grype - Gitleaks project:
https://github.com/gitleaks/gitleaks