Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Flan Scan — User Guide

Generated July 2026 from cloudflare/flan at 6588b07

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Flan Scan is a lightweight network vulnerability scanner. It finds open ports on your network, identifies the services and versions running behind them, and produces a report of the CVEs (known vulnerabilities) that affect them. Under the hood it is a wrapper around Nmap and the vulners NSE script, packaged in a Docker container so you can run a full network vulnerability scan with two commands.

This guide covers everything you need to run scans, choose report formats, and push results to the cloud. Deep architecture, security hardening, and developer internals live in the Admin Guide.


1. Quick Start

The fastest path from zero to your first report.

Requirements: Docker installed and running, and make available on your host.

# 1. Clone the repository
git clone https://github.com/cloudflare/flan.git
cd flan

# 2. Add the IPs or CIDR ranges you want to scan (one per line)
echo "8.8.8.8" >> shared/ips.txt

# 3. Build the container image
make build

# 4. Run your first scan
make start

When the scan finishes, the report is written to shared/reports/ and the raw Nmap XML is written to shared/xml_files/:

shared/
├── ips.txt
├── reports/
│   └── report_2024.09.19-14.32.tex
└── xml_files/
    └── 2024.09.19-14.32/
        └── 8.8.8.8.xml

Note

The default report format is LaTeX (.tex). To get a human-readable format immediately, run make md (Markdown) or make html instead of make start. See Everyday Usage.

Warning

Only scan hosts and networks you own or are explicitly authorized to test. Network vulnerability scanning against systems you do not control may be illegal in your jurisdiction. The default shared/ips.txt shipped in the repo contains example addresses (1.1.1.1, 8.8.8.8, 127.0.0.0/8) — replace them with your own targets.


2. Installation

Flan Scan is distributed as source that you build into a Docker image locally. There is no published package or binary — you always build the container yourself.

Requirements

Requirement Details
Docker Required. Verify with docker --version.
make Used by the provided Makefile targets (build, start, md, html, json). Optional if you invoke docker directly.
Network access The build clones the nmap-vulners script from GitHub and updates the Nmap script database.
Target list A shared/ips.txt file containing the IPs/CIDRs to scan.

Everything else — Nmap, the Nmap scripts, the vulners NSE script, and all Python dependencies — is installed inside the container by the Dockerfile. You do not need Nmap or Python installed on your host.

Docker

Verify Docker is available:

docker --version
Docker version 24.0.6, build ed223bc

Build the image (tagged flan_scan):

make build

This runs, under the hood:

docker build --no-cache -t flan_scan -f Dockerfile .

The build is based on python:3.9-alpine and installs nmap, nmap-scripts, and the vulners script, then runs nmap --script-updatedb. The first build downloads these packages and can take a few minutes.

Tip

make build uses --no-cache, so every build is a clean rebuild. This guarantees the latest nmap-vulners script and CVE matching data, but means builds are not incremental. Rebuild whenever you want fresh vulnerability data.


3. Configuration Basics

Flan Scan is configured through two things: the target list file and a small set of environment variables. There is no config file to edit.

The target list

Add one IP address or CIDR range per line to shared/ips.txt:

192.0.2.10
198.51.100.0/24
203.0.113.5

Important

Make sure the file ends with a newline. A known issue is that the last IP in ips.txt will not be scanned unless the line ends with a newline character (community issue #21). Always leave a trailing blank line.

Environment Variables

These control report format and cloud upload. Only format matters for a purely local scan.

Variable Values Default Purpose
format tex, md, html, json tex Output report format.
upload aws, gcp (unset — no upload) Push results to cloud storage.
bucket bucket name (none) Target S3 or GCS bucket when upload is set.
GOOGLE_APPLICATION_CREDENTIALS path (none) Path to a GCS service-account key (GCP uploads).
AWS_ACCESS_KEY_ID key id (none) AWS credential (AWS uploads).
AWS_SECRET_ACCESS_KEY secret (none) AWS credential (AWS uploads).

The Makefile targets set format for you (make md sets format=md, etc.). To set upload, bucket, or credentials you run the container directly with docker run -e ... (see Pushing Results to the Cloud).

The full configuration reference — including exactly where each variable is read in the code — is in the Admin Guide.


4. Everyday Usage

Flan Scan is driven entirely by make targets (for common cases) or docker run (for full control). Each target runs one complete scan of every host in shared/ips.txt and writes one report.

Run a scan (default LaTeX report)

make start

This runs the container with only the NET_RAW Linux capability (needed by Nmap for raw-socket scanning) and mounts your local shared/ directory into the container:

docker run --rm --cap-drop=all --cap-add=NET_RAW --name flan_<timestamp> \
  -v "$(CURDIR)/shared:/shared:Z" flan_scan

The report is written to shared/reports/report_<timestamp>.tex.

Choose a report format

Command Format Output extension
make start LaTeX (default) .tex
make md Markdown .md
make html HTML (rendered from a Jinja2 template) .html
make json JSON .json
make md
shared/reports/report_2024.09.19-14.32.md

Tip

Markdown (make md) and HTML (make html) are the easiest to read directly. JSON (make json) is best if you want to feed results into another tool or dashboard. LaTeX (make start) is intended to be compiled into a polished PDF.

Find your reports

After any scan:

  • Reports: shared/reports/report_<timestamp>.<ext>
  • Raw Nmap XML: shared/xml_files/<timestamp>/<ip>.xml

The timestamp format is YYYY.MM.DD-HH.MM (for the report) so each run produces a distinct file and nothing is overwritten.

Add custom Nmap flags

Any flags you pass to the container are appended to the Nmap command. This lets you run UDP scans, scan IPv6 addresses, or change scan behavior. After make build, run the image directly and add your flags at the end:

docker run --rm --cap-drop=all --cap-add=NET_RAW \
  -v "$(pwd)/shared:/shared:Z" flan_scan -sU -6

Here -sU requests a UDP scan and -6 enables IPv6. These are inserted into Flan Scan's built-in Nmap invocation.

Note

The base command Flan Scan always runs is:

nmap -sV -oX <xml-out> -oN - -v1 <your-flags> --script=vulners/vulners.nse <ip>

-sV enables service/version detection, -oX writes XML output, -oN - prints normal output to the console, -v1 sets verbosity, and --script=vulners/vulners.nse matches detected services to CVEs.


5. Common Workflows

End-to-end recipes for the most common tasks.

Workflow 1 — Scan a single subnet and read the results

# Target one subnet
echo "192.0.2.0/24" > shared/ips.txt
echo "" >> shared/ips.txt          # ensure trailing newline

make build
make md                            # Markdown is easy to read
cat shared/reports/report_*.md

Workflow 2 — Local scan with a compiled PDF

The default LaTeX output is designed to be compiled into a PDF. Flan Scan produces the .tex file; you compile it with any LaTeX toolchain:

make start
cd shared/reports
pdflatex report_2024.09.19-14.32.tex

Warning

LaTeX compilation can fail if a vulnerability summary contains characters that are special to LaTeX. Flan Scan already escapes _, $, #, and % in .tex reports, but other constructs pulled from CVE descriptions have occasionally broken LaTeX tooling (community issue #33). If compilation fails, switch to make md, make html, or make json.

Workflow 3 — Scan and push a report to AWS S3

make build
docker run --rm --cap-drop=all --cap-add=NET_RAW \
  --name flan_s3 \
  -v "$(pwd)/shared:/shared:Z" \
  -e upload=aws \
  -e bucket=my-security-bucket \
  -e AWS_ACCESS_KEY_ID=AKIA... \
  -e AWS_SECRET_ACCESS_KEY=... \
  -e format=md \
  flan_scan

Both the report and the raw XML files are uploaded to the named bucket.

Workflow 4 — Scan and push a report to Google Cloud Storage

# Place your service-account key in shared/ so the container can read it
cp /path/to/key.json shared/key.json

make build
docker run --rm --cap-drop=all --cap-add=NET_RAW \
  --name flan_gcs \
  -v "$(pwd)/shared:/shared:Z" \
  -e upload=gcp \
  -e bucket=my-gcs-bucket \
  -e GOOGLE_APPLICATION_CREDENTIALS=/shared/key.json \
  -e format=html \
  flan_scan

Workflow 5 — Recurring scans

Because each run is a self-contained container and every report/XML file is timestamped, you can schedule Flan Scan with cron on your host (call make start or the docker run command on a schedule) or, for cloud-native recurring scans, deploy it as a Kubernetes CronJob. The Kubernetes approach is documented in the Admin Guide.


6. Advanced Usage

Passing multiple Nmap flags

All arguments after the image name are forwarded to Nmap verbatim. For example, to run a UDP + TCP service scan with OS detection against IPv6 hosts:

docker run --rm --cap-drop=all --cap-add=NET_RAW \
  -v "$(pwd)/shared:/shared:Z" flan_scan -sU -sV -O -6

Understanding severity levels

Flan Scan classifies each CVE by its CVSS score into three severity bands:

CVSS score Severity label
< 4.0 Low
4.0 – 6.9 Medium
≥ 7.0 High

These labels appear in every report format and, in the LaTeX report, drive the color-coding of each vulnerability (High = red, Medium = orange, Low = blue).

Vulnerability descriptions

For each CVE it finds, Flan Scan fetches a plain-language description from the public CVEProject/cvelist repository and links to the matching NIST NVD detail page. This requires outbound internet access from the container. If a description can't be fetched, the vulnerability is still reported — only its summary text is left blank.


7. How It Works

A short mental model so you can interpret results with confidence.

A Flan Scan run has two phases:

  1. Scan phase (run.sh). For each line in /shared/ips.txt, the container runs one Nmap command with service detection and the vulners script. Nmap identifies open ports, detects the service and version on each, and the vulners script matches those service versions against a database of known CVEs. The raw result for each host is saved as an XML file.

  2. Report phase (output_report.py). Flan Scan parses every XML file, groups findings into services with vulnerabilities and services with no known vulnerabilities, enriches each CVE with a description and NIST link, and renders everything into your chosen format.

Key terms:

  • Service — an application detected on an open port, identified by product name and version (e.g. OpenSSH 7.4).
  • Vulnerability (Vuln) — a CVE matched to a detected service, with a name, type, and CVSS-based severity.
  • Vulners — the Nmap NSE script that performs the CVE matching. Without it, Nmap only finds open ports; with it, Flan Scan becomes a vulnerability scanner.

Note

Flan Scan only knows about vulnerabilities that the vulners script can match to a detected service version. If Nmap can't determine a service's version, that service is reported but no CVEs are attached to it.

The internal architecture (parser, report builders, description providers) is documented in the Admin Guide.


8. Troubleshooting

Format: the exact symptom, its cause, the fix, and how to avoid it. These are grounded in real community-reported issues.

Error: mkdir: can't create directory '/shared/xml_files/...': Permission denied

  • Cause: The mounted shared/ directory is not writable by the container, commonly due to SELinux labeling on the host (issues #73, #10).
  • Solution: The provided Makefile mounts the volume with the :Z SELinux relabel flag (-v "$(CURDIR)/shared:/shared:Z"). If you run docker run by hand, include :Z on the volume mount. On non-SELinux systems, ensure the shared/ directory and its reports/ and xml_files/ subdirectories are writable.
  • Prevention: Prefer the make targets, which already set the correct mount flags.

Error: Permission denied when running make start

  • Cause: Either the Docker daemon is not accessible to your user, or the shared/ mount is not writable (issue #10).
  • Solution: Confirm docker ps works without sudo; if not, add your user to the docker group or run with elevated privileges. Verify the shared/ directory permissions as above.
  • Prevention: Verify docker --version and docker ps succeed before building.

Error: Run-time Error: No Launch Config Set

  • Cause: The container was started without the configuration it needs to run the scan/report pipeline (issue #84), typically when invoked outside the provided make/docker run patterns.
  • Solution: Start Flan Scan using make start (or one of make md|html|json), or the documented docker run command that mounts /shared and sets the format/upload environment variables.
  • Prevention: Use the documented invocation patterns rather than starting the image with a custom entrypoint.

Problem: The last IP in my list is never scanned

  • Cause: run.sh reads ips.txt line by line; a final line without a trailing newline is not read (issue #21).
  • Solution: Add a trailing newline (an empty final line) to shared/ips.txt.
  • Prevention: Always leave one blank line at the end of the file.

Problem: Scan completes but no CVEs are reported

  • Cause: The vulners script can only match CVEs when Nmap successfully detects the service version; hosts that are down, filtered, or return no version info yield no CVEs (issues #62, #46).
  • Solution: Confirm the target hosts are up and reachable and that ports are open. -sV is already included; consider adding flags such as -Pn (via the custom-flags mechanism) for hosts that block ping.
  • Prevention: Validate reachability with a manual nmap -sV <ip> before relying on scheduled scans.

Problem: Some hosts are missing from the report (KeyError)

  • Cause: Hosts that are down or return an unexpected XML structure could previously trigger a KeyError during parsing (issue #46).
  • Solution: Update to the current version, which guards against missing service data (unknown services are labeled unknown rather than crashing). Re-run the scan.
  • Prevention: Keep your image rebuilt from the latest source (make build).

Error: LaTeX report fails to compile

  • Cause: Special characters in a CVE summary that LaTeX treats as control characters (issue #33).
  • Solution: Flan Scan escapes _, $, #, and % automatically; for summaries that still break compilation, generate a different format with make md, make html, or make json.
  • Prevention: Use Markdown or HTML output if you don't specifically need LaTeX/PDF.

Error: connection error / description fetching error

  • Cause: The container could not reach the CVEProject GitHub repository or NIST to fetch vulnerability descriptions (issue #52), or has no outbound internet access.
  • Solution: Ensure the container has outbound HTTPS access. The vulnerability is still listed even when the description can't be fetched — only the summary text is blank.
  • Prevention: Run scans from a network with outbound internet access, or accept blank descriptions for air-gapped environments.

Error: free(): invalid pointer during make build

  • Cause: A low-level failure during the image build (issue #4), typically environment-specific (host architecture / base image interaction).
  • Solution: Rebuild with make build; if it persists, try on a different host architecture or an updated Docker version.
  • Prevention: Build on a supported x86-64 Docker host.

9. FAQ

Q: Do I need Nmap or Python installed on my machine? No. Everything runs inside the Docker container. You only need Docker (and make for convenience).

Q: What report formats are available? Four: LaTeX (tex, the default), Markdown (md), HTML (html), and JSON (json). Select with the matching make target or the format environment variable.

Q: Where do reports and raw scan data go? Reports go to shared/reports/ and the raw Nmap XML goes to shared/xml_files/, both timestamped. These directories are on your host because shared/ is mounted into the container.

Q: Can I scan UDP ports or IPv6 addresses? Yes. Pass Nmap flags (e.g. -sU for UDP, -6 for IPv6) after the image name when you run the container directly; they are appended to Flan Scan's Nmap command.

Q: How does Flan Scan decide a service is vulnerable? It runs the Nmap vulners script, which matches the detected service and version against a CVE database. Each match becomes a vulnerability, classified Low/Medium/High by CVSS score.

Q: Can I upload results automatically? Yes. Set upload=aws or upload=gcp plus a bucket name and the corresponding cloud credentials as environment variables. Both the report and raw XML are pushed to the bucket.

Q: Is it safe to scan any IP address? Only scan networks you own or are authorized to test. Replace the example addresses in shared/ips.txt with your own targets before scanning.


10. Reference

Command Quick Reference

Command Effect
make build Build the flan_scan Docker image (clean, no cache).
make start Run a scan → LaTeX (.tex) report.
make md Run a scan → Markdown (.md) report.
make html Run a scan → HTML (.html) report.
make json Run a scan → JSON (.json) report.
docker run ... flan_scan <flags> Run a scan with custom Nmap flags and/or cloud upload.

Configuration Quick Reference

Variable Values Default Notes
format tex / md / html / json tex Report format.
upload aws / gcp (unset) Enables cloud upload.
bucket bucket name (none) Required when upload is set.

For the complete configuration reference (including credential variables and where each is read in the code), see the Admin Guide.

Input / Output Locations

Path Purpose
shared/ips.txt Your list of target IPs/CIDRs (one per line, trailing newline required).
shared/reports/ Generated reports, timestamped.
shared/xml_files/ Raw Nmap XML output, timestamped.