Multi-architecture container images for security tooling, published to GitHub Container Registry.
Many good security tools ship no container image, ship an amd64-only image, or
ship one that has gone stale. This repository fills that gap: it builds them on
a pinned schedule, for both linux/amd64 and linux/arm64, with signed build
provenance and a vulnerability scan on every publish.
docker pull ghcr.io/matusso/wafw00f:latest
docker run --rm ghcr.io/matusso/wafw00f:latest https://example.comEvery image resolves to the right architecture automatically — the tag is a
multi-arch manifest, so the same command works on an Apple Silicon laptop and an
x86_64 server.
More examples:
# Fingerprint a WAF
docker run --rm ghcr.io/matusso/wafw00f:v2.4.2 https://example.com
# Brute-force web paths
docker run --rm ghcr.io/matusso/dirsearch:v0.5.0 -u https://example.com
# Run PocketBase, persisting data to a named volume
docker run --rm -p 8080:8080 -v pb_data:/pb/pb_data ghcr.io/matusso/pocketbase:latest
# Analyse firmware in the current directory
docker run --rm -v "$PWD:/work" ghcr.io/matusso/binwalk:latest /work/firmware.bin
# Sweep a subnet for web ports (masscan scans from a raw socket, so it needs the host network)
docker run --rm --network host ghcr.io/matusso/masscan:latest -p80,443 192.0.2.0/24 --rate 1000
# MVT exposes two CLIs, so the image starts a shell
docker run --rm -it ghcr.io/matusso/mvt:latestAll images are published as ghcr.io/matusso/<image> for linux/amd64 and
linux/arm64.
| Image | Version | Upstream | Built from |
|---|---|---|---|
binwalk |
v3.1.0 |
ReFirmLabs/binwalk | upstream Dockerfile |
dirsearch |
v0.5.0 |
maurosoria/dirsearch | upstream Dockerfile |
ghauri |
1.4.3 |
r0oth3x49/ghauri | files/ghauri |
kiterunner |
v1.0.2 |
assetnote/kiterunner | files/kiterunner |
masscan |
1.3.2 |
robertdavidgraham/masscan | files/masscan |
metasploit-framework |
6.5.5 |
rapid7/metasploit-framework | upstream Dockerfile |
mvt |
v2026.9.7 |
mvt-project/mvt | files/mvt |
pocketbase |
v0.40.4 |
pocketbase/pocketbase | files/pocketbase |
raptor |
sha-<commit> |
gadievron/raptor | upstream devcontainer Dockerfile |
routersploit |
v3.4.7 |
threat9/routersploit | files/routersploit |
wafw00f |
v2.4.2 |
EnableSecurity/wafw00f | files/wafw00f |
pocketbaselistens on8080, stores data in/pb/pb_data, and defines aHEALTHCHECKagainst/api/health. Mount a volume at/pb/pb_datato persist data.mvtinstalls two entrypoints,mvt-iosandmvt-android, so the image defaults to a shell rather than choosing one for you.masscanscans from a raw socket. The binary carries thecap_net_rawfile capability, which Docker grants by default, so no extra flags are needed — but it has to see a real network, so run it with--network host. Conversely--cap-drop=ALLstops the image starting at all: a file capability the container cannot grant makesexecvefail.routersploitstarts the interactiversf.pyconsole; run it with-it.raptoris a development-environment image, not a minimal runtime. It tracks upstream's default branch rather than a release, so it is taggedsha-<commit>pluslatest. Onarm64the bundled CodeQL CLI is replaced by a stub that explains why — GitHub publishes CodeQL CLI binaries forlinux64only.metasploit-frameworkuses upstream's entrypoint, which provisions a matching host user and expects a database. Follow upstream's Docker instructions.
Not an image — a GitHub Release. Caddy
v2.11.4 built with github.com/caddy-dns/cloudflare compiled in, for
linux-amd64, linux-arm64 and linux-armv7.
gh release download caddy-v2.11.4 --repo matusso/docker-builds --pattern '*linux-amd64*'
sha256sum -c SHA256SUMS --ignore-missingEvery published manifest carries a signed build provenance attestation tying it to the exact workflow run and commit that produced it.
gh attestation verify oci://ghcr.io/matusso/wafw00f:latest --owner matussoTo pin an exact image rather than a moving tag, resolve the digest once and use it everywhere:
docker buildx imagetools inspect ghcr.io/matusso/wafw00f:v2.4.2 --format '{{.Manifest.Digest}}'
docker run --rm ghcr.io/matusso/wafw00f@sha256:<digest> --help| Tag | Meaning |
|---|---|
<version> |
The upstream release this image packages, for example v2.4.2. Immutable in practice — a rebuild of the same upstream version overwrites it only if the build inputs changed. |
latest |
The most recent build of the newest pinned version. |
sha-<commit> |
Used only by raptor, which tracks a branch rather than releases. |
There are no per-architecture tags. Each architecture is built on a native
runner and pushed by digest; only the multi-arch manifest gets tagged. This
replaced an earlier scheme that published -amd64 and -arm64 tags as build
intermediates.
Every image we build ourselves runs as uid/gid 10001 and works in /work
(/pb for PocketBase, /data for kiterunner). That is the right default, but it
means writes to a bind-mounted host directory fail unless the host directory is
writable by that uid.
Two ways to handle it:
# Match the container user to yours
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" ghcr.io/matusso/ghauri:latest ...
# Or make the mount writable by uid 10001
mkdir -p out && chown 10001:10001 out
docker run --rm -v "$PWD/out:/work" ghcr.io/matusso/ghauri:latest ...Images built from an upstream Dockerfile use whatever user upstream chose.
One reusable workflow, _build-image.yml,
does the work. Each tool has a small caller workflow that supplies its name,
version, build context and smoke test.
flowchart LR
R[resolve<br/>map platforms to<br/>native runners] --> A[build amd64<br/>ubuntu-latest]
R --> B[build arm64<br/>ubuntu-24.04-arm]
A -->|push by digest| M[merge<br/>manifest list<br/>+ provenance]
B -->|push by digest| M
M --> S[scan<br/>Trivy → SARIF]
- Native runners, no emulation.
amd64builds onubuntu-latest,arm64onubuntu-24.04-arm. Nothing runs under QEMU. - Smoke tested per architecture. Each build runs the image's entrypoint and
asserts on the result before the manifest is published. A broken
arm64build cannot reachlatest. - Pull requests build but never push. A PR gets the full two-architecture build and both smoke tests with publishing disabled.
- Scanned on publish. Trivy results go to GitHub code scanning. Findings are reported rather than enforced; see SECURITY.md.
A caller workflow in full:
jobs:
image:
uses: ./.github/workflows/_build-image.yml
permissions:
contents: read
packages: write
security-events: write
id-token: write
attestations: write
with:
image: wafw00f
version: v2.4.2
context: files/wafw00f
description: Web application firewall fingerprinting toolkit
licenses: BSD-3-Clause
push: ${{ github.event_name != 'pull_request' }}
smoke-command: docker run --rm "$IMAGE" --help | grep -qi 'usage'.github/workflows/
_build-image.yml Reusable build → merge → scan pipeline
_sonar-scan.yml Reusable SonarCloud scan of an upstream source tree
ci.yml Linters, Trivy config scan, and the test suite
<tool>.yml One thin caller per published image
files/<image>/
Dockerfile Images we build ourselves, all on one template
scripts/
catalog.py Reads the repo's declared state; defines "consistent"
check_catalog.py Dependency-free CLI over the above
patch_raptor_dockerfile.py Declarative CI patches for RAPTOR's Dockerfile
tests/ Conventions, supply-chain rules, and catalog consistency
Two layers, deliberately split:
| Layer | What it proves | Where |
|---|---|---|
pytest |
The repository's declared state is coherent — Dockerfiles follow one template, workflows keep their supply-chain guarantees, the RAPTOR patcher behaves, and the README catalog matches every workflow pin | ci.yml, every push and pull request |
| Smoke tests | The built image actually runs, on each architecture separately | _build-image.yml, before any manifest is published |
The first layer exists because "all images follow the same format" and "the docs
match reality" are only true while something checks them. Run it with
pytest -rs; see CONTRIBUTING.md.
Bumping a version is a two-line change — the workflow's version: input and the
catalog table above. Dependabot handles base images and action versions weekly.
See CONTRIBUTING.md for the Dockerfile conventions, how to add a new image, and the local lint commands.
Supply-chain posture, scanning policy and how to report a vulnerability are in SECURITY.md.
These images package penetration testing and forensics tooling. Use them only against systems you are authorised to test.
Two separate things, and conflating them is the usual mistake:
This repository — the workflows, Dockerfiles, scripts and tests — is Apache-2.0. All of it is original work; no upstream source is vendored here.
The published images are a different matter. Each one contains a third-party tool under that tool's own license, and those terms travel with the image regardless of this repository's license. If you redistribute an image, you take on its upstream obligations — three of them are copyleft:
| Image | Upstream license |
|---|---|
kiterunner, masscan |
AGPL-3.0-only |
dirsearch |
GPL-2.0-or-later |
mvt |
MVT License 1.1 — Mozilla-derived, not OSI-approved, and it restricts use |
metasploit-framework, routersploit, wafw00f |
BSD-3-Clause |
binwalk, ghauri, pocketbase, raptor |
MIT |
caddy (binary release) |
Apache-2.0 |
One extra caveat for raptor: on linux/amd64 the image bundles GitHub's
CodeQL CLI, which is not covered by RAPTOR's MIT licence and is distributed
under GitHub's own terms, which
restrict what you may use it for. Upstream flags this too. The arm64 image
contains no CodeQL binary at all, so the restriction does not apply there.
Every image records this in its org.opencontainers.image.licenses label, so you
can check what you have without consulting this table:
docker buildx imagetools inspect ghcr.io/matusso/kiterunner:latest \
--format '{{json .Image}}' | jq -r '.config.Labels["org.opencontainers.image.licenses"]'