Why polyglot templates
Most scanners describe a detection as data: send this request, match this string, report this finding. cxg supports that too, and YAML templates work exactly that way. It also lets a template be an ordinary program in eleven other languages.
This page explains when that second option earns its cost, and what the cost actually is. It is not an argument that code templates are better. For most checks they are worse.
Use YAML when YAML fits
Section titled “Use YAML when YAML fits”A YAML template is declarative, so the engine can read it without running it. That buys real things: cxg can list, filter, and search templates by their metadata; the template has no runtime dependency beyond cxg itself; and there is no way for the template to do something its author did not intend, because it cannot do anything except issue requests and match responses.
For a check shaped like “request this path, look for this string, at this status code”, YAML is the right answer and code is overhead.
Where the declarative model runs out
Section titled “Where the declarative model runs out”The limit is not expressiveness in the abstract. It is that a matcher decides whether a response is interesting, and nothing more. A YAML template cannot use what it learned from one response to decide what to do next, and it cannot compute anything.
That rules out three broad classes of detection.
Requests whose target is chosen by an earlier response
Section titled “Requests whose target is chosen by an earlier response”Matchers evaluate independently against a fixed request list. There is no way to
say “parse the ref path out of /.git/HEAD, then fetch that path”.
This distinction matters more than it looks. A pattern match on /.git/HEAD
fires on any server that returns 200 with plausible content for every path: a
soft-404 handler, a SPA fallback, a catch-all CDN rule. Following the reference
and requiring it to resolve is what separates an exposed repository from a
server that merely looks like one. That is a false-positive problem in the first
case, and one your reader has to triage by hand.
Write your first template builds exactly this detection.
Anything requiring computation
Section titled “Anything requiring computation”Matchers compare: equality, substring, regex, size, time. They do not compute. So there is no expressing:
- Cryptographic checks. Verify a JWT’s signature; confirm a token is signed
with a known weak key; check whether a certificate’s public key is reused
elsewhere; test whether
alg: noneis accepted, which needs re-signing and resending a modified token. - Statistical checks. Measure entropy across a sample of session identifiers to tell a random token from a counter. One token proves nothing; the distribution is the finding.
- Structural parsing. Read a binary format such as a git index, a serialised object, or a protocol frame, and assert something about its contents.
A regex over a response body can find a token. It cannot tell you the token is predictable.
Verification rather than indication
Section titled “Verification rather than indication”The difference between “this endpoint appears vulnerable” and “this endpoint is vulnerable, and here is the artifact proving it” is usually a second request that depends on the first. Confirming an SSRF means observing the callback. Confirming an injection means the second, differently-shaped payload behaving as predicted. Those are multi-step by nature.
A code template can carry state between steps and refuse to report anything until the chain completes. That is what the first-template guide demonstrates: three dependent requests, and no finding unless all three hold.
What it costs
Section titled “What it costs”Real costs, not caveats.
Every language you use is a dependency on the scanning host
Section titled “Every language you use is a dependency on the scanning host”cxg executes a template by invoking its language’s interpreter or toolchain. If
that program is not on PATH, the template cannot run.
A Python template needs python3, and any library it imports. A Go template
needs the Go compiler unless a cached build already exists. Writing detections
in eleven languages means a scanning host that can run eleven languages, and a
container image that carries all of them.
This is also why the templates you write for yourself should use as few
third-party libraries as possible. A template that imports requests will not
run on a host that has Python but not requests.
A missing runtime looks exactly like a clean scan
Section titled “A missing runtime looks exactly like a clean scan”This is the sharp edge, and it deserves more attention than it usually gets.
When a template’s runtime is missing, cxg logs a warning, records no finding, and
exits 0. The default output is indistinguishable from a scan that ran
everything and found nothing:
Findings by Severity: CRITICAL: 0 HIGH: 0 MEDIUM: 0 LOW: 0 INFO: 0
TOTAL: 0The reason only appears with -v:
WARN cert_x_gen::executor: Template php-probe failed for target 127.0.0.1: Execution error: Failed to execute command: No such file or directory (os error 2)WARN cert_x_gen::executor: Template go-probe3 failed for target 127.0.0.1: Execution error: Go compiler not foundBoth were produced by running cxg against a host missing PHP, and against a Go
template the toolchain had never compiled, with go removed from PATH. Each
warning is one line; the timestamp and thread id that precede it on every log
line are cut here, and the wrap is ours.
A false negative that reports as a clean result is worse than a crash. In CI,
run scans with -v and treat template execution failures as build failures. A
declarative YAML template cannot fail this way, because it has no runtime to be
missing.
Compiled languages add a build step
Section titled “Compiled languages add a build step”Templates in C, C++, Go, Java, and Rust are compiled before they run. cxg caches
the build, and only recompiles when the cached binary is missing or older than
the source. With go removed from PATH, a Go template compiled by an earlier
run still executed from that cache, while a copy of it saved under a new name,
never compiled and so never cached, failed with Go compiler not found in the
same scan.
The practical consequence is that a cold scanning host, a fresh CI container, pays compile time that a warm one does not, and needs the toolchains present even for templates that will later run from cache.
Templates run with your privileges, unsandboxed
Section titled “Templates run with your privileges, unsandboxed”This is the most important cost, and the one to understand before running a
template you did not write. There is no boundary between cxg and the template it
runs: the engine launches the interpreter or compiled binary as a child process
and reads its stdout. A probe run on cxg 1.3.0 read the invoking user’s ~/.ssh,
spawned a child process, and resolved an external host. Everything the user
could do, the template could do.
Neither any config key nor the cxg sandbox command changes this. The full
mechanism, the probe evidence, and the mitigations that actually apply are the
template trust model. Read it before
running third-party templates. In brief: place your isolation boundary outside
cxg (a container or VM, an unprivileged user), review code templates as the
scripts they are, and prefer YAML for anything you have not read.
Deciding
Section titled “Deciding”Reach for a code template when the check needs to remember something between requests, compute something, or prove something. Use YAML when it does not.
Then account for the cost: the runtime must exist on every host that runs the scan, its absence is silent, and the template runs with your privileges.

