| id | 82 |
|---|---|
| title | YAML billion-laughs mitigation |
| status | ✅ |
| summary | Reject YAML anchor/alias syntax in user-supplied content before unmarshalling to prevent exponential memory expansion. |
Prevent OOM from YAML front matter or directive bodies
that use anchor/alias expansion. These create
exponential memory growth during yaml.Unmarshal.
gopkg.in/yaml.v3 has no alias-expansion limit.
A 1 KB YAML with 8 levels of nested aliases can expand
to 10^8 strings. Byte-length caps on input do not
prevent this — the attack uses small input.
Prettier and remark-lint use eemeli/yaml which
defaults to maxAliasCount: 100. Vale uses
yaml.v2 v2.4.0 which back-ported alias-depth fixes.
markdownlint avoids the issue by not parsing YAML at
all. textlint (js-yaml v4) is vulnerable like mdsmith.
Legitimate Markdown front matter virtually never uses YAML anchors or aliases.
Before any yaml.Unmarshal call on user-supplied
content, scan the raw bytes for YAML anchor (&) or
alias (*) characters. If found, return an error
diagnostic rather than proceeding to unmarshal.
func RejectYAMLAliases(data []byte) error {
if bytes.ContainsAny(data, "&*") {
return fmt.Errorf(
"YAML anchors/aliases are not permitted")
}
return nil
}A bare & or * can appear in YAML string values
(e.g., title: "Q&A"). To reduce false positives,
only reject when & or * appears in a YAML
structural position:
&followed by a YAML identifier (anchor definition): pattern&\w*followed by a YAML identifier (alias reference): pattern\*\wat the start of a value
Use a simple regex or byte scan, not a full YAML parser.
All yaml.Unmarshal sites processing user-supplied
content (13 total):
internal/archetype/gensection/parse.go:189(directive YAML body)internal/rules/catalog/rule.go:415(per-file front matter)internal/rules/requiredstructure/rule.go:220,233,556,924(schema front matter, require directives)cmd/mdsmith/main.go:352(querysubcommand front matter)internal/config/load.go:22,35(config file — lower risk, operator-controlled)internal/corpus/config.go:35,82(corpus config — internal tooling)
Sites 1–4 are high priority (user-supplied .md
content). Sites 5–6 are lower priority (operator-
controlled config files) but should be guarded for
defense in depth.
Use goccy/go-yaml with yaml.WithMaxAliasesNum(100)
to mirror eemeli/yaml. This is a larger dependency
change. Defer unless the pre-scan produces false
positives.
- Add
internal/lint/yamlsafe.gowithRejectYAMLAliases(data []byte) error - Add
internal/lint/yamlsafe_test.gowith tests for clean YAML, anchor YAML, alias YAML,Q&Ain string values (false positive check) - Guard all 11
yaml.Unmarshalcall sites with aRejectYAMLAliasescheck before unmarshalling - Add integration test:
.mdfile with YAML anchor front matter produces a clear error diagnostic
- YAML with
&anchor/*aliasis rejected before unmarshalling - Legitimate front matter with
&in string values (e.g.,"Q&A") is accepted - All 11
yaml.Unmarshalsites are guarded - Error message clearly states anchors/aliases are not permitted
- All tests pass:
go test ./... -
go tool golangci-lint runreports no issues