#7704·nuclei

[BUG] SDK: .nuclei-ignore is read before the installer creates it, so the first engine runs with an empty deny-list

Author: G360-NiekCreated Sep 4, 2026Updated Sep 4, 2026

Is there an existing issue for this?

  • I have searched the existing issues.

Current Behavior

init() reads .nuclei-ignore before the installer writes it, in the same function.

The only read that feeds template filtering is lib/sdk_private.go:140, inside applyRequiredDefaults:

go
e.opts.ExcludeTags = append(e.opts.ExcludeTags, config.ReadIgnoreFile().Tags...)

applyRequiredDefaults has exactly one caller — init() line 199. Later in that same init(), the installer creates/updates the file:

go
line 349:  latestIgnoreHash, _ := installer.NucleiSDKVersionCheck()
line 379:  err = tm.UpdateIfOutdated()      // writes templates + .nuclei-ignore

init() spans 146–358 on v3.11.1 (read 188, writes 338/360), so the read always precedes the write. Consequence: on any host where .nuclei-ignore does not already exist, the first engine constructed reads nothing and runs with an empty ExcludeTags. It executes the templates the deny-list exists to block — dos, bruteforce, fuzz, local, txt-service.

ExcludeTags is frozen per engine and never refreshed, so the file appearing seconds later has no effect on that engine. Every subsequent engine re-reads the file, so it self-heals after the first — which is why it is easy to miss.

The blast radius is larger than "one engine" for SDK consumers that construct one engine per protocol group and reuse it across a scan: a single leaking engine can cover every HTTP template for that whole scan.

Expected Behavior

The deny-list should be in force for the first engine too. Either read the ignore file after the installer has had a chance to create/update it, or ensure it exists before reading. The installer already knows how to write it.

Steps To Reproduce

Controlled A/B on one host, same binary, same config, same targets — the only variable is whether .nuclei-ignore existed at engine construction.

  1. Start from a container/host with no nuclei config dir (fresh writable layer, or rm -rf ~/.config/nuclei ~/nuclei-templates), with updateTemplates enabled so the SDK installs templates itself.
  2. Run a scan that constructs a thread-safe engine and executes HTTP templates against a target that trips a fuzz-tagged template (we used waf-fuzz, tags: waf,fuzz,fuzzing,vuln).
  3. Observe: .nuclei-ignore is written during that first engine's init(), after the read.
  4. Run the same scan again, now that the file exists.

Timeline from our run:

12:12:23Z  container started (fresh writable layer, no nuclei config dir)
12:14:30Z  scan 1 starts; engine 1 init -> applyRequiredDefaults reads a MISSING .nuclei-ignore
12:15:12Z  same init, later: installer writes .nuclei-ignore
12:15:24Z  installer writes nuclei-templates
12:41:41Z  waf-fuzz (fuzz-tagged) fires and is persisted -- still engine 1
14:12:57Z  scan 2 starts, ignore file now present

Relevant log output

bash
# scan 1 (.nuclei-ignore absent at engine construction)
fuzz-tagged templates detected = 8     across 4 hosts
total detections               = 13

# scan 2 (.nuclei-ignore present), same binary/config/targets
fuzz-tagged templates detected = 0
total detections               = 5
# all 4 of the hosts that tripped waf-fuzz in scan 1 still produced
# non-deny-listed detections in scan 2, so they were scanned, not skipped

Environment

  • OS: Linux (containerised deployment; also applies to any host without a pre-existing ignore file)
  • Nuclei: v3.11.1 used as a library; ordering also present on dev @ a34f810f
  • Go: go1.26.0

Anything else?

Environments that pre-provision .nuclei-ignore out-of-band (e.g. a shared volume populated before the process starts) never see this, which is why it can hide for a long time. It surfaces on any freshly provisioned host, and on containerised deployments it recurs on every redeploy that resets the filesystem — not just the first install.

Happy to open a PR. The smallest fix is to move the ignore-file merge in applyRequiredDefaults to after the installer runs, or to re-apply it once the installer has completed.

Related, same area, both already filed: #7695 (per-scan WithTemplateFilters clearing the deny-list, PR #7698) and #7696 (the files: section never applied in library mode, PR #7697). This one is independent of both — it is about when the file is read rather than what is read from it or what later overwrites it.