projectdiscovery/nuclei · error

include directive exceeded maximum include depth of %d

Error message

include directive exceeded maximum include depth of %d

What it means

The YAML preprocessor refuses to recurse past 32 levels of nested include directives. When an included file is itself YAML, readIncludedFile recursively preprocesses it; before recursing it checks depth >= maxIncludeDepth (a hard-coded constant of 32) and fails fast. This is a runaway-include guard — normal templates are only a few levels deep.

Source

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

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
		}
	}

	return includeFileContent, nil
}

func includePathKey(includeFileName string) string {
	includePath, err := filepath.Abs(includeFileName)
	if err != nil {
		return filepath.Clean(includeFileName)
	}
	if evaluatedPath, err := filepath.EvalSymlinks(includePath); err == nil {
		return evaluatedPath
	}

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Flatten the include chain: move the deepest content up or merge files so nesting stays well under 32.
  2. Look for a rename-induced pseudo-cycle (file X includes a copy of itself under another name/path) and remove the duplicate.
  3. If you genuinely need more than 32 levels, restructure — the limit is intentional; raise maxIncludeDepth only as a last-resort fork.
  4. Audit generated includes: run the preprocessor on the root file and print the include chain as it descends.

Example fix

# before: a1 includes a2 includes a3 ... includes a33 (33 files)
# -> include directive exceeded maximum include depth of 32

# after: merge a20..a33 into a single 'tail.yaml' so total depth <= 20
include: a19.yaml
# a19.yaml
include: tail.yaml
Defensive patterns

Strategy: validation

Validate before calling

// assert nesting stays shallow before running
func includeDepth(root string) (int, error) { /* walk include directives, return max depth */ }

Try / catch

if _, err := yamlutil.Preprocess(raw, path); err != nil {
    if strings.Contains(err.Error(), "maximum include depth") {
        // structural defect: flatten the tree, do not retry
    }
}

Prevention

When it happens

Trigger: A chain of YAML files each including the next that reaches 32 nesting levels; combined with a cycle that varies paths (e.g. generated filenames) so the circular-include check never fires; a script that auto-generates include files and keeps appending.

Common situations: Machine-generated template trees, deeply chained shared-credential or payload files, or an accidental self-include through a copy of the file under a different name (cycle detection keys on absolute path, so renamed copies evade it while depth keeps growing).

Related errors


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