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
- Flatten the include chain: move the deepest content up or merge files so nesting stays well under 32.
- Look for a rename-induced pseudo-cycle (file X includes a copy of itself under another name/path) and remove the duplicate.
- If you genuinely need more than 32 levels, restructure — the limit is intentional; raise maxIncludeDepth only as a last-resort fork.
- 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
- Keep include trees under ~5 levels; 32 is a runaway guard, not a budget.
- Beware self-include via renamed copies (cycle detection uses absolute paths).
- Generate-and-audit: if includes are machine-produced, log the chain while descending.
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
- circular include directive detected: %s
- validation failed for these fields
- both verbose and silent mode specified
- probe concurrency must be at least 1
- Invalid severity: %s
AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15).
Data as JSON: /api/errors/8d642c37bd69b600.
Report an issue: GitHub.