Single image
Use a tag or digest when the target scope should stay narrow.
layerleak scan mongo:latest
layerleak scan repo/app@sha256:...
layerleak reads registry manifests, image config, build history, and decompressed filesystem layers directly. It is read-only, runs as a single Go binary, and keeps actionable findings separated from likely test fixtures and example placeholders.
The module root is the supported CLI entrypoint. Install or build from there, then
scan a public image reference. GOBIN or GOPATH/bin must be
on PATH after go install.
go install github.com/brumbelow/layerleak@latest
layerleak --help
layerleak scan library/nginx:latest --format json
Source builds use the same module:
git clone https://github.com/brumbelow/layerleak.git
cd layerleak
go build -o layerleak .
./layerleak scan alpine:latest --platform linux/amd64
To pin a published release, replace @latest with the
v1.x.y tag you want.
layerleak supports public images on OCI-compatible registries including Docker Hub, GHCR, Quay, GCR, MCR, Amazon ECR Public, and self-hosted registries. It does not require a local Docker daemon and does not verify discovered secrets.
Use a tag or digest when the target scope should stay narrow.
layerleak scan mongo:latest
layerleak scan repo/app@sha256:...
Bare repository names enumerate public tags, resolve each tag to a digest, and group duplicate digests.
layerleak scan mongo
layerleak scan quay.io/prometheus/busybox
Multi-arch indexes can be narrowed with
--platform os/arch[/variant]. Attestation and provenance manifests
(for example application/vnd.in-toto+json) are skipped instead of
counted as platform scan failures.
Detection uses native detectors for 60+ secret types, with TruffleHog defaults as a fallback layer. Findings on paths that look like tests, fixtures, examples, specs, or e2e suites are kept as suppressed findings rather than counted as actionable.
Defaults live in .env.example and the Go config loader. Positive
MAX_* caps fail closed when exceeded, while preserving the partial
results produced before the failure.
LAYERLEAK_LOG_LEVEL=info
LAYERLEAK_FINDINGS_DIR=findings
LAYERLEAK_API_ADDR=127.0.0.1:8080
LAYERLEAK_PERSIST_RAW_SECRETS=0
LAYERLEAK_TAG_PAGE_SIZE=100
LAYERLEAK_HTTP_TIMEOUT=30s
LAYERLEAK_MAX_FILE_BYTES=1048576
LAYERLEAK_MAX_LAYER_BYTES=536870912
LAYERLEAK_MAX_LAYER_ENTRIES=50000
LAYERLEAK_MAX_MANIFEST_BYTES=0
LAYERLEAK_MAX_CONFIG_BYTES=0
LAYERLEAK_MAX_TAG_RESPONSE_BYTES=8388608
LAYERLEAK_MAX_REPOSITORY_TAGS=0
LAYERLEAK_MAX_REPOSITORY_TARGETS=0
LAYERLEAK_REGISTRY_REQUEST_ATTEMPTS=2
LAYERLEAK_DATABASE_URL=postgres://postgres:postgres@localhost:5432/layerleak?sslmode=disable
MAX_LAYER_BYTES, MAX_LAYER_ENTRIES,
MAX_MANIFEST_BYTES, MAX_CONFIG_BYTES,
MAX_TAG_RESPONSE_BYTES, MAX_REPOSITORY_TAGS,
MAX_REPOSITORY_TARGETS) accept 0 to disable the cap.
MAX_FILE_BYTES must be greater than zero.
LAYERLEAK_HTTP_TIMEOUT uses Go duration
syntax (30s, 2m, 1h) and applies to every
registry request: manifests, blobs, tag pages, and auth tokens.
LAYERLEAK_REGISTRY_BASE_URL and
LAYERLEAK_REGISTRY_AUTH_URL are optional. Leave them unset unless
forcing traffic through a proxy or alternate auth endpoint.
LAYERLEAK_API_ADDR to 0.0.0.0:8080.
Actionable findings remain in findings and drive the non-zero scan
exit status. Likely test, example, fixture, and spec placeholders are emitted as
suppressed findings with disposition metadata, and do not count toward
total_findings.
A scan saves a JSON file under LAYERLEAK_FINDINGS_DIR. If unset, it
defaults to findings/ beside the nearest go.mod, falling
back to the current working directory.
Saved files and Postgres writes default to redacted values and redacted context
snippets. Raw secret values and raw snippets are written only when
LAYERLEAK_PERSIST_RAW_SECRETS=1. API responses stay redacted even if a
writer persisted raw values.
Migrations are manual on purpose. The scanner does not create or upgrade schema at
runtime. Use PostgreSQL server >= 16.13.
psql "$LAYERLEAK_DATABASE_URL" -f migrations/0001_initial.up.sql
psql "$LAYERLEAK_DATABASE_URL" -f migrations/0002_finding_occurrence_metadata.up.sql
psql "$LAYERLEAK_DATABASE_URL" -f migrations/0003_scan_runs.up.sql
The published container image ships a rerunnable helper that enforces server
version >= 16.13 and exits non-zero on partial migration state:
docker run --rm \
-e LAYERLEAK_DATABASE_URL="$LAYERLEAK_DATABASE_URL" \
ghcr.io/brumbelow/layerleak:latest \
layerleak-migrate-up
Current state is deduplicated by (manifest_digest, fingerprint).
Append-only scan history lives in scan_runs as a redacted snapshot of
the public result JSON, regardless of LAYERLEAK_PERSIST_RAW_SECRETS.
The API binary under cmd/api is JSON-only and Postgres-backed. It
requires LAYERLEAK_DATABASE_URL; it does not serve from local findings
files.
go run ./cmd/api
/health/api/v1/scans/api/v1/scans/{id}/api/v1/repositories/api/v1/repositories/{repository}/scans/api/v1/repositories/{repository}/findings/api/v1/findings/{id}
POST /api/v1/scans is synchronous and accepts a JSON body with
reference and optional platform. The response includes
scan_run_id when Postgres persistence is enabled, plus the same
redacted result schema produced by the CLI.
Repository scan and finding list endpoints accept an optional
?registry= query parameter (defaults to docker.io),
plus ?limit= and ?offset= pagination
(max limit is 200). Findings list also accepts
?disposition=actionable|suppressed|all.
The published image runs the API by default. It binds
0.0.0.0:8080 and expects LAYERLEAK_DATABASE_URL.
docker pull ghcr.io/brumbelow/layerleak:latest
docker run --rm \
-p 8080:8080 \
-e LAYERLEAK_DATABASE_URL='postgres://<user>:<pass>@<host>:5432/layerleak?sslmode=disable' \
ghcr.io/brumbelow/layerleak:latest
The repository ships a Compose stack in docker-compose.yml with
db, migrate, and api services. The
db service baseline is pinned to
postgres:16.13-alpine.
docker compose --profile manual run --rm migrate
docker compose up -d api
In Dockge or Komodo, import the same Compose file and run the migrate
service once before enabling the long-running api service.
The Pages demo is fully static. It replays a fixed transcript and renders a synthetic Postgres-style snapshot from versioned fixture data in the repository. Nothing is sent to any registry or database.
layerleak scan vulnerableHost:latest --platform linux/amd64