Repository: aws-cloudformation/cfn-lint

Issue:
Regression in version 1.35.0: E3002 Additional properties are not allowed ('Fn::Transform' was unexpected)
### CloudFormation Lint Version

1.35.0

### What operating system are you using?

Debian (wsl2)

### Describe the bug

There appears to be some regression in version 1.35.0. A template that is using `Fn::Transform` is giving an error `E3002 Additional properties are not allowed ('Fn::Transform' was unexpected)` since version 1.35.0. In version 1.34.2 it was parsed correctly, and CloudFormation deploys it without complaining.

Note that an error is only shown for the second `Fn::Transform` in the reproduction template. Maybe this is because it is in a list? Just a guess.

### Expected behavior

Template should be parsed without error.

### Reproduction template

```yaml
AWSTemplateFormatVersion: 2010-09-09

Resources:
  ManagedLoginBranding:
    Type: AWS::Cognito::ManagedLoginBranding
    Properties:
      UserPoolId: XXXXXX
      ClientId: YYYYYY
      UseCognitoProvidedValues: false
      Settings:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location: settings.json
      Assets:
        - Category: FAVICON_SVG
          ColorMode: LIGHT
          Extension: SVG
          Fn::Transform:
            Name: AWS::Include
            Parameters:
              Location: favicon.json
```

Discussion:

Solution patch:
diff --git a/src/cfnlint/helpers.py b/src/cfnlint/helpers.py
index 0f012899b..046d725bd 100644
--- a/src/cfnlint/helpers.py
+++ b/src/cfnlint/helpers.py
@@ -203,6 +203,7 @@ FUNCTION_REF_ALL = "Fn::RefAll"
 FUNCTION_VALUE_OF = "Fn::ValueOf"
 FUNCTION_VALUE_OF_ALL = "Fn::ValueOfAll"
 FUNCTION_FOR_EACH = re.compile(r"^Fn::ForEach::[a-zA-Z0-9]+$")
+FUNCTION_TRANSFORM = "Fn::Transform"
 
 FUNCTION_CONDITIONS = frozenset(
     [FUNCTION_AND, FUNCTION_OR, FUNCTION_NOT, FUNCTION_EQUALS]
diff --git a/src/cfnlint/jsonschema/_keywords_cfn.py b/src/cfnlint/jsonschema/_keywords_cfn.py
index e6fd955b2..83ef9f261 100644
--- a/src/cfnlint/jsonschema/_keywords_cfn.py
+++ b/src/cfnlint/jsonschema/_keywords_cfn.py
@@ -20,22 +20,49 @@ from __future__ import annotations
 
 from typing import Any
 
+import regex as re
+
 import cfnlint.jsonschema._keywords as validators_standard
-from cfnlint.helpers import BOOLEAN_STRINGS, ensure_list, is_function
+from cfnlint.helpers import (
+    BOOLEAN_STRINGS,
+    FUNCTION_FOR_EACH,
+    FUNCTION_TRANSFORM,
+    ensure_list,
+    is_function,
+)
 from cfnlint.jsonschema import ValidationError, Validator
 from cfnlint.jsonschema._typing import V, ValidationResult
 
+_ap_exception_fns = set([FUNCTION_TRANSFORM, FUNCTION_FOR_EACH])
+
 
 def additionalProperties(
     validator: Validator, aP: Any, instance: Any, schema: Any
 ) -> ValidationResult:
+    # is function will just return if one item is present
+    # as this is the standard. We will handle exceptions below
     k, _ = is_function(instance)
     if k in validator.context.functions:
         return
     for err in validators_standard.additionalProperties(
         validator, aP, instance, schema
     ):
-        yield err
+        # Some functions can exist at the same level
+        # so we need to validate that if those functions are
+        # currently supported by the context and are part of the
+        # error
+
+        # if the path is 0 just yield the error and return
+        # this should never happen
+        if not len(err.path) > 0:  # pragma: no cover
+            yield err  # pragma: no cover
+            return  # pragma: no cover
+
+        for fn in list(_ap_exception_fns & set(validator.context.functions)):
+            if re.fullmatch(fn, str(err.path[0])):  # type: ignore
+                break
+        else:
+            yield err
 
 
 def cfnContext(


Test patch:
diff --git a/test/unit/module/jsonschema/test_keywords_cfn.py b/test/unit/module/jsonschema/test_keywords_cfn.py
index 399ce7799..b46ce0d76 100644
--- a/test/unit/module/jsonschema/test_keywords_cfn.py
+++ b/test/unit/module/jsonschema/test_keywords_cfn.py
@@ -190,6 +190,13 @@ def test_cfn_context_with_refs(instance, schema, mock_resolver, expected, valida
                 )
             ],
         ),
+        # Test case where function matches and should be allowed
+        (
+            {"Foo": True, "Fn::Transform": {}},
+            {"additionalProperties": False, "properties": {"Foo": {}}},
+            ["Fn::Transform"],
+            [],
+        ),
         # Test with empty instance
         (
             {},