Skip to content

Guard against YAML alias amplification (billion laughs) - #4670

Merged
kddejong merged 4 commits into
mainfrom
fix/yaml-alias-expansion
Sep 3, 2026
Merged

kddejong merged 4 commits into
mainfrom
fix/yaml-alias-expansion

Conversation

@kddejong

@kddejong kddejong commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

CloudFormation itself rejects YAML anchors/aliases, but the AWS CLI aws cloudformation package command and AWS SAM resolve them client-side before sending the template to CloudFormation. cfn-lint mirrors that and resolves aliases too.

Because aliases resolve to a single shared object referenced from many places, a small template using nested aliases (the classic "billion laughs" pattern) forms a DAG that flattens into an exponentially large tree. Any consumer that walks the template as a tree — the AWS::LanguageExtensions transform, Template.search_deep_keys, the JSON-Schema validators, JSON serialization — re-expands it and consumes CPU without bound. It is not infinite recursion (the aliases are acyclic; self-referential ones are already rejected as E0000), just exponential work: a ~640-byte template can expand to 500k+ nodes.

Changes

  • Load-time guard (decode/cfn_yaml.py): compute the alias-resolved (flattened) node count in O(distinct nodes) via memoization on object identity, and reject templates whose expansion exceeds 250_000 nodes with a clear E0000. This bounds the amplification at the single point where the template is loaded, so no downstream consumer ever receives an exponential structure. The limit sits ~5x above the largest deployable template (CloudFormation's ~1MB template ≈ 50k nodes).
  • New rule W1101 (rules/aws_cli/UsingAlias.py), modeled on W1100 (YAML merge): warn when a template uses YAML aliases, since such templates can only be deployed via package/SAM. It uses a visited-set walk so it is itself safe on shared DAGs. Templates still lint.

Testing

  • New unit tests for W1101 (good template, alias, alias-used-more-than-once).
  • A billion-laughs template that previously hung >60s now fails fast (~0.6s) with E0000.
  • A normal alias template emits W1101 and continues linting.
  • W1101 produces no findings across the existing good/ fixtures.
  • Full unit suite passes (remaining failures are pre-existing/environmental — missing pydot/pygraphviz/defusedxml).

CloudFormation rejects YAML aliases, but the AWS CLI 'package' command
and SAM resolve them client-side, so cfn-lint resolves them too. A small
template using nested aliases can expand into an exponentially large
structure that exhausts CPU in any consumer that walks the template.

- Add a load-time guard that computes the alias-resolved node count in
  O(distinct nodes) via memoization and rejects templates whose expansion
  exceeds 250k nodes, bounding the amplification at a single choke point.
- Add rule W1101 to warn when YAML aliases are used, since such templates
  can only be deployed via 'aws cloudformation package' or SAM. The
  template still lints.
- decode tests: alias amplification rejected (E0000), reasonable alias use
  allowed, non-alias templates unaffected, and a direct guard-function test
- end-to-end (lint_all) tests: amplification fails fast with only E0000;
  a normal alias warns W1101 and still lints
- integration fixtures + scenarios for both the amplification (exit 2) and
  the normal-alias warning (exit 4) paths
A YAML alias requires a matching '&' anchor, so a source with no '&'
cannot contain aliases. Gate the expansion walk behind a cheap substring
check so templates that define no anchors (the vast majority) skip it
entirely instead of paying for a full extra pass on every decode.
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.64%. Comparing base (de755aa) to head (0763937).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4670      +/-   ##
==========================================
+ Coverage   94.62%   94.64%   +0.01%     
==========================================
  Files         432      433       +1     
  Lines       15448    15503      +55     
  Branches     2995     3008      +13     
==========================================
+ Hits        14618    14673      +55     
  Misses        453      453              
  Partials      377      377              
Flag Coverage Δ
unittests 94.64% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kddejong
kddejong merged commit ea30ed7 into main Sep 3, 2026
29 checks passed
@kddejong
kddejong deleted the fix/yaml-alias-expansion branch September 3, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant