{"id":"6430f203e5d182be","repo":"gohugoio/hugo","slug":"too-many-yaml-aliases-for-non-scalar-nodes","errorCode":null,"errorMessage":"too many YAML aliases for non-scalar nodes","messagePattern":"too many YAML aliases for non-scalar nodes","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"parser/metadecoders/decoder.go","lineNumber":231,"sourceCode":"// we can easily get a delayed laughter when we try to render this very big structure later,\n// e.g. via RenderString.\nfunc validateAliasLimitForCollections(v any, limit int) error {\n\tif limit <= 0 {\n\t\tlimit = 1000\n\t}\n\n\tcollectionRefCounts := make(map[uintptr]int)\n\n\tcheckCollectionRef := func(v *any) error {\n\t\t// Conversion of a Pointer to a uintptr (but not back to Pointer) is considered safe.\n\t\t// See https://pkg.go.dev/unsafe#pkg-functions\n\t\tptr := uintptr(unsafe.Pointer(v))\n\t\tif ptr == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tcollectionRefCounts[ptr]++\n\t\tif collectionRefCounts[ptr] > limit {\n\t\t\treturn fmt.Errorf(\"too many YAML aliases for non-scalar nodes\")\n\t\t}\n\t\treturn nil\n\t}\n\n\tvar validate func(v any) error\n\tvalidate = func(v any) error {\n\t\tswitch vv := v.(type) {\n\t\tcase *map[string]any:\n\t\t\tif err := checkCollectionRef(&v); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor _, vvv := range *vv {\n\t\t\t\tif err := validate(vvv); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tcase map[string]any:\n\t\t\tif err := checkCollectionRef(&v); err != nil {","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/parser/metadecoders/decoder.go#L213-L249","documentation":"After YAML unmarshaling, Hugo counts how often each non-scalar (map/slice) node is referenced via YAML aliases and fails if any exceeds a size-scaled limit (100 for files under ~2 KB, up to 10,000 for larger files). This defends against Billion-Laughs-style expansion attacks: goccy/go-yaml itself isn't vulnerable at parse time, but rendering the massively self-referencing structure later (e.g. via RenderString) would be (see goccy/go-yaml issue 461).","triggerScenarios":"Parsing YAML front matter, data files, or config where a single anchored map or sequence (&anchor) is referenced with *alias more times than calculateCollectionAliasLimit allows for the file's size — most aggressively in small files (<2 KB, limit 100), which is where malicious payloads live.","commonSituations":"Legitimate data files that use one shared anchor (e.g. a common defaults map) across thousands of rows in a small file; security scanners or fuzzers feeding Billion Laughs payloads; content pipelines that generate alias-heavy YAML. Introduced alongside the goccy/go-yaml migration in Hugo v0.152.0, so files that worked on older Hugo may newly fail.","solutions":["Reduce alias usage: expand the shared anchor inline for some entries, or restructure so each row stores only its own data and the shared defaults are merged in templates instead.","Split one small alias-dense file into larger or multiple files — the limit scales with file size (100/<2KB, 5000/<10KB, 10000 above), so padding legitimate data into a larger file raises the cap.","Convert the data file to JSON or TOML, which have no aliases and thus bypass the check.","If the file is untrusted input, treat this as the guard working correctly — do not attempt to bypass it."],"exampleFix":"# before (small file, one anchor aliased hundreds of times)\ndefaults: &d {a: 1, b: 2}\nrows:\n  - *d\n  - *d\n  # ... 200 more\n\n# after (store rows plainly; merge defaults in the template)\ndefaults: {a: 1, b: 2}\nrows:\n  - {}\n  - {}","handlingStrategy":"validation","validationCode":"# Pre-scan YAML for alias-expansion abuse before feeding it to Hugo\ngrep -c '\\*[A-Za-z0-9_]' data.yaml   # large counts of aliases on mappings/sequences are suspect","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"too many YAML aliases\") {\n    // reject the document as untrusted (billion-laughs style); do not raise limits\n}","preventionTips":["Treat this as a security guard against billion-laughs YAML bombs — don't work around it","Avoid YAML anchors/aliases on maps and lists in your own data files; duplicate the data or restructure","Never feed untrusted user-supplied YAML into Hugo data files without pre-validation"],"tags":["hugo","yaml","security","billion-laughs","data-files"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}