projectdiscovery/nuclei · error

circular include directive detected: %s

Error message

circular include directive detected: %s

What it means

The YAML preprocessor detected a circular include chain while expanding include directives. readIncludedFile tracks every file currently on the include stack by absolute path (includePathKey); if the file being included is already on the stack, expansion aborts with this error naming the offending include path.

Source

Thrown at pkg/utils/yaml/preprocess.go:125

		// pad each line of file content with padBytes
		includeFileContent = bytes.ReplaceAll(includeFileContent, []byte("\n"), padBytes)

		// copy everything up to the directive (including its indentation), then
		// the expanded content, and resume after the directive.
		out.Write(data[lastEnd:matchStart])
		out.Write(includeFileContent)
		lastEnd = matchEnd
	}
	out.Write(data[lastEnd:])

	return out.Bytes(), nil
}

func readIncludedFile(includeFileName string, includeStack map[string]struct{}, depth int) ([]byte, error) {
	includePath := includePathKey(includeFileName)
	if _, ok := includeStack[includePath]; ok {
		return nil, fmt.Errorf("circular include directive detected: %s", includeFileName)
	}

	includeStack[includePath] = struct{}{}
	defer delete(includeStack, includePath)

	includeFileContent, err := os.ReadFile(includeFileName)
	if err != nil {
		return nil, err
	}

	// if it's yaml, tries to preprocess that too recursively
	if stringsutil.HasSuffixAny(includeFileName, extensions.YAML) {
		if depth >= maxIncludeDepth {
			return nil, fmt.Errorf("include directive exceeded maximum include depth of %d", maxIncludeDepth)
		}
		includeFileContent, err = preProcess(includeFileContent, includeFileName, includeStack, depth+1)
		if err != nil {
			return nil, err

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Map the include chain: the error names the file that closes the loop; walk includes from the root template to find the back-edge.
  2. Break the cycle by inlining the shared snippet or extracting the mutually-needed part into a third file that both A and B include one-way.
  3. Validate locally with the template-validate target or by loading the template through pkg/utils/yaml Preprocess before shipping.
  4. Never add an include of an ancestor file inside a snippet meant to be embedded.

Example fix

# before
# a.yaml
include: b.yaml
# b.yaml
include: a.yaml   # -> circular include directive detected: a.yaml

# after
# common.yaml holds the shared lines
# a.yaml
include: common.yaml
# b.yaml
include: common.yaml
Defensive patterns

Strategy: validation

Validate before calling

// pre-validate an include tree before preprocessing
func detectCycle(root string, seen map[string]bool) error {
    abs, _ := filepath.Abs(root)
    if seen[abs] { return fmt.Errorf("circular include at %s", abs) }
    seen[abs] = true
    defer delete(seen, abs)
    data, err := os.ReadFile(root)
    if err != nil { return err }
    for _, m := range includeRe.FindAllStringSubmatch(string(data), -1) {
        if err := detectCycle(filepath.Join(filepath.Dir(root), m[1]), seen); err != nil { return err }
    }
    return nil
}

Try / catch

content, err := yamlutil.Preprocess(raw, path)
if err != nil && strings.Contains(err.Error(), "circular include") {
    // template defect: fail validation, never ship/retry
    return fmt.Errorf("bad template %s: %w", path, err)
}

Prevention

When it happens

Trigger: Template A includes B and B includes A (directly or through a longer chain like A→B→C→A); a file including itself; two files including each other via different relative paths that resolve to the same absolute path.

Common situations: Refactoring nuclei template YAML files and accidentally creating mutual references; a shared 'common' file that was later taught to include one of its consumers; symlinks/duplicate paths making a diamond look like a cycle is NOT flagged (only true cycles are), so this almost always means a real loop.

Related errors


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