The Template Passed Review: Nuclei Scanner Trust
The CI job had one unusual child process. Not a shell launched by the application under test, but an interpreter spawned by the scanner itself.
That distinction changes the investigation. The target may still be hostile, yet the asset at risk is now the machine trusted to inspect it: its filesystem, environment variables, network reach, and short-lived cloud credentials. Nuclei template security begins with that reversal. A template is not merely a collection of HTTP requests once it can select JavaScript, file, code, workflow, or DAST behavior.
This is a composite laboratory scenario, not a report of a specific intrusion. It frames five ProjectDiscovery advisories finalized in 2026 around one engineering problem: capability checks implemented at one loader or protocol boundary can be bypassed through another.[1][3][5]
The affected Nuclei range was 3.0.0 through 3.9.x; the fixes landed in 3.10.0, with additional JavaScript signing hardening in 3.11.0.[2][4][7]
The previous article examined two HTTP parsers disagreeing about a byte stream. This analysis moves the same trust-boundary idea into security tooling. Here the disagreement is internal: one path classifies a template as safe enough to load, while a later component discovers that it can execute on the scanner host.
The template is part of the attack surface
An operator usually thinks in the outbound direction: templates describe probes, the scanner sends them, and the target returns evidence. The five advisories force a second data-flow model. Template content, workflow references, target-controlled responses, protocol libraries, and runtime flags all feed an execution engine that holds local authority.
CVE-2026-76802 showed the cleanest policy split. Nuclei normally required a valid signature and the -code opt-in before a code: template could invoke an interpreter. A template combining fuzzing: with unsigned code:, however, entered through the DAST loader. That branch omitted the unsigned-code guard and placed the code request in the execution queue when -dast was enabled.[1]
The exploitable primitive was not “YAML runs commands.” It was path-sensitive authorization. The same host-impacting capability received a different decision depending on which loader reached it. The 3.10.0 patch moved signature enforcement ahead of DAST dispatch, so the decision applies before the path forks.[1]
CVE-2026-76804 repeated the pattern with local files. Direct file-protocol templates were gated by -file, but a workflow could reference the same protocol without passing through the equivalent check. Because untrusted workflows could run unsigned, a workflow became a capability tunnel into scanner-local file reads.[3]
The durable change was architectural. ProjectDiscovery centralized opt-in requirements for file, self-contained, headless, code, DAST, and global-matcher capabilities, then reused that table during template loading, workflow parsing, request compilation, and runner statistics.[6] That is the important lesson for any extensible security tool: authorization belongs to the capability, not to the first user interface that exposes it.

JavaScript made “no code flag” a dangerous assumption
The operator’s next hypothesis should be that every embedded language is code, even when the CLI uses a different protocol name.
CVE-2026-76819 affected the Goja engine behind javascript: templates. A memory-safety flaw could turn attacker-controlled JavaScript into native code execution on the scanner host. On affected versions, JavaScript templates neither required -code nor the code-template signature check, and an init section could execute during initialization before later security checks completed.[2]
Upgrading to 3.10.0 updated the vulnerable dependency. Version 3.11.0 then added a separate requirement for JavaScript template signatures.[2][7] The second change matters even after the memory bug is fixed. A managed runtime reduces some exploit classes; it does not make an untrusted program an inert document.
CVE-2026-76803 demonstrated the same point through a library rather than the engine. A JavaScript template could set MySQL’s allowAllFiles=true. A malicious database endpoint could then request LOAD DATA LOCAL INFILE for an arbitrary scanner-host path, bypassing the normal -allow-local-file-access sandbox. The patch strips that DSN option unless local file access is explicitly enabled.[4]
File policy must cover indirect readers: database drivers, archive handlers, browser upload controls, language module loaders, and any helper that accepts a path. Auditing only obvious open() wrappers misses the component most likely to reimplement filesystem access.
The target can write into the scanner’s expression language
CVE-2026-76805 moved the trust problem from template provenance to response provenance. In DAST mode, an internal extractor could capture target-controlled response data, insert it into a later fuzz payload, and pass the result through expression evaluation again. With -env-vars enabled, text such as a template expression returned by the target could be reinterpreted on the second pass and resolve scanner-host environment variables.[5]
The flag was off by default, which narrows exposure but does not weaken the design finding. Substitution and evaluation are different operations. Once attacker-controlled output becomes fresh syntax, the target has crossed from data plane to control plane. The fix created a clearer rendering boundary instead of relying on each caller to remember whether a value had already been evaluated.[5]
This is close to the problem described in advanced attack-chain threat modeling: authority often emerges from composition. A response extractor, a second render pass, and an opt-in environment map may look acceptable separately. Their join leaks a secret.
A safe pre-execution gate
Do not validate this class of issue by running an unsigned template on a vulnerable scanner. A safer lab stops before Nuclei. Treat the template as bytes, calculate its digest, identify requested capabilities, and quarantine anything that lacks approved provenance or asks for host-impacting protocols.
python3 lab_gate.py candidate.yaml
{
"capabilities": ["code", "fuzzing"],
"signed": false,
"decision": "quarantine",
"reasons": [
"unsigned template",
"host-impacting capability",
"response-derived evaluation path"
]
}
The local mechanism lab for this article performed only that static classification. It opened no socket, launched no interpreter from the candidate template, and did not install or execute an affected Nuclei release. The result is intentionally conservative. A digest-looking comment is not proof of signer identity, so production admission must use Nuclei’s cryptographic verifier or an independently validated equivalent.[8]
A useful CI policy separates provenance from capability:
- Pin the Nuclei binary and template repository to reviewed versions or commits.
- Require valid signatures for every custom template, not only code and JavaScript.
- Enable
-disable-unsigned-templatesfor every managed scanner job.[9] - Reject unexpected
code,javascript,file,headless,self-contained, workflow, and fuzzing capabilities before execution. - Do not expose general CI secrets through
-env-vars; inject one scan-scoped value only when a reviewed template requires it. - Run the scanner as an ephemeral, non-root identity with a read-only filesystem, empty home directory, bounded egress, and no mounted container socket or cloud credential directory.
This complements an SBOM and VEX decision pipeline. Dependency inventory can identify a vulnerable scanner version, but it cannot tell you whether untrusted templates, DAST, JavaScript, local files, or environment-variable expansion are reachable in a specific job. That requires execution-context evidence.
Detection starts on the scanner side
Scanner logs usually emphasize findings against targets. Add telemetry for what the scanner itself was allowed to do. Record the binary version, template SHA-256, signer identity, source repository and commit, resolved protocol set, enabled flags, worker identity, and destination set. Preserve rejected-template events. A skipped unsigned template is security telemetry, not console noise.
At the host or container layer, alert on child shells and interpreters that do not match an approved signed template; reads from credential paths; access to metadata services; outbound connections outside the supplied target scope; DNS or MySQL traffic from jobs expected to run HTTP-only templates; and writes outside the designated output directory.
The most actionable correlation joins a template decision to runtime activity. “Python executed” may be expected. “Unsigned template hash X caused Python to read $HOME/.aws and contact an unscoped address” is an incident. If your autonomous pentest pipeline can select templates dynamically, retain the planner decision and artifact provenance beside that host telemetry.
Strategic takeaways
The small detail in this chain is the route into the runtime: DAST instead of direct loading, workflow instead of file mode, a driver option instead of a filesystem API, JavaScript instead of code, or a second render pass instead of the first. Each alternate path inherited more authority than its gate recognized.
Upgrade Nuclei to at least 3.10.0 for the DAST, workflow, file-access, and rendering fixes.[1][3][5]
Prefer 3.11.0 or later for mandatory JavaScript signing.[2][4][7] Then assume the verifier can fail again. Keep templates signed and pinned, capabilities denied unless expected, secrets absent by default, and execution inside a disposable worker whose network and filesystem policy are independent of the scanner.
A security scanner is privileged because defenders trust it to touch many systems. That trust should not flow backward into every template, target response, and embedded runtime it knows how to interpret.
Sources
- [1] CVE-2026-76802: DAST code signature bypass
- [2] CVE-2026-76819: Goja JavaScript engine code execution
- [3] CVE-2026-76804: workflow file-protocol gate bypass
- [4] CVE-2026-76803: MySQL local-file sandbox bypass
- [5] CVE-2026-76805: DAST environment-variable disclosure
- [6] Centralized Nuclei capability-gate commit
- [7] Nuclei 3.11.0 JavaScript signature requirement
- [8] ProjectDiscovery template-signing documentation
- [9] ProjectDiscovery Nuclei execution options
💜 Enjoyed this content? Support the blog with USDT (TRC20):
TX7obcjHQbDUXb4mGqoASEu1QFTKT2CFGG
