
layerleak the Docker Hub Secret Scanner
Check CONTRIBUTING.md for contribution guidelines.
The published site is built from web/ on main by .github/workflows/pages.yml. The docs source and the simulated browser demo both live under that directory.
Prerequisites:
Install with Go:
go install github.com/brumbelow/layerleak@latest
layerleak --help
The canonical install target is the module root. To pin a release explicitly:
go install github.com/brumbelow/[email protected]
Replace v1.0.0 with the published v1.x.y tag you want.
Make sure your GOBIN or GOPATH/bin directory is on PATH.
The module path is github.com/brumbelow/layerleak, so go install @latest resolves to the highest published v1.x.y tag. A v2.x.y module release would require the module path to change to github.com/brumbelow/layerleak/v2. Module-installed binaries report the resolved module version through layerleak --version; local checkout builds report the version Go embeds for the checkout, falling back to dev when no module version is available.
Build from source:
git clone https://github.com/brumbelow/layerleak.git
cd layerleak
go build -o layerleak .
./layerleak --help
Run the API with a container image:
docker pull ghcr.io/brumbelow/layerleak:latest
docker run --rm \
-p 8080:8080 \
-e LAYERLEAK_DATABASE_URL='postgres://<user>:<password>@<host>:5432/layerleak?sslmode=disable' \
ghcr.io/brumbelow/layerleak:latest
The container image runs the API by default and sets LAYERLEAK_API_ADDR=0.0.0.0:8080.
Optional environment configuration:
cp .env.example .env
Result and database configuration:
export LAYERLEAK_LOG_LEVEL=info
export LAYERLEAK_FINDINGS_DIR=findings
export LAYERLEAK_API_ADDR=127.0.0.1:8080
export LAYERLEAK_PERSIST_RAW_SECRETS=0
export LAYERLEAK_TAG_PAGE_SIZE=100
export LAYERLEAK_HTTP_TIMEOUT=30s
export LAYERLEAK_MAX_FILE_BYTES=1048576
export LAYERLEAK_MAX_LAYER_BYTES=536870912
export LAYERLEAK_MAX_LAYER_ENTRIES=50000
export LAYERLEAK_MAX_MANIFEST_BYTES=0
export LAYERLEAK_MAX_CONFIG_BYTES=0
export LAYERLEAK_MAX_TAG_RESPONSE_BYTES=8388608
export LAYERLEAK_MAX_REPOSITORY_TAGS=0
export LAYERLEAK_MAX_REPOSITORY_TARGETS=0
export LAYERLEAK_REGISTRY_REQUEST_ATTEMPTS=2
# Optional registry overrides; usually leave unset.
export LAYERLEAK_REGISTRY_BASE_URL=
export LAYERLEAK_REGISTRY_AUTH_URL=
export LAYERLEAK_DATABASE_URL=postgres://postgres:postgres@localhost:5432/layerleak?sslmode=disable
The same variables and their defaults live in .env.example, which is the source of truth for default values.
| Variable | Default | Purpose |
|---|---|---|
LAYERLEAK_LOG_LEVEL | info | Log level: debug, info, warn, or error. |
LAYERLEAK_FINDINGS_DIR | unset | Where to write JSON findings files. If unset, defaults to findings/ under the nearest parent containing go.mod, falling back to the current working directory. |
LAYERLEAK_API_ADDR | 127.0.0.1:8080 | Bind address for the API server. The container image overrides this to 0.0.0.0:8080. |
LAYERLEAK_PERSIST_RAW_SECRETS | 0 | Set to 1 to write raw secret values and raw context snippets to disk and Postgres. Findings stay redacted by default. |
LAYERLEAK_HTTP_TIMEOUT | 30s | Per-request timeout for every registry call (manifests, blobs, tag pages, auth tokens). Accepts any Go duration (30s, 2m, 1h). |
LAYERLEAK_MAX_FILE_BYTES | 1048576 (1 MiB) | Max decompressed bytes buffered per file inside a layer. Files larger than this are skipped as oversize. Must be greater than zero. |
LAYERLEAK_MAX_LAYER_BYTES | 536870912 (512 MiB) | Max decompressed layer stream bytes per layer. 0 disables the limit. |
LAYERLEAK_MAX_LAYER_ENTRIES | 50000 | Max tar entries per layer. 0 disables the limit. |
LAYERLEAK_MAX_MANIFEST_BYTES | 0 | Max manifest body bytes. 0 disables the limit. |
LAYERLEAK_MAX_CONFIG_BYTES | 0 | Max image config body bytes. 0 disables the limit. |
LAYERLEAK_MAX_TAG_RESPONSE_BYTES | 8388608 (8 MiB) | Max bytes per registry tag-list response page. 0 disables the limit. |
LAYERLEAK_TAG_PAGE_SIZE | 100 | Registry tag-list page size for repository-wide scans. |
LAYERLEAK_MAX_REPOSITORY_TAGS | 0 | Max tags enumerated per repository scan. 0 disables the limit. |
LAYERLEAK_MAX_REPOSITORY_TARGETS | 0 | Max distinct targets resolved per repository scan. 0 disables the limit. |
LAYERLEAK_REGISTRY_REQUEST_ATTEMPTS | 2 | Number of attempts (including the first) for each registry request. |
LAYERLEAK_REGISTRY_BASE_URL | unset | Optional override. Normally layerleak derives this from each image reference; set only to force scans through a proxy or alternate endpoint. |
LAYERLEAK_REGISTRY_AUTH_URL | unset | Optional override. Normally discovered from the registry's WWW-Authenticate challenge. |
LAYERLEAK_DATABASE_URL | unset | If set, layerleak writes scans to Postgres and fails the command if persistence does not succeed. |
When any of the MAX_* limits is set to a positive value, exceeding it fails the scan with a clear error instead of silently truncating work.
Result behavior:
findings and drive the non-zero scan exit status.total_findings.disposition, disposition_reason, and line_number to make triage and false-positive review easier.1 because the scan is incomplete.Layerleak ships versioned SQL migrations under migrations/.
Migrations are manual on purpose. The scanner does not auto-create or auto-upgrade the schema.
Layerleak requires PostgreSQL server >= 16.13 for DB-backed API and scanner persistence.
Apply the migrations with psql in order: