Summary
maxLength validation (and every other non-function validator) is silently skipped for any string node whose value merely contains dynamic-reference-shaped text — even when the string is not a dynamic reference at all, e.g. prose in a Description that happens to mention {{resolve:ssm:...}}.
The result is that genuine CloudFormation limit violations pass cfn-lint and only surface at CreateChangeSet.
Version
cfn-lint 1.32.1
Mechanism
cfnlint/helpers.py — REGEX_DYN_REF = re.compile(r"^.*{{\s*(resolve:.+)\s*}}.*$") matches the substring anywhere in any string, with no positional or contextual constraint.
cfnlint/jsonschema/_filter.py — FunctionFilter.filter() sees the match, substitutes a dynamicReference schema and returns early, bypassing the remaining validators for that string, including maxLength.
Reproduction
Two templates, identical except that one mentions dynamic-reference syntax in prose. Both have a template-level Description well over CloudFormation's 1024-character limit.
A — flagged correctly (E1003):
AWSTemplateFormatVersion: '2010-09-09'
Description: >
<1500 characters of plain prose>
Resources:
Dummy:
Type: AWS::CloudFormation::WaitConditionHandle
B — silently unchecked:
AWSTemplateFormatVersion: '2010-09-09'
Description: >
<the same 1500 characters, but mentioning {{resolve:ssm:/some/path}} somewhere in the prose>
Resources:
Dummy:
Type: AWS::CloudFormation::WaitConditionHandle
Expected: both report E1003 for the over-length Description.
Actual: A reports E1003; B reports nothing. Deploying B fails at change-set creation with Template format error: 'Description' length is greater than 1024.
Why this is easy to hit
Documenting a dynamic reference in a Description, AlarmDescription or parameter description is natural. We hit it writing prose that explained a {{resolve:ssm:...}} behaviour — the text describing the feature disabled the rule that would have caught its own length.
Suggested fixes
- Constrain
REGEX_DYN_REF so it only matches where a dynamic reference is structurally valid, rather than anywhere in any string.
- Or have
FunctionFilter.filter() continue applying non-function validators (maxLength, pattern, …) after substituting the dynamicReference schema, rather than returning early.
(2) seems the safer minimal change: a string containing a dynamic reference still has a template-text length that CloudFormation enforces.
Workaround
A local custom rule re-asserting the documented length limits independently of the dynamic-ref filter.
Summary
maxLengthvalidation (and every other non-function validator) is silently skipped for any string node whose value merely contains dynamic-reference-shaped text — even when the string is not a dynamic reference at all, e.g. prose in aDescriptionthat happens to mention{{resolve:ssm:...}}.The result is that genuine CloudFormation limit violations pass
cfn-lintand only surface atCreateChangeSet.Version
cfn-lint 1.32.1Mechanism
cfnlint/helpers.py—REGEX_DYN_REF = re.compile(r"^.*{{\s*(resolve:.+)\s*}}.*$")matches the substring anywhere in any string, with no positional or contextual constraint.cfnlint/jsonschema/_filter.py—FunctionFilter.filter()sees the match, substitutes adynamicReferenceschema and returns early, bypassing the remaining validators for that string, includingmaxLength.Reproduction
Two templates, identical except that one mentions dynamic-reference syntax in prose. Both have a template-level
Descriptionwell over CloudFormation's 1024-character limit.A — flagged correctly (E1003):
B — silently unchecked:
Expected: both report
E1003for the over-lengthDescription.Actual: A reports
E1003; B reports nothing. Deploying B fails at change-set creation withTemplate format error: 'Description' length is greater than 1024.Why this is easy to hit
Documenting a dynamic reference in a
Description,AlarmDescriptionor parameter description is natural. We hit it writing prose that explained a{{resolve:ssm:...}}behaviour — the text describing the feature disabled the rule that would have caught its own length.Suggested fixes
REGEX_DYN_REFso it only matches where a dynamic reference is structurally valid, rather than anywhere in any string.FunctionFilter.filter()continue applying non-function validators (maxLength,pattern, …) after substituting thedynamicReferenceschema, rather than returning early.(2) seems the safer minimal change: a string containing a dynamic reference still has a template-text length that CloudFormation enforces.
Workaround
A local custom rule re-asserting the documented length limits independently of the dynamic-ref filter.