LeanSecurity standard deployment wrapper for Nuclei external vulnerability scanning.
Automated Nuclei scans against external assets. Three deployment modes share the same scanner core.
| Mode | What You Need | How It Works |
|---|---|---|
| Local CLI | Nuclei installed | ./scanner/scan.py <client> — results to local disk |
| Local Docker | Docker installed | Build from docker/Dockerfile.local, volume-mount config |
| Cloud (GCP) | GCP project, Terraform | Cloud Run job + Cloud Scheduler. See setup guide |
This repository contains the reusable pipeline (scanner core, container, GCP module). Per-client deployment configuration lives in client-controlled storage outside this repo. terraform apply is run from an architect's workstation against the client GCP project; the public-repo CI never holds GCP credentials and never touches client infrastructure.
The scanner image is distributed via GitHub Container Registry: ghcr.io/eriklacson/leansec-nuclei. Production deployments pin to a semver tag.
For the full picture: docs/gcp_architecture.md · module reference · end-to-end setup guide.
# Install Nuclei
brew install nuclei # macOS
# or: go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
# Run scan
./scanner/scan.py <client>
# Results in results/<client>/YYYY-MM/Push the month's JSONL into a GCS bucket for downstream tooling. In cloud mode, the Cloud Run job uploads automatically. In local mode, push by hand:
gcloud auth login
gcloud storage cp results/<client>/<YYYY-MM>/*.jsonl \
gs://<your-bucket>/nuclei/<YYYY-MM>/Local Docker is a deployment vehicle for the same scan.py the Local CLI runs — output is equivalent at identical paths with identical filenames. Use it when you want to scan without installing Nuclei on the host.
# Build (build context is repo root, NOT docker/)
docker build -f docker/Dockerfile.local -t eriklacson/leansec-nuclei:local .
# Run a scan
docker run --rm \
-e CLIENT=<client> \
-v "$(pwd)/deployments:/app/deployments:ro" \
-v "$(pwd)/results:/app/results" \
eriklacson/leansec-nuclei:local
# Results land in results/<client>/YYYY-MM/ on the host, just like Local CLI.Notes:
profiles.yamlis baked into the image at build time. To change scan profiles, editscanner/profiles/profiles.yamland rebuild the image.- The Nuclei version is pinned via
--build-arg NUCLEI_VERSION=vX.Y.Z(default indocker/Dockerfile.local). - Set
-e UPDATE_TEMPLATES=falseto skip thenuclei -update-templatescall at startup (useful in air-gapped or CI environments). - Architecture rationale: see decisions/ADR-007-python-in-container.md.
After rebuilding the scanner image, you can sanity-check it against a
local validation harness (DVWA, Juice Shop, WebGoat). See
tests/validation/README.md for the full
workflow. Quick version:
# Bring up the harness
docker compose -f tests/validation/docker-compose.yaml up -d
# Scan it
docker run --rm \
--network=scanner-validation \
-e CLIENT=_validation \
-v "$(pwd)/deployments:/app/deployments:ro" \
-v "$(pwd)/results:/app/results" \
eriklacson/leansec-nuclei:local
# Tear down
docker compose -f tests/validation/docker-compose.yaml downExpect findings > 0 across multiple profiles. Zero findings against the validation harness means something is wrong with the rebuild — the apps are intentionally vulnerable.
Spin up Damn Vulnerable Web Application as a known-bad target to validate scan profiles end-to-end:
# Start DVWA on http://localhost:80
docker run --rm -p 80:80 vulnerables/web-dvwa
# In another terminal, point the localtest deployment at it
echo "http://localhost:80" > deployments/localtest/targets.txt
# Run the scan
./scanner/scan.py localtest
# Inspect findings
ls results/localtest/$(date +%Y-%m)/Expect hits from baseline_web, owasp_top10_core, and transport_security profiles. Use this as smoke-test coverage before promoting profile changes to client deployments.
Cloud-automated deployment runs the scanner as a Cloud Run job on a Cloud Scheduler cadence. The pipeline is architect-driven: an architect runs terraform apply from their workstation against the client GCP project; after that, Cloud Scheduler triggers the job autonomously.
# 1. Bootstrap the client GCP project (one-time)
./scripts/bootstrap-gcp-client.sh <your-project-id>
# 2. Copy the example folder to your private storage
cp -r deployments/_example/ /path/to/private/<client>/
# 3. Edit terraform.tfvars + backend.tf + targets.txt in your copy, then
cd /path/to/private/<client>/
terraform init
terraform plan
terraform applyThe scanner image is pulled from GHCR (ghcr.io/eriklacson/leansec-nuclei). Production deployments pin to a semver tag. The full walkthrough — including troubleshooting for the common failure modes (API not enabled, IAM denied, image pull failure, etc.) — is in docs/setup-guide.md. Maintainers publishing new image versions: see docs/release.md.
AWS and Azure modules under infra/aws/ and infra/azure/ are stubs and not part of the current activation.
leansecurity-nuclei/
├── scanner/ # Nuclei CLI runner + profiles (vendor-agnostic)
├── docker/ # Container build (used by both Local Docker and Cloud modes)
├── infra/gcp/ # Reusable GCP Terraform module
├── scripts/ # Operator scripts (bootstrap-gcp-client.sh)
├── deployments/_example/ # Deployment template (copy to private storage)
├── docs/ # Architecture + setup guide
└── .github/workflows/ # CI (lint/test) + publish-image (GHCR)
| Control | Evidence |
|---|---|
| DE.CM-08 | Scan execution + JSONL output |
| PR.AA-01 | identity_remote_access profile |
| PR.DS-01 | data_protection profile |
| PR.PS-01 | patch_cve + owasp_top10_core + vuln_monitoring |
| PR.IR-01 | transport_security profile |