projectdiscovery/nuclei · error

unresolved DSL placeholders must be inside string literals

Error message

unresolved DSL placeholders must be inside string literals

What it means

Runtime resolution error from resolveDSLStringMarkers (pkg/operators/matchers/dsl_string_markers.go:45). When a DSL matcher expression still contains unresolved {{...}} markers after compile-time variable substitution, nuclei attempts to substitute them only inside string literals of the expression. If findDSLStringMarkers returns zero markers — the expression has unresolved markers but none resided in string literals — this error aborts the matcher with that message.

Source

Thrown at pkg/operators/matchers/dsl_string_markers.go:45

}

type stringLiteralSpan struct {
	start int
	end   int
	quote byte
}

func resolveDSLStringMarkers(expression string, data map[string]interface{}) (string, error) {
	// Resolve only marker spans already present in the compiled DSL source.
	// Values are escaped for the surrounding string literal before recompilation.
	stringSpans := findStringLiteralSpans(expression)
	markers, err := findDSLStringMarkers(expression, data, stringSpans)
	if err != nil {
		return "", err
	}

	if len(markers) == 0 {
		return "", fmt.Errorf("unresolved DSL placeholders must be inside string literals")
	}

	sort.Slice(markers, func(i, j int) bool {
		return markers[i].start > markers[j].start
	})

	resolved := expression
	for _, marker := range markers {
		result, err := render.Render(render.Input{
			Text:   "{{" + marker.expr + "}}",
			Values: data,
		})
		if err != nil {
			return "", err
		}

		replacement := expressions.EscapeStringValue(result.Text, marker.quote)
		resolved = resolved[:marker.start] + replacement + resolved[marker.end:]

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Quote the placeholder so it is a string literal: `"'{{payload}}' == '1'"` style, letting the resolver substitute inside quotes
  2. Fix the variable name so the marker binds at compile time and disappears entirely
  3. Precompute the value into a named variable (internal:true extractor + {{var}}) instead of inlining a raw marker in the DSL expression
  4. Re-run with -v to see the exact expression after partial substitution and confirm quoting

Example fix

# before
dsl:
  - '{{myvar}} == "admin"'
# after
dsl:
  - "'{{myvar}}' == 'admin'"
Defensive patterns

Strategy: validation

Validate before calling

// lint: any {{...}} in a dsl matcher must sit between quotes
var bareMarker = regexp.MustCompile(`(^|[^"'])\{\{[^{}]+\}\}($|[^"'])`)
if bareMarker.MatchString(dslExpr) {
	return fmt.Errorf("dsl %q contains an unquoted placeholder", dslExpr)
}

Type guard

func dslMarkersQuoted(expr string) bool { return !bareMarkerRE.MatchString(expr) }

Try / catch

// errors surface at match time; log and drop to no-match, then fix template:
if err := m.Match(data); err != nil && strings.Contains(err.Error(), "unresolved DSL placeholders") {
	gologger.Warn().Msgf("template bug in %s: quote {{...}} inside dsl", tplID)
}

Prevention

When it happens

Trigger: A DSL matcher like `dsl: ["{{payload}} == '1'"]` where `{{payload}}` sits outside quotes, and `payload` is not a known govaluate variable/base value at evaluation time, so the raw marker survives to this stage; the marker scan then finds no in-literal candidates and len(markers)==0 triggers the error.

Common situations: Fuzzing/payload templates where generator variables are referenced unquoted inside DSL expressions; marker typos ({{Paylaod}}) that never bind; referencing dynamic extractor names before they are defined; expressions mixing govaluate identifiers with template markers incorrectly.

Related errors


AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15). Data as JSON: /api/errors/2ec1dafea522dbfe. Report an issue: GitHub.