cdk-preflight 1.7.3 → 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.jsii CHANGED
@@ -9626,7 +9626,7 @@
9626
9626
  },
9627
9627
  "name": "cdk-preflight",
9628
9628
  "readme": {
9629
- "markdown": "<p align=\"center\">\n <img src=\"https://raw.githubusercontent.com/badmintoncryer/cdk-preflight/main/assets/logo.png\" alt=\"cdk-preflight\" width=\"104\" height=\"104\">\n</p>\n\n<h1 align=\"center\">cdk-preflight</h1>\n\n<p align=\"center\">\n <strong>Catch deploy-time CloudFormation failures at <code>cdk synth</code> time.</strong>\n</p>\n\n<p align=\"center\">\n <a href=\"https://github.com/badmintoncryer/cdk-preflight/actions/workflows/monthly-verify.yml\"><img src=\"https://github.com/badmintoncryer/cdk-preflight/actions/workflows/monthly-verify.yml/badge.svg\" alt=\"monthly real-deploy verification\"></a>\n <a href=\"https://www.npmjs.com/package/cdk-preflight\"><img src=\"https://img.shields.io/npm/v/cdk-preflight.svg\" alt=\"npm version\"></a>\n <a href=\"https://www.npmjs.com/package/cdk-preflight\"><img src=\"https://img.shields.io/npm/dt/cdk-preflight.svg\" alt=\"npm total downloads\"></a>\n <a href=\"docs/rules.md\"><img src=\"https://img.shields.io/badge/rules-3230-blue\" alt=\"3230 bundled rules\"></a>\n</p>\n\nSome CloudFormation constraints are not expressed in resource provider schemas — they live only in documentation, in service API validation, or across multiple properties. Templates that violate them pass `cdk synth`, pass CloudFormation pre-deployment validation, and then fail minutes into a deployment, burning a rollback cycle.\n\ncdk-preflight is a curated [Rego rule pack](docs/rules.md) for exactly those constraints, evaluated with the CloudFormation validation engine that ships inside `aws-cdk-lib` (>= 2.267.0). By default a violation **fails `cdk synth`** — a template that is known to fail at deploy time never leaves your machine.\n\nThe pack aims at **every deploy-time failure, and every post-deploy run that is certain to fail, that no existing CDK mechanism already catches** — nothing narrower. Every bundled rule is backed by a `fail`/`pass` template pair, and the failure has been reproduced against real AWS. The handful of rules that could not be reproduced are marked `doc-only` and report as **warnings**, as does a rule whose remaining false positives cannot be told from the template (a cross-account Lambda layer the owner may have shared): they show up in the validation report but never fail synth. Rules that the built-in validation engine already covers are deliberately **not** duplicated — a test suite enforces this, and a rule the engine catches up with later is skipped on engines that cover it (see [Scope and rule lifecycle](#scope-and-rule-lifecycle)).\n\n> **Requires `aws-cdk-lib` >= 2.267.0** (released 2026-08-27) — the first release that bundles\n> the CloudFormation validation engine. On older versions the rules cannot run at all.\n\n## Quick start\n\n```bash\nnpm i -D cdk-preflight\nnpx cdkpf init # inserts Preflight.apply(app) into your CDK app\n # (`npx cdkpf init` is the same command, shorter)\n```\n\nor add one line yourself:\n\n```ts\nimport { Preflight } from 'cdk-preflight';\n\nconst app = new App();\nPreflight.apply(app);\n```\n\nOn violation, `cdk synth` fails with one error per finding, including the construct trace:\n\n```text\nERROR idle_timeout.timeout_seconds is 5000 but must be between 1 and 4000 seconds (cdk-preflight)\n MyStack/Alb/Resource (Alb16C2F182) aws-cdk-lib.aws_elasticloadbalancingv2.CfnLoadBalancer\n\nSynthesis finished with errors\n```\n\n## What it catches\n\nFour ordinary-looking snippets. All of them pass `cdk synth` and CloudFormation\npre-deployment validation, and all of them fail minutes into a deployment:\n\n```ts\n// 1) pf-iam-inline-policy-size — enumerate buckets, grant each one, blow past 10,240 chars\n// \"Maximum policy size of 10240 bytes exceeded for role IngestRole\"\n// (via role.addToPolicy the CDK auto-splits into managed policies instead,\n// and you hit the 6,144-char limit as pf-iam-managed-policy-size)\nnew iam.Policy(this, 'IngestPolicy', {\n roles: [role],\n statements: [new iam.PolicyStatement({\n actions: ['s3:GetObject', 's3:ListBucket'],\n resources: Array.from({ length: 200 },\n (_, i) => `arn:aws:s3:::data-lake-landing-zone-${i}/year=*/month=*/*`),\n })],\n});\n\n// 2) pf-lambda-env-size — a config blob in the environment, over the 4KB total\n// \"Lambda was unable to configure your environment variables because the\n// environment variables you have provided exceeded the 4KB limit\"\nnew lambda.Function(this, 'Fn', {\n runtime: lambda.Runtime.NODEJS_22_X,\n handler: 'index.handler',\n code: lambda.Code.fromInline('exports.handler = async () => {};'),\n environment: { FEATURE_FLAGS: JSON.stringify(bigFeatureFlagMap) },\n});\n\n// 3) pf-sfn-asl-missing-state (+ pf-sfn-asl-unreachable-state) — a typo in a state name\n// \"Invalid State Machine Definition: 'MISSING_TRANSITION_TARGET: ...'\"\nnew sfn.StateMachine(this, 'Pipeline', {\n definitionBody: sfn.DefinitionBody.fromString(JSON.stringify({\n StartAt: 'Validate',\n States: {\n Validate: { Type: 'Pass', Next: 'Transform' },\n Trasform: { Type: 'Pass', End: true }, // typo: Transform\n },\n })),\n});\n\n// 4) pf-logs-filter-pattern-bracket — a filter pattern opened with '[' and never closed\n// \"If a filter pattern starts with '[' it must end with ']'\"\nnew logs.MetricFilter(this, 'ErrorFilter', {\n logGroup,\n metricNamespace: 'Pipeline',\n metricName: 'Errors',\n filterPattern: logs.FilterPattern.literal('[time, level=ERROR, msg'),\n});\n```\n\nNone of these are type errors, so the L2 constructs accept them; none of them are\nexpressible in a resource schema, so CloudFormation accepts the template. With\n`Preflight.apply(app)` in place they fail `cdk synth` instead.\n\n## Observe-only mode\n\nTo roll the rules out gradually, start with `enforce: false`: findings then surface as synth **warnings** through the CDK built-in validator, with construct traces and per-finding acknowledgement:\n\n```ts\nPreflight.apply(app, { enforce: false });\n```\n\n```text\nWARNING idle_timeout.timeout_seconds is 5000 but must be between 1 and 4000 seconds (CloudFormation Validate)\n MyStack/Alb (Alb) aws-cdk-lib.aws_elasticloadbalancingv2.CfnLoadBalancer\n Acknowledge with 'CloudFormation-Validate::pf-elbv2-lb-idle-timeout-range'\n```\n\n> **Known limitation with stages.** The AWS CDK CLI drops validation findings for stacks nested in a `Stage`\n> before printing them, so in observe-only mode those findings appear **only** in `cdk.out/validation-report.json`\n> and never on the console. Enforce mode is not affected: cdk-preflight reports such findings itself and fails\n> synthesis. This is a CLI-side bug (present since aws-cdk 2.1128.1), not a rule evaluation problem.\n\n> **If the rules cannot run, the build stops.** When the evaluation engine fails on a template (a rule pack that\n> does not compile, an engine bug), enforce mode reports it as a violation named `pf-engine-error` and fails\n> synthesis for that stack instead of passing green with no rule having run. The other stacks keep their rules.\n> `pf-engine-error` is not a bundled rule and cannot be `exclude`d; `enforce: false` unblocks the build if you\n> need one.\n\n| Option | Default | Effect |\n|---|---|---|\n| `enforce` | `true` | Violations of bundled rules fail synthesis; set to `false` to only warn |\n| `strict` | `false` | With `enforce`: also fail on error-class findings (`ERROR`/`FATAL`, e.g. `F3034`) of the built-in validation engine itself, which the CDK currently downgrades to warnings |\n| `exclude` | `[]` | Rule ids to disable |\n| `includeUpstreamPending` | `true` | Include rules already proposed to the upstream engine but not yet merged |\n| `checkQuotas` | `false` | Also check the target account's Service Quotas and current usage (calls AWS at synth time, see below) |\n| `quotaCacheTtl` | 5 minutes | How long `checkQuotas` reuses the usage and deployed-stack state it fetched |\n\nTo opt out of a single rule everywhere, pass its id in `exclude`. To suppress a\nsingle *finding* on one construct, acknowledge it — this works in both modes, the\nid prefix just differs (`cdk-preflight::` when enforcing, `CloudFormation-Validate::`\nin observe-only, as printed in the warning text):\n\n```ts\ncdk.Validations.of(errorFilter).acknowledge({\n id: 'cdk-preflight::pf-logs-filter-pattern-bracket',\n reason: 'log group is written by a legacy producer; pattern is fixed upstream',\n});\n```\n\n## Service quota check (opt-in)\n\nThe rules only read the template, so they never assert a limit an account can raise. `checkQuotas: true` covers that\ngap for the quotas a CDK app runs into most: it adds up what every stack in the app (nested stacks included) is about\nto create in each account and Region, and compares that against the account's applied quota and current usage.\n\n```ts\nPreflight.apply(app, { checkQuotas: true });\n```\n\n| Check | Counts | Against |\n|---|---|---|\n| `pf-quota-vpcs-per-region` | new `AWS::EC2::VPC` | VPCs per Region (`vpc/L-F678F1CE`) |\n| `pf-quota-internet-gateways-per-region` | new `AWS::EC2::InternetGateway` | Internet gateways per Region (`vpc/L-A4707A72`) |\n| `pf-quota-elastic-ips-per-region` | new `AWS::EC2::EIP` (one per NAT gateway in a default `ec2.Vpc`) | EC2-VPC Elastic IPs (`ec2/L-0263D0A3`) |\n| `pf-quota-lambda-unreserved-concurrency` | increase in `ReservedConcurrentExecutions` | Lambda keeps at least 100 concurrent executions unreserved |\n\n- **Only new resources count.** The deployed template of each stack is read with `GetTemplate`; a resource whose\n logical id and type are already deployed is not counted again, so redeploying an unchanged app never trips the\n check. Resources behind a `Condition` and the brief overlap during a replacement are not counted.\n- **A finding fails synth** in enforce mode and is a warning with `enforce: false`. Acknowledge one like any other\n finding (`cdk-preflight::pf-quota-...`; `cdk-preflight-quotas::pf-quota-...` with `enforce: false`). The ids also work in `exclude`, including `pf-quota-check-skipped` to silence the skip warning.\n- **It needs credentials in the app process.** The CDK CLI does not pass `--profile` on to the app, so set\n `AWS_PROFILE` (or `AWS_*` variables) instead. When the credentials are missing, lack permissions, or belong to a\n different account than the stack's, the check is skipped with a `pf-quota-check-skipped` warning and synth goes on.\n- **Permissions:** `servicequotas:GetServiceQuota`, `servicequotas:GetAWSDefaultServiceQuota`, `ec2:DescribeVpcs`,\n `ec2:DescribeInternetGateways`, `ec2:DescribeAddresses`, `lambda:GetAccountSettings`, `cloudformation:GetTemplate`,\n `cloudformation:DescribeStackResource`. All read-only (`ReadOnlyAccess` covers them). The check also calls\n `sts:GetCallerIdentity`, which IAM never denies.\n- **Results are cached** in `cdk.out/.cdk-preflight/quota-cache.json`: usage and deployed templates for\n `quotaCacheTtl` (5 minutes), quota values for 24 hours. Delete the file to force a refetch.\n- Usage can change between synth and deploy (another team deploying to the same account), so a clean check is not a\n guarantee. The reverse also happens: EC2 does not always enforce the Elastic IP quota when CloudFormation\n creates several addresses in parallel (measured 2026-10-16: with 4 of 5 in use, two parallel `AWS::EC2::EIP` both\n succeeded), so a template the check stops may occasionally deploy. The check follows the quota, not the race.\n\n## Bundled rules\n\nSee [docs/rules.md](docs/rules.md) for the generated rule table.\n\n<!-- supported-resources:start -->\n<details>\n<summary><b>414 resource types across 77 services</b> — click to expand</summary>\n\nResource names are relative to `AWS::<Service>::`; the number in parentheses is how many rules target that type.\n\n| Service | Resource types |\n|---|---|\n| **(any resource type)** | `*` (14) |\n| **ApiGateway** | `ApiKey` (1), `Authorizer` (5), `Deployment` (1), `DocumentationPart` (1), `DomainName` (4), `GatewayResponse` (1), `Method` (14), `Model` (3), `Resource` (1), `RestApi` (5), `Stage` (9), `UsagePlan` (5), `VpcLink` (1) |\n| **ApiGatewayV2** | `Api` (5), `Authorizer` (11), `DomainName` (3), `Integration` (15), `IntegrationResponse` (1), `Model` (2), `Route` (6), `RouteResponse` (2), `Stage` (4), `VpcLink` (1) |\n| **ApplicationAutoScaling** | `ScalableTarget` (19), `ScalingPolicy` (30) |\n| **AppSync** | `Api` (7), `ApiCache` (4), `ApiKey` (2), `ChannelNamespace` (5), `DataSource` (15), `DomainName` (1), `FunctionConfiguration` (24), `GraphQLApi` (16), `GraphQLSchema` (12), `Resolver` (32), `SourceApiAssociation` (3) |\n| **APS** | `AnomalyDetector` (9), `ResourcePolicy` (5), `RuleGroupsNamespace` (10), `Workspace` (12) |\n| **Athena** | `DataCatalog` (6), `WorkGroup` (8) |\n| **AutoScaling** | `AutoScalingGroup` (38), `LifecycleHook` (8), `ScalingPolicy` (41), `ScheduledAction` (13), `WarmPool` (4) |\n| **Backup** | `BackupPlan` (12), `BackupSelection` (5), `BackupVault` (3), `Framework` (3), `LogicallyAirGappedBackupVault` (2), `ReportPlan` (2), `RestoreTestingPlan` (5), `RestoreTestingSelection` (3) |\n| **Batch** | `ComputeEnvironment` (40), `ConsumableResource` (2), `JobDefinition` (109), `JobQueue` (18), `SchedulingPolicy` (8), `ServiceEnvironment` (6) |\n| **Bedrock** | `ApplicationInferenceProfile` (2), `AutomatedReasoningPolicy` (3), `Blueprint` (3), `DataAutomationProject` (8), `DataSource` (12), `Flow` (14), `Guardrail` (19), `IntelligentPromptRouter` (5), `KnowledgeBase` (18), `Prompt` (4) |\n| **BedrockAgentCore** | `ApiKeyCredentialProvider` (1), `BrowserCustom` (1), `CodeInterpreterCustom` (1), `ConfigurationBundle` (3), `Dataset` (3), `Evaluator` (4), `Gateway` (5), `GatewayRule` (3), `GatewayTarget` (9), `Harness` (2), `HarnessEndpoint` (2), `Memory` (6), `OAuth2CredentialProvider` (3), `OnlineEvaluationConfig` (3), `PaymentCredentialProvider` (1), `PaymentManager` (1), `Policy` (3), `ResourcePolicy` (1), `Runtime` (8), `RuntimeEndpoint` (1) |\n| **Cassandra** | `Keyspace` (2), `Table` (22), `Type` (13) |\n| **CertificateManager** | `Certificate` (4) |\n| **CloudFormation** | `GuardHook` (1), `Stack` (2), `StackSet` (6) |\n| **CloudFront** | `AnycastIpList` (1), `CachePolicy` (7), `ContinuousDeploymentPolicy` (5), `Distribution` (55), `Function` (4), `KeyGroup` (1), `KeyValueStore` (2), `OriginRequestPolicy` (5), `PublicKey` (1), `RealtimeLogConfig` (3), `ResponseHeadersPolicy` (8), `VpcOrigin` (3) |\n| **CloudTrail** | `EventDataStore` (1), `Trail` (19) |\n| **CloudWatch** | `Alarm` (22), `AnomalyDetector` (6), `CompositeAlarm` (7), `Dashboard` (14), `InsightRule` (7), `MetricStream` (6) |\n| **CodeBuild** | `Project` (37), `ReportGroup` (2), `SourceCredential` (4) |\n| **CodeCommit** | `Repository` (11) |\n| **CodeDeploy** | `Application` (7), `DeploymentConfig` (12), `DeploymentGroup` (23) |\n| **CodePipeline** | `CustomActionType` (3), `Pipeline` (25), `Webhook` (2) |\n| **Cognito** | `IdentityPool` (3), `IdentityPoolRoleAttachment` (7), `LogDeliveryConfiguration` (3), `ManagedLoginBranding` (2), `UserPool` (52), `UserPoolClient` (21), `UserPoolDomain` (7), `UserPoolGroup` (1), `UserPoolIdentityProvider` (11), `UserPoolResourceServer` (4), `UserPoolRiskConfigurationAttachment` (6), `UserPoolUICustomizationAttachment` (2) |\n| **Config** | `ConfigRule` (6), `ConfigurationAggregator` (2), `ConfigurationRecorder` (7), `ConformancePack` (1), `RemediationConfiguration` (2) |\n| **DocDB** | `DBCluster` (14), `DBInstance` (1), `DBSubnetGroup` (2), `EventSubscription` (1) |\n| **DocDBElastic** | `Cluster` (7) |\n| **DynamoDB** | `GlobalTable` (25), `Table` (28) |\n| **EC2** | `ClientVpnAuthorizationRule` (1), `ClientVpnEndpoint` (6), `DHCPOptions` (2), `EIPAssociation` (1), `FlowLog` (4), `Instance` (10), `KeyPair` (1), `LaunchTemplate` (12), `NatGateway` (2), `NetworkAclEntry` (1), `NetworkInterface` (2), `PlacementGroup` (3), `PrefixList` (3), `Route` (1), `SecurityGroup` (7), `SecurityGroupEgress` (5), `SecurityGroupIngress` (5), `Subnet` (14), `TrafficMirrorTarget` (1), `TransitGateway` (2), `TransitGatewayRoute` (1), `Volume` (6), `VPC` (2), `VPCCidrBlock` (1), `VPCEndpoint` (4), `VPCGatewayAttachment` (1), `VPNConnection` (5) |\n| **ECR** | `PullThroughCacheRule` (3), `RegistryScanningConfiguration` (1), `ReplicationConfiguration` (1), `Repository` (7), `RepositoryCreationTemplate` (8), `SigningConfiguration` (1) |\n| **ECS** | `CapacityProvider` (3), `Cluster` (5), `Service` (37), `TaskDefinition` (66), `TaskSet` (2) |\n| **EFS** | `AccessPoint` (1), `FileSystem` (7), `MountTarget` (4) |\n| **EKS** | `AccessEntry` (14), `Addon` (5), `Cluster` (21), `FargateProfile` (5), `IdentityProviderConfig` (3), `Nodegroup` (21), `PodIdentityAssociation` (5) |\n| **ElastiCache** | `CacheCluster` (9), `ReplicationGroup` (14), `User` (2), `UserGroup` (1) |\n| **ElasticBeanstalk** | `Application` (2), `ConfigurationTemplate` (37), `Environment` (35) |\n| **ElasticLoadBalancingV2** | `Listener` (57), `ListenerCertificate` (2), `ListenerRule` (38), `LoadBalancer` (37), `TargetGroup` (58), `TrustStore` (1), `TrustStoreRevocation` (1) |\n| **Events** | `ApiDestination` (1), `Archive` (5), `Connection` (1), `Endpoint` (2), `EventBus` (2), `Rule` (27) |\n| **EventSchemas** | `Discoverer` (1), `Registry` (1), `RegistryPolicy` (1), `Schema` (1) |\n| **GlobalAccelerator** | `Accelerator` (1), `EndpointGroup` (5), `Listener` (2) |\n| **Glue** | `Classifier` (6), `Connection` (5), `Crawler` (11), `CustomEntityType` (1), `Database` (1), `DataQualityRuleset` (1), `Job` (15), `MLTransform` (4), `Partition` (1), `Schema` (3), `SecurityConfiguration` (1), `Table` (1), `Trigger` (10), `UserDefinedFunction` (2), `Workflow` (1) |\n| **Grafana** | `Workspace` (7) |\n| **IAM** | `Group` (23), `GroupPolicy` (1), `InstanceProfile` (2), `ManagedPolicy` (31), `OIDCProvider` (2), `Policy` (29), `Role` (44), `RolePolicy` (1), `ServiceLinkedRole` (1), `User` (24), `UserPolicy` (1) |\n| **IoT** | `Authorizer` (3), `Certificate` (1), `CertificateProvider` (1), `Command` (1), `DomainConfiguration` (3), `JobTemplate` (3), `MitigationAction` (1), `Policy` (4), `PolicyPrincipalAttachment` (1), `ProvisioningTemplate` (3), `RoleAlias` (1), `ScheduledAudit` (1), `Thing` (1), `ThingGroup` (2), `TopicRule` (5), `TopicRuleDestination` (2) |\n| **Kinesis** | `ResourcePolicy` (4), `Stream` (5), `StreamConsumer` (2) |\n| **KinesisAnalyticsV2** | `Application` (20), `ApplicationCloudWatchLoggingOption` (1), `ApplicationOutput` (1), `ApplicationReferenceDataSource` (1) |\n| **KinesisFirehose** | `DeliveryStream` (68) |\n| **KMS** | `Alias` (2), `Key` (12), `ReplicaKey` (4) |\n| **Lambda** | `Alias` (7), `CodeSigningConfig` (1), `EventInvokeConfig` (5), `EventSourceMapping` (54), `Function` (48), `LayerVersion` (8), `LayerVersionPermission` (3), `Permission` (8), `Url` (8), `Version` (1) |\n| **Logs** | `AccountPolicy` (6), `DeliveryDestination` (3), `Destination` (1), `LogAnomalyDetector` (2), `LogGroup` (10), `MetricFilter` (7), `QueryDefinition` (1), `ResourcePolicy` (1), `SubscriptionFilter` (6), `Transformer` (3) |\n| **MediaConvert** | `JobTemplate` (9), `Preset` (32), `Queue` (2) |\n| **MemoryDB** | `Cluster` (8), `User` (1) |\n| **MSK** | `BatchScramSecret` (2), `Cluster` (31), `ClusterPolicy` (1), `Configuration` (4), `Replicator` (10), `ServerlessCluster` (4) |\n| **Neptune** | `DBCluster` (11), `DBClusterParameterGroup` (1), `DBInstance` (3), `DBSubnetGroup` (1), `GlobalCluster` (1) |\n| **NeptuneGraph** | `Graph` (2), `PrivateGraphEndpoint` (1) |\n| **NetworkFirewall** | `FirewallPolicy` (18), `RuleGroup` (34), `TLSInspectionConfiguration` (7) |\n| **OpenSearchServerless** | `AccessPolicy` (7), `Collection` (2), `CollectionGroup` (3), `LifecyclePolicy` (4), `SecurityConfig` (5), `SecurityPolicy` (14) |\n| **OpenSearchService** | `Domain` (65) |\n| **Pipes** | `Pipe` (9) |\n| **RDS** | `DBCluster` (19), `DBInstance` (33), `DBParameterGroup` (2), `DBProxy` (4), `DBProxyTargetGroup` (2), `DBShardGroup` (1), `DBSubnetGroup` (3), `EventSubscription` (3), `OptionGroup` (1) |\n| **Redshift** | `Cluster` (20), `EventSubscription` (1), `ScheduledAction` (2) |\n| **RedshiftServerless** | `Namespace` (5), `Workgroup` (6) |\n| **Route53** | `CidrCollection` (11), `DNSSEC` (3), `HealthCheck` (30), `HostedZone` (14), `KeySigningKey` (9), `RecordSet` (74), `RecordSetGroup` (75) |\n| **Route53Profiles** | `ProfileAssociation` (1), `ProfileResourceAssociation` (6) |\n| **Route53Resolver** | `FirewallDomainList` (3), `FirewallRuleGroup` (18), `FirewallRuleGroupAssociation` (4), `ResolverDNSSECConfig` (1), `ResolverEndpoint` (18), `ResolverQueryLoggingConfig` (1), `ResolverQueryLoggingConfigAssociation` (1), `ResolverRule` (15), `ResolverRuleAssociation` (1) |\n| **S3** | `AccessPoint` (1), `Bucket` (54), `BucketPolicy` (4), `StorageLens` (5), `StorageLensGroup` (3) |\n| **S3Express** | `AccessPoint` (3), `DirectoryBucket` (11) |\n| **Scheduler** | `Schedule` (12), `ScheduleGroup` (1) |\n| **SecretsManager** | `RotationSchedule` (4), `Secret` (5), `SecretTargetAttachment` (1) |\n| **ServiceDiscovery** | `HttpNamespace` (3), `Instance` (19), `PrivateDnsNamespace` (4), `PublicDnsNamespace` (4), `Service` (13) |\n| **SES** | `ConfigurationSetEventDestination` (8), `ContactList` (1), `EmailIdentity` (6), `ReceiptRule` (6), `ReceiptRuleSet` (1), `Template` (1) |\n| **SNS** | `Subscription` (10), `Topic` (14), `TopicPolicy` (1) |\n| **SQS** | `Queue` (10), `QueuePolicy` (1) |\n| **SSM** | `Association` (4), `Document` (3), `MaintenanceWindow` (2), `MaintenanceWindowTarget` (1), `MaintenanceWindowTask` (1), `Parameter` (5) |\n| **StepFunctions** | `Activity` (3), `StateMachine` (26) |\n| **Synthetics** | `Canary` (16) |\n| **VerifiedPermissions** | `IdentitySource` (3), `Policy` (26), `PolicyStore` (23), `PolicyStoreAlias` (1), `PolicyTemplate` (29) |\n| **WAFv2** | `IPSet` (3), `LoggingConfiguration` (3), `RegexPatternSet` (3), `RuleGroup` (18), `WebACL` (23), `WebACLAssociation` (2) |\n| **XRay** | `Group` (1), `ResourcePolicy` (3), `SamplingRule` (6) |\n\n</details>\n<!-- supported-resources:end -->\n\nHighlights:\n\n- **ELBv2**: `idle_timeout` / `deregistration_delay` / `slow_start` attribute ranges (stringly-typed Key/Value attributes are invisible to schema validation)\n- **IAM**: managed (6,144 chars) and inline (role/group/user) policy document size limits\n- **CloudFront**: `MinTTL <= DefaultTTL <= MaxTTL` ordering, ACM certificates must live in `us-east-1`\n- **Step Functions**: `Next`/`Default`/`Choices` must reference defined states (a dangling `StartAt` is already caught by the engine's built-in `E3601`)\n- **EC2**: security group TCP/UDP port ranges and `FromPort <= ToPort`\n\n## For AI agents\n\nTo add cdk-preflight to a CDK app:\n\n1. `npm i -D cdk-preflight`\n2. `npx cdk-preflight init` — or the shorter alias `npx cdkpf init` (finds the entry point via `cdk.json` and inserts `Preflight.apply(app)`; use `--dry-run` to preview)\n3. Run `cdk synth`. Violations fail the synth with a validation report; each finding carries a rule id, the actual value, and a suggested fix\n4. To fix a finding, prefer correcting the resource. To opt out instead: `exclude: ['<rule-id>']` disables one rule, `enforce: false` switches to warn-only reporting\n\nThe machine-readable findings are written to `cdk.out/validation-report.json` when synthesizing with `-c @aws-cdk/core:validationReportJson=true`.\n\n### Report missed deploy errors\n\ncdk-preflight only knows the failure modes it has benched. When a deploy fails on a constraint the pack should have caught, that report is how new rules get added — but at that moment cdk-preflight is out of the loop (it passed the synth), so the instruction has to already sit in your agent's context. Paste this into your repository's `AGENTS.md` (or `CLAUDE.md`):\n\n```markdown\n## cdk-preflight: report missed deploy errors\n\nIf `cdk deploy` fails even though `cdk synth` (with cdk-preflight enabled) passed,\nand the failure is a service-side validation rejection — CREATE_FAILED/UPDATE_FAILED\nwith a 400-class message such as \"Invalid request provided: ...\" — cdk-preflight is\nmissing a rule. Report it:\n\n1. Qualify first. NOT reportable: AccessDenied / permissions, service quotas\n (those belong to the opt-in `checkQuotas`; open a regular issue if it missed one),\n throttling, name conflicts with existing resources, stabilization timeouts.\n2. Redact account-specific data (account IDs -> 123456789012, real ARNs/domains\n -> placeholders).\n3. Search existing issues: https://github.com/badmintoncryer/cdk-preflight/issues\n4. With your user's approval, open a \"Missed deploy error\" issue with the verbatim\n error message, the resource type, a minimal template snippet, and your\n aws-cdk-lib / cdk-preflight versions.\n```\n\n## Scope and rule lifecycle\n\nA constraint belongs in the pack when violating it makes a real deployment fail, or lets the deployment succeed and then makes every run of the deployed resource fail (an SSM Automation association that targets several tags, for example), *and* no layer that sees the same synthesized template already blocks it. There is no further \"is this worth a rule\" question — if the gap is real, it gets a rule.\n\nCDK L2 construct validation is deliberately **not** one of those layers. `CfnXxx` usage, escape hatches, `addPropertyOverride`, `cloudformation-include` and migrated templates all bypass L2, so an L2 guard covering the same mistake neither disqualifies a rule nor retires one.\n\nThat makes growth the normal state, and it has a consequence worth knowing before you upgrade: **new rules land in minor releases, so a minor upgrade can newly fail a `cdk synth` that passed yesterday.** That is intended, not a regression. If you need a frozen rule set, pin the version; to drop a single rule, `exclude: ['<rule-id>']`; to see everything without failing the build, `enforce: false`.\n\nRules move the other way too. Once the validation engine bundled in `aws-cdk-lib` starts blocking a constraint, the rule records the engine version that covers it and stops being evaluated on that engine or later — but only where that changes nothing about whether synth fails: with `enforce: false`, with `strict: true`, or when your app sets the context `@aws-cdk/core:validateAgainstDefaultRules: true`. In the default enforce mode the CDK reports its own engine's errors as warnings, so the rule keeps running and you may see both. Users on an older `aws-cdk-lib` keep the rule either way; it is deleted only once the minimum supported `aws-cdk-lib` bundles an engine that covers it.\n\n## How it works\n\n`Preflight.apply()` evaluates the rules with the [cloudformation-validate](https://github.com/aws-cloudformation/cloudformation-validate) Rust/WASM engine that ships inside `aws-cdk-lib` — no extra binaries, and no network access at synth time unless you opt in to `checkQuotas`, which calls AWS from a child process. In the default enforce mode the engine is invoked through a dedicated CDK validation plugin so that violations fail synthesis; with `enforce: false` the rules are instead injected into the CDK built-in `CloudFormationValidatePlugin` and reported as warnings.\n\nConstraints that *can* be expressed in schemas or generic engine rules also make good upstream PRs to that engine, but nothing here waits on one — the upstream release cycle is deliberately slower than this pack's. Each rule's `meta.yaml` tracks its upstream status so that retirement stays bookkeeping.\n\n## Requirements\n\n- `aws-cdk-lib` >= 2.267.0, released 2026-08-27 (the first release that bundles the built-in CloudFormation validator). This is a recent release — an existing CDK app may need an upgrade before cdk-preflight can run.\n\n## Contributing\n\nRule authoring, the verification gates (including real-deploy reproduction), and the test layout are documented in [AGENTS.md](AGENTS.md) — written for AI coding agents and humans alike.\n\n## License\n\nApache-2.0\n"
9629
+ "markdown": "<p align=\"center\">\n <img src=\"https://raw.githubusercontent.com/badmintoncryer/cdk-preflight/main/assets/logo.png\" alt=\"cdk-preflight\" width=\"104\" height=\"104\">\n</p>\n\n<h1 align=\"center\">cdk-preflight</h1>\n\n<p align=\"center\">\n <strong>Catch deploy-time CloudFormation failures at <code>cdk synth</code> time.</strong>\n</p>\n\n<p align=\"center\">\n <a href=\"https://github.com/badmintoncryer/cdk-preflight/actions/workflows/monthly-verify.yml\"><img src=\"https://github.com/badmintoncryer/cdk-preflight/actions/workflows/monthly-verify.yml/badge.svg\" alt=\"monthly real-deploy verification\"></a>\n <a href=\"https://www.npmjs.com/package/cdk-preflight\"><img src=\"https://img.shields.io/npm/v/cdk-preflight.svg\" alt=\"npm version\"></a>\n <a href=\"https://www.npmjs.com/package/cdk-preflight\"><img src=\"https://img.shields.io/npm/dt/cdk-preflight.svg\" alt=\"npm total downloads\"></a>\n <a href=\"docs/rules.md\"><img src=\"https://img.shields.io/badge/rules-3230-blue\" alt=\"3230 bundled rules\"></a>\n</p>\n\nSome CloudFormation constraints are not expressed in resource provider schemas — they live only in documentation, in service API validation, or across multiple properties. Templates that violate them pass `cdk synth`, pass CloudFormation pre-deployment validation, and then fail minutes into a deployment, burning a rollback cycle.\n\ncdk-preflight is a curated [Rego rule pack](docs/rules.md) for exactly those constraints, evaluated with the CloudFormation validation engine that ships inside `aws-cdk-lib` (>= 2.267.0). By default a violation **fails `cdk synth`** — a template that is known to fail at deploy time never leaves your machine.\n\nThe pack aims at **every deploy-time failure, and every post-deploy run that is certain to fail, that no existing CDK mechanism already catches** — nothing narrower. Every bundled rule is backed by a `fail`/`pass` template pair, and the failure has been reproduced against real AWS. The handful of rules that could not be reproduced are marked `doc-only` and report as **warnings**, as does a rule whose remaining false positives cannot be told from the template (a cross-account Lambda layer the owner may have shared): they show up in the validation report but never fail synth. Rules that the built-in validation engine already covers are deliberately **not** duplicated — a test suite enforces this, and a rule the engine catches up with later is skipped on engines that cover it (see [Scope and rule lifecycle](#scope-and-rule-lifecycle)).\n\n> **Requires `aws-cdk-lib` >= 2.267.0** (released 2026-08-27) — the first release that bundles\n> the CloudFormation validation engine. On older versions the rules cannot run at all.\n\n## Quick start\n\n```bash\nnpm i -D cdk-preflight\nnpx cdkpf init # inserts Preflight.apply(app) into your CDK app\n # (`npx cdkpf init` is the same command, shorter)\n```\n\nor add one line yourself:\n\n```ts\nimport { Preflight } from 'cdk-preflight';\n\nconst app = new App();\nPreflight.apply(app);\n```\n\nOn violation, `cdk synth` fails with one error per finding, including the construct trace:\n\n```text\nERROR idle_timeout.timeout_seconds is 5000 but must be between 1 and 4000 seconds (cdk-preflight)\n MyStack/Alb/Resource (Alb16C2F182) aws-cdk-lib.aws_elasticloadbalancingv2.CfnLoadBalancer\n\nSynthesis finished with errors\n```\n\n## What it catches\n\nFour ordinary-looking snippets. All of them pass `cdk synth` and CloudFormation\npre-deployment validation, and all of them fail minutes into a deployment:\n\n```ts\n// 1) pf-iam-inline-policy-size — enumerate buckets, grant each one, blow past 10,240 chars\n// \"Maximum policy size of 10240 bytes exceeded for role IngestRole\"\n// (via role.addToPolicy the CDK auto-splits into managed policies instead,\n// and you hit the 6,144-char limit as pf-iam-managed-policy-size)\nnew iam.Policy(this, 'IngestPolicy', {\n roles: [role],\n statements: [new iam.PolicyStatement({\n actions: ['s3:GetObject', 's3:ListBucket'],\n resources: Array.from({ length: 200 },\n (_, i) => `arn:aws:s3:::data-lake-landing-zone-${i}/year=*/month=*/*`),\n })],\n});\n\n// 2) pf-lambda-env-size — a config blob in the environment, over the 4KB total\n// \"Lambda was unable to configure your environment variables because the\n// environment variables you have provided exceeded the 4KB limit\"\nnew lambda.Function(this, 'Fn', {\n runtime: lambda.Runtime.NODEJS_22_X,\n handler: 'index.handler',\n code: lambda.Code.fromInline('exports.handler = async () => {};'),\n environment: { FEATURE_FLAGS: JSON.stringify(bigFeatureFlagMap) },\n});\n\n// 3) pf-sfn-asl-missing-state (+ pf-sfn-asl-unreachable-state) — a typo in a state name\n// \"Invalid State Machine Definition: 'MISSING_TRANSITION_TARGET: ...'\"\nnew sfn.StateMachine(this, 'Pipeline', {\n definitionBody: sfn.DefinitionBody.fromString(JSON.stringify({\n StartAt: 'Validate',\n States: {\n Validate: { Type: 'Pass', Next: 'Transform' },\n Trasform: { Type: 'Pass', End: true }, // typo: Transform\n },\n })),\n});\n\n// 4) pf-logs-filter-pattern-bracket — a filter pattern opened with '[' and never closed\n// \"If a filter pattern starts with '[' it must end with ']'\"\nnew logs.MetricFilter(this, 'ErrorFilter', {\n logGroup,\n metricNamespace: 'Pipeline',\n metricName: 'Errors',\n filterPattern: logs.FilterPattern.literal('[time, level=ERROR, msg'),\n});\n```\n\nNone of these are type errors, so the L2 constructs accept them; none of them are\nexpressible in a resource schema, so CloudFormation accepts the template. With\n`Preflight.apply(app)` in place they fail `cdk synth` instead.\n\n## Observe-only mode\n\nTo roll the rules out gradually, start with `enforce: false`: findings then surface as synth **warnings** through the CDK built-in validator, with construct traces and per-finding acknowledgement:\n\n```ts\nPreflight.apply(app, { enforce: false });\n```\n\n```text\nWARNING idle_timeout.timeout_seconds is 5000 but must be between 1 and 4000 seconds (CloudFormation Validate)\n MyStack/Alb (Alb) aws-cdk-lib.aws_elasticloadbalancingv2.CfnLoadBalancer\n Acknowledge with 'CloudFormation-Validate::pf-elbv2-lb-idle-timeout-range'\n```\n\n> **Known limitation with stages.** The AWS CDK CLI drops validation findings for stacks nested in a `Stage`\n> before printing them, so in observe-only mode those findings appear **only** in `cdk.out/validation-report.json`\n> and never on the console. Enforce mode is not affected: cdk-preflight reports such findings itself and fails\n> synthesis. This is a CLI-side bug (present since aws-cdk 2.1128.1), not a rule evaluation problem.\n\n> **If the rules cannot run, the build stops.** When the evaluation engine fails on a template (a rule pack that\n> does not compile, an engine bug), enforce mode reports it as a violation named `pf-engine-error` and fails\n> synthesis for that stack instead of passing green with no rule having run. The other stacks keep their rules.\n> `pf-engine-error` is not a bundled rule and cannot be `exclude`d; `enforce: false` unblocks the build if you\n> need one.\n\n| Option | Default | Effect |\n|---|---|---|\n| `enforce` | `true` | Violations of bundled rules fail synthesis; set to `false` to only warn |\n| `strict` | `false` | With `enforce`: also fail on error-class findings (`ERROR`/`FATAL`, e.g. `F3034`) of the built-in validation engine itself, which the CDK currently downgrades to warnings |\n| `exclude` | `[]` | Rule ids to disable |\n| `includeUpstreamPending` | `true` | Include rules already proposed to the upstream engine but not yet merged |\n| `checkQuotas` | `false` | Also check the target account's Service Quotas and current usage (calls AWS at synth time, see below) |\n| `quotaCacheTtl` | 5 minutes | How long `checkQuotas` reuses the usage and deployed-stack state it fetched |\n\nTo opt out of a single rule everywhere, pass its id in `exclude`. To suppress a\nsingle *finding* on one construct, acknowledge it — this works in both modes, the\nid prefix just differs (`cdk-preflight::` when enforcing, `CloudFormation-Validate::`\nin observe-only, as printed in the warning text):\n\n```ts\ncdk.Validations.of(errorFilter).acknowledge({\n id: 'cdk-preflight::pf-logs-filter-pattern-bracket',\n reason: 'log group is written by a legacy producer; pattern is fixed upstream',\n});\n```\n\n## Service quota check (opt-in)\n\nThe rules only read the template, so they never assert a limit an account can raise. `checkQuotas: true` covers that\ngap for the quotas a CDK app runs into most: it adds up what every stack in the app (nested stacks included) is about\nto create in each account and Region, and compares that against the account's applied quota and current usage.\n\n```ts\nPreflight.apply(app, { checkQuotas: true });\n```\n\n| Check | Counts | Against |\n|---|---|---|\n| `pf-quota-vpcs-per-region` | new `AWS::EC2::VPC` | VPCs per Region (`vpc/L-F678F1CE`) |\n| `pf-quota-internet-gateways-per-region` | new `AWS::EC2::InternetGateway` | Internet gateways per Region (`vpc/L-A4707A72`) |\n| `pf-quota-elastic-ips-per-region` | new `AWS::EC2::EIP` (one per NAT gateway in a default `ec2.Vpc`) | EC2-VPC Elastic IPs (`ec2/L-0263D0A3`) |\n| `pf-quota-lambda-unreserved-concurrency` | increase in `ReservedConcurrentExecutions` | Lambda keeps at least 100 concurrent executions unreserved |\n\n- **Only new resources count.** The deployed template of each stack is read with `GetTemplate`; a resource whose\n logical id and type are already deployed is not counted again, so redeploying an unchanged app never trips the\n check. Resources behind a `Condition` and the brief overlap during a replacement are not counted.\n- **A finding fails synth** in enforce mode and is a warning with `enforce: false`. Acknowledge one like any other\n finding (`cdk-preflight::pf-quota-...`; `cdk-preflight-quotas::pf-quota-...` with `enforce: false`). The ids also work in `exclude`, including `pf-quota-check-skipped` to silence the skip warning.\n- **It needs credentials in the app process.** The CDK CLI does not pass `--profile` on to the app, so set\n `AWS_PROFILE` (or `AWS_*` variables) instead. When the credentials are missing, lack permissions, or belong to a\n different account than the stack's, the check is skipped with a `pf-quota-check-skipped` warning and synth goes on.\n- **Permissions:** `servicequotas:GetServiceQuota`, `servicequotas:GetAWSDefaultServiceQuota`, `ec2:DescribeVpcs`,\n `ec2:DescribeInternetGateways`, `ec2:DescribeAddresses`, `lambda:GetAccountSettings`, `cloudformation:GetTemplate`,\n `cloudformation:DescribeStackResource`. All read-only (`ReadOnlyAccess` covers them). The check also calls\n `sts:GetCallerIdentity`, which IAM never denies.\n- **Results are cached** in `cdk.out/.cdk-preflight/quota-cache.json`: usage and deployed templates for\n `quotaCacheTtl` (5 minutes), quota values for 24 hours. Delete the file to force a refetch.\n- Usage can change between synth and deploy (another team deploying to the same account), so a clean check is not a\n guarantee. The reverse also happens: EC2 does not always enforce the Elastic IP quota when CloudFormation\n creates several addresses in parallel (measured 2026-10-16: with 4 of 5 in use, two parallel `AWS::EC2::EIP` both\n succeeded), so a template the check stops may occasionally deploy. The check follows the quota, not the race.\n\n## Bundled rules\n\nSee [docs/rules.md](docs/rules.md) for the generated rule table.\n\n<!-- supported-resources:start -->\n<details>\n<summary><b>414 resource types across 77 services</b> — click to expand</summary>\n\nResource names are relative to `AWS::<Service>::`; the number in parentheses is how many rules target that type.\n\n| Service | Resource types |\n|---|---|\n| **(any resource type)** | `*` (14) |\n| **ApiGateway** | `ApiKey` (1), `Authorizer` (5), `Deployment` (1), `DocumentationPart` (1), `DomainName` (4), `GatewayResponse` (1), `Method` (14), `Model` (3), `Resource` (1), `RestApi` (5), `Stage` (9), `UsagePlan` (5), `VpcLink` (1) |\n| **ApiGatewayV2** | `Api` (5), `Authorizer` (11), `DomainName` (3), `Integration` (15), `IntegrationResponse` (1), `Model` (2), `Route` (6), `RouteResponse` (2), `Stage` (4), `VpcLink` (1) |\n| **ApplicationAutoScaling** | `ScalableTarget` (19), `ScalingPolicy` (30) |\n| **AppSync** | `Api` (7), `ApiCache` (4), `ApiKey` (2), `ChannelNamespace` (5), `DataSource` (15), `DomainName` (1), `FunctionConfiguration` (24), `GraphQLApi` (16), `GraphQLSchema` (12), `Resolver` (32), `SourceApiAssociation` (3) |\n| **APS** | `AnomalyDetector` (9), `ResourcePolicy` (5), `RuleGroupsNamespace` (10), `Workspace` (12) |\n| **Athena** | `DataCatalog` (6), `WorkGroup` (8) |\n| **AutoScaling** | `AutoScalingGroup` (38), `LifecycleHook` (8), `ScalingPolicy` (41), `ScheduledAction` (13), `WarmPool` (4) |\n| **Backup** | `BackupPlan` (12), `BackupSelection` (5), `BackupVault` (3), `Framework` (3), `LogicallyAirGappedBackupVault` (2), `ReportPlan` (2), `RestoreTestingPlan` (5), `RestoreTestingSelection` (3) |\n| **Batch** | `ComputeEnvironment` (40), `ConsumableResource` (2), `JobDefinition` (109), `JobQueue` (18), `SchedulingPolicy` (8), `ServiceEnvironment` (6) |\n| **Bedrock** | `ApplicationInferenceProfile` (2), `AutomatedReasoningPolicy` (3), `Blueprint` (3), `DataAutomationProject` (8), `DataSource` (12), `Flow` (14), `Guardrail` (19), `IntelligentPromptRouter` (5), `KnowledgeBase` (18), `Prompt` (4) |\n| **BedrockAgentCore** | `ApiKeyCredentialProvider` (1), `BrowserCustom` (1), `CodeInterpreterCustom` (1), `ConfigurationBundle` (3), `Dataset` (3), `Evaluator` (4), `Gateway` (5), `GatewayRule` (3), `GatewayTarget` (9), `Harness` (2), `HarnessEndpoint` (2), `Memory` (6), `OAuth2CredentialProvider` (3), `OnlineEvaluationConfig` (3), `PaymentCredentialProvider` (1), `PaymentManager` (1), `Policy` (3), `ResourcePolicy` (1), `Runtime` (8), `RuntimeEndpoint` (1) |\n| **Cassandra** | `Keyspace` (2), `Table` (22), `Type` (13) |\n| **CertificateManager** | `Certificate` (4) |\n| **CloudFormation** | `GuardHook` (1), `Stack` (2), `StackSet` (6) |\n| **CloudFront** | `AnycastIpList` (1), `CachePolicy` (7), `ContinuousDeploymentPolicy` (5), `Distribution` (55), `Function` (4), `KeyGroup` (1), `KeyValueStore` (2), `OriginRequestPolicy` (5), `PublicKey` (1), `RealtimeLogConfig` (3), `ResponseHeadersPolicy` (8), `VpcOrigin` (3) |\n| **CloudTrail** | `EventDataStore` (1), `Trail` (19) |\n| **CloudWatch** | `Alarm` (22), `AnomalyDetector` (6), `CompositeAlarm` (7), `Dashboard` (14), `InsightRule` (7), `MetricStream` (6) |\n| **CodeBuild** | `Project` (37), `ReportGroup` (2), `SourceCredential` (4) |\n| **CodeCommit** | `Repository` (11) |\n| **CodeDeploy** | `Application` (7), `DeploymentConfig` (12), `DeploymentGroup` (23) |\n| **CodePipeline** | `CustomActionType` (3), `Pipeline` (25), `Webhook` (2) |\n| **Cognito** | `IdentityPool` (3), `IdentityPoolRoleAttachment` (7), `LogDeliveryConfiguration` (3), `ManagedLoginBranding` (2), `UserPool` (52), `UserPoolClient` (21), `UserPoolDomain` (7), `UserPoolGroup` (1), `UserPoolIdentityProvider` (11), `UserPoolResourceServer` (4), `UserPoolRiskConfigurationAttachment` (6), `UserPoolUICustomizationAttachment` (2) |\n| **Config** | `ConfigRule` (6), `ConfigurationAggregator` (2), `ConfigurationRecorder` (7), `ConformancePack` (1), `RemediationConfiguration` (2) |\n| **DocDB** | `DBCluster` (14), `DBInstance` (1), `DBSubnetGroup` (2), `EventSubscription` (1) |\n| **DocDBElastic** | `Cluster` (7) |\n| **DynamoDB** | `GlobalTable` (25), `Table` (28) |\n| **EC2** | `ClientVpnAuthorizationRule` (1), `ClientVpnEndpoint` (6), `DHCPOptions` (2), `EIPAssociation` (1), `FlowLog` (4), `Instance` (10), `KeyPair` (1), `LaunchTemplate` (12), `NatGateway` (2), `NetworkAclEntry` (1), `NetworkInterface` (2), `PlacementGroup` (3), `PrefixList` (3), `Route` (1), `SecurityGroup` (7), `SecurityGroupEgress` (5), `SecurityGroupIngress` (5), `Subnet` (14), `TrafficMirrorTarget` (1), `TransitGateway` (2), `TransitGatewayRoute` (1), `Volume` (6), `VPC` (2), `VPCCidrBlock` (1), `VPCEndpoint` (4), `VPCGatewayAttachment` (1), `VPNConnection` (5) |\n| **ECR** | `PullThroughCacheRule` (3), `RegistryScanningConfiguration` (1), `ReplicationConfiguration` (1), `Repository` (7), `RepositoryCreationTemplate` (8), `SigningConfiguration` (1) |\n| **ECS** | `CapacityProvider` (3), `Cluster` (5), `Service` (37), `TaskDefinition` (66), `TaskSet` (2) |\n| **EFS** | `AccessPoint` (1), `FileSystem` (7), `MountTarget` (4) |\n| **EKS** | `AccessEntry` (14), `Addon` (5), `Cluster` (21), `FargateProfile` (5), `IdentityProviderConfig` (3), `Nodegroup` (21), `PodIdentityAssociation` (5) |\n| **ElastiCache** | `CacheCluster` (9), `ReplicationGroup` (14), `User` (2), `UserGroup` (1) |\n| **ElasticBeanstalk** | `Application` (2), `ConfigurationTemplate` (37), `Environment` (35) |\n| **ElasticLoadBalancingV2** | `Listener` (57), `ListenerCertificate` (2), `ListenerRule` (38), `LoadBalancer` (37), `TargetGroup` (58), `TrustStore` (1), `TrustStoreRevocation` (1) |\n| **Events** | `ApiDestination` (1), `Archive` (5), `Connection` (1), `Endpoint` (2), `EventBus` (2), `Rule` (27) |\n| **EventSchemas** | `Discoverer` (1), `Registry` (1), `RegistryPolicy` (1), `Schema` (1) |\n| **GlobalAccelerator** | `Accelerator` (1), `EndpointGroup` (5), `Listener` (2) |\n| **Glue** | `Classifier` (6), `Connection` (5), `Crawler` (11), `CustomEntityType` (1), `Database` (1), `DataQualityRuleset` (1), `Job` (15), `MLTransform` (4), `Partition` (1), `Schema` (3), `SecurityConfiguration` (1), `Table` (1), `Trigger` (10), `UserDefinedFunction` (2), `Workflow` (1) |\n| **Grafana** | `Workspace` (7) |\n| **IAM** | `Group` (23), `GroupPolicy` (19), `InstanceProfile` (2), `ManagedPolicy` (31), `OIDCProvider` (2), `Policy` (29), `Role` (44), `RolePolicy` (19), `ServiceLinkedRole` (1), `User` (24), `UserPolicy` (19) |\n| **IoT** | `Authorizer` (3), `Certificate` (1), `CertificateProvider` (1), `Command` (1), `DomainConfiguration` (3), `JobTemplate` (3), `MitigationAction` (1), `Policy` (4), `PolicyPrincipalAttachment` (1), `ProvisioningTemplate` (3), `RoleAlias` (1), `ScheduledAudit` (1), `Thing` (1), `ThingGroup` (2), `TopicRule` (5), `TopicRuleDestination` (2) |\n| **Kinesis** | `ResourcePolicy` (4), `Stream` (5), `StreamConsumer` (2) |\n| **KinesisAnalyticsV2** | `Application` (20), `ApplicationCloudWatchLoggingOption` (1), `ApplicationOutput` (1), `ApplicationReferenceDataSource` (1) |\n| **KinesisFirehose** | `DeliveryStream` (68) |\n| **KMS** | `Alias` (2), `Key` (12), `ReplicaKey` (4) |\n| **Lambda** | `Alias` (7), `CodeSigningConfig` (1), `EventInvokeConfig` (5), `EventSourceMapping` (54), `Function` (48), `LayerVersion` (8), `LayerVersionPermission` (3), `Permission` (8), `Url` (8), `Version` (1) |\n| **Logs** | `AccountPolicy` (6), `DeliveryDestination` (3), `Destination` (1), `LogAnomalyDetector` (2), `LogGroup` (10), `MetricFilter` (7), `QueryDefinition` (1), `ResourcePolicy` (1), `SubscriptionFilter` (6), `Transformer` (3) |\n| **MediaConvert** | `JobTemplate` (9), `Preset` (32), `Queue` (2) |\n| **MemoryDB** | `Cluster` (8), `User` (1) |\n| **MSK** | `BatchScramSecret` (2), `Cluster` (31), `ClusterPolicy` (1), `Configuration` (4), `Replicator` (10), `ServerlessCluster` (4) |\n| **Neptune** | `DBCluster` (11), `DBClusterParameterGroup` (1), `DBInstance` (3), `DBSubnetGroup` (1), `GlobalCluster` (1) |\n| **NeptuneGraph** | `Graph` (2), `PrivateGraphEndpoint` (1) |\n| **NetworkFirewall** | `FirewallPolicy` (18), `RuleGroup` (34), `TLSInspectionConfiguration` (7) |\n| **OpenSearchServerless** | `AccessPolicy` (7), `Collection` (2), `CollectionGroup` (3), `LifecyclePolicy` (4), `SecurityConfig` (5), `SecurityPolicy` (14) |\n| **OpenSearchService** | `Domain` (65) |\n| **Pipes** | `Pipe` (9) |\n| **RDS** | `DBCluster` (19), `DBInstance` (33), `DBParameterGroup` (2), `DBProxy` (4), `DBProxyTargetGroup` (2), `DBShardGroup` (1), `DBSubnetGroup` (3), `EventSubscription` (3), `OptionGroup` (1) |\n| **Redshift** | `Cluster` (20), `EventSubscription` (1), `ScheduledAction` (2) |\n| **RedshiftServerless** | `Namespace` (5), `Workgroup` (6) |\n| **Route53** | `CidrCollection` (11), `DNSSEC` (3), `HealthCheck` (30), `HostedZone` (14), `KeySigningKey` (9), `RecordSet` (74), `RecordSetGroup` (76) |\n| **Route53Profiles** | `ProfileAssociation` (1), `ProfileResourceAssociation` (6) |\n| **Route53Resolver** | `FirewallDomainList` (3), `FirewallRuleGroup` (18), `FirewallRuleGroupAssociation` (4), `ResolverDNSSECConfig` (1), `ResolverEndpoint` (18), `ResolverQueryLoggingConfig` (1), `ResolverQueryLoggingConfigAssociation` (1), `ResolverRule` (15), `ResolverRuleAssociation` (1) |\n| **S3** | `AccessPoint` (1), `Bucket` (54), `BucketPolicy` (4), `StorageLens` (5), `StorageLensGroup` (3) |\n| **S3Express** | `AccessPoint` (3), `DirectoryBucket` (11) |\n| **Scheduler** | `Schedule` (12), `ScheduleGroup` (1) |\n| **SecretsManager** | `RotationSchedule` (4), `Secret` (5), `SecretTargetAttachment` (1) |\n| **ServiceDiscovery** | `HttpNamespace` (3), `Instance` (19), `PrivateDnsNamespace` (4), `PublicDnsNamespace` (4), `Service` (13) |\n| **SES** | `ConfigurationSetEventDestination` (8), `ContactList` (1), `EmailIdentity` (6), `ReceiptRule` (6), `ReceiptRuleSet` (1), `Template` (1) |\n| **SNS** | `Subscription` (10), `Topic` (14), `TopicPolicy` (1) |\n| **SQS** | `Queue` (10), `QueuePolicy` (1) |\n| **SSM** | `Association` (4), `Document` (3), `MaintenanceWindow` (2), `MaintenanceWindowTarget` (1), `MaintenanceWindowTask` (1), `Parameter` (5) |\n| **StepFunctions** | `Activity` (3), `StateMachine` (26) |\n| **Synthetics** | `Canary` (16) |\n| **VerifiedPermissions** | `IdentitySource` (3), `Policy` (26), `PolicyStore` (23), `PolicyStoreAlias` (1), `PolicyTemplate` (29) |\n| **WAFv2** | `IPSet` (3), `LoggingConfiguration` (3), `RegexPatternSet` (3), `RuleGroup` (18), `WebACL` (23), `WebACLAssociation` (2) |\n| **XRay** | `Group` (1), `ResourcePolicy` (3), `SamplingRule` (6) |\n\n</details>\n<!-- supported-resources:end -->\n\nHighlights:\n\n- **ELBv2**: `idle_timeout` / `deregistration_delay` / `slow_start` attribute ranges (stringly-typed Key/Value attributes are invisible to schema validation)\n- **IAM**: managed (6,144 chars) and inline (role/group/user) policy document size limits\n- **CloudFront**: `MinTTL <= DefaultTTL <= MaxTTL` ordering, ACM certificates must live in `us-east-1`\n- **Step Functions**: `Next`/`Default`/`Choices` must reference defined states (a dangling `StartAt` is already caught by the engine's built-in `E3601`)\n- **EC2**: security group TCP/UDP port ranges and `FromPort <= ToPort`\n\n## For AI agents\n\nTo add cdk-preflight to a CDK app:\n\n1. `npm i -D cdk-preflight`\n2. `npx cdk-preflight init` — or the shorter alias `npx cdkpf init` (finds the entry point via `cdk.json` and inserts `Preflight.apply(app)`; use `--dry-run` to preview)\n3. Run `cdk synth`. Violations fail the synth with a validation report; each finding carries a rule id, the actual value, and a suggested fix\n4. To fix a finding, prefer correcting the resource. To opt out instead: `exclude: ['<rule-id>']` disables one rule, `enforce: false` switches to warn-only reporting\n\nThe machine-readable findings are written to `cdk.out/validation-report.json` when synthesizing with `-c @aws-cdk/core:validationReportJson=true`.\n\n### Report missed deploy errors\n\ncdk-preflight only knows the failure modes it has benched. When a deploy fails on a constraint the pack should have caught, that report is how new rules get added — but at that moment cdk-preflight is out of the loop (it passed the synth), so the instruction has to already sit in your agent's context. Paste this into your repository's `AGENTS.md` (or `CLAUDE.md`):\n\n```markdown\n## cdk-preflight: report missed deploy errors\n\nIf `cdk deploy` fails even though `cdk synth` (with cdk-preflight enabled) passed,\nand the failure is a service-side validation rejection — CREATE_FAILED/UPDATE_FAILED\nwith a 400-class message such as \"Invalid request provided: ...\" — cdk-preflight is\nmissing a rule. Report it:\n\n1. Qualify first. NOT reportable: AccessDenied / permissions, service quotas\n (those belong to the opt-in `checkQuotas`; open a regular issue if it missed one),\n throttling, name conflicts with existing resources, stabilization timeouts.\n2. Redact account-specific data (account IDs -> 123456789012, real ARNs/domains\n -> placeholders).\n3. Search existing issues: https://github.com/badmintoncryer/cdk-preflight/issues\n4. With your user's approval, open a \"Missed deploy error\" issue with the verbatim\n error message, the resource type, a minimal template snippet, and your\n aws-cdk-lib / cdk-preflight versions.\n```\n\n## Scope and rule lifecycle\n\nA constraint belongs in the pack when violating it makes a real deployment fail, or lets the deployment succeed and then makes every run of the deployed resource fail (an SSM Automation association that targets several tags, for example), *and* no layer that sees the same synthesized template already blocks it. There is no further \"is this worth a rule\" question — if the gap is real, it gets a rule.\n\nCDK L2 construct validation is deliberately **not** one of those layers. `CfnXxx` usage, escape hatches, `addPropertyOverride`, `cloudformation-include` and migrated templates all bypass L2, so an L2 guard covering the same mistake neither disqualifies a rule nor retires one.\n\nThat makes growth the normal state, and it has a consequence worth knowing before you upgrade: **new rules land in minor releases, so a minor upgrade can newly fail a `cdk synth` that passed yesterday.** That is intended, not a regression. If you need a frozen rule set, pin the version; to drop a single rule, `exclude: ['<rule-id>']`; to see everything without failing the build, `enforce: false`.\n\nRules move the other way too. Once the validation engine bundled in `aws-cdk-lib` starts blocking a constraint, the rule records the engine version that covers it and stops being evaluated on that engine or later — but only where that changes nothing about whether synth fails: with `enforce: false`, with `strict: true`, or when your app sets the context `@aws-cdk/core:validateAgainstDefaultRules: true`. In the default enforce mode the CDK reports its own engine's errors as warnings, so the rule keeps running and you may see both. Users on an older `aws-cdk-lib` keep the rule either way; it is deleted only once the minimum supported `aws-cdk-lib` bundles an engine that covers it.\n\n## How it works\n\n`Preflight.apply()` evaluates the rules with the [cloudformation-validate](https://github.com/aws-cloudformation/cloudformation-validate) Rust/WASM engine that ships inside `aws-cdk-lib` — no extra binaries, and no network access at synth time unless you opt in to `checkQuotas`, which calls AWS from a child process. In the default enforce mode the engine is invoked through a dedicated CDK validation plugin so that violations fail synthesis; with `enforce: false` the rules are instead injected into the CDK built-in `CloudFormationValidatePlugin` and reported as warnings.\n\nConstraints that *can* be expressed in schemas or generic engine rules also make good upstream PRs to that engine, but nothing here waits on one — the upstream release cycle is deliberately slower than this pack's. Each rule's `meta.yaml` tracks its upstream status so that retirement stays bookkeeping.\n\n## Requirements\n\n- `aws-cdk-lib` >= 2.267.0, released 2026-08-27 (the first release that bundles the built-in CloudFormation validator). This is a recent release — an existing CDK app may need an upgrade before cdk-preflight can run.\n\n## Contributing\n\nRule authoring, the verification gates (including real-deploy reproduction), and the test layout are documented in [AGENTS.md](AGENTS.md) — written for AI coding agents and humans alike.\n\n## License\n\nApache-2.0\n"
9630
9630
  },
9631
9631
  "repository": {
9632
9632
  "type": "git",
@@ -9847,6 +9847,6 @@
9847
9847
  "symbolId": "src/index:PreflightOptions"
9848
9848
  }
9849
9849
  },
9850
- "version": "1.7.3",
9851
- "fingerprint": "wDZ2WTb3x6BYjrGxjvgWmxvtuHh4FejFpjXTTXG2gfw="
9850
+ "version": "1.7.4",
9851
+ "fingerprint": "rcAxyQU3V2Ctfo1F0GpsU0lJ9aCPEsHVC2ZN0TIWHgI="
9852
9852
  }
package/README.md CHANGED
@@ -239,7 +239,7 @@ Resource names are relative to `AWS::<Service>::`; the number in parentheses is
239
239
  | **GlobalAccelerator** | `Accelerator` (1), `EndpointGroup` (5), `Listener` (2) |
240
240
  | **Glue** | `Classifier` (6), `Connection` (5), `Crawler` (11), `CustomEntityType` (1), `Database` (1), `DataQualityRuleset` (1), `Job` (15), `MLTransform` (4), `Partition` (1), `Schema` (3), `SecurityConfiguration` (1), `Table` (1), `Trigger` (10), `UserDefinedFunction` (2), `Workflow` (1) |
241
241
  | **Grafana** | `Workspace` (7) |
242
- | **IAM** | `Group` (23), `GroupPolicy` (1), `InstanceProfile` (2), `ManagedPolicy` (31), `OIDCProvider` (2), `Policy` (29), `Role` (44), `RolePolicy` (1), `ServiceLinkedRole` (1), `User` (24), `UserPolicy` (1) |
242
+ | **IAM** | `Group` (23), `GroupPolicy` (19), `InstanceProfile` (2), `ManagedPolicy` (31), `OIDCProvider` (2), `Policy` (29), `Role` (44), `RolePolicy` (19), `ServiceLinkedRole` (1), `User` (24), `UserPolicy` (19) |
243
243
  | **IoT** | `Authorizer` (3), `Certificate` (1), `CertificateProvider` (1), `Command` (1), `DomainConfiguration` (3), `JobTemplate` (3), `MitigationAction` (1), `Policy` (4), `PolicyPrincipalAttachment` (1), `ProvisioningTemplate` (3), `RoleAlias` (1), `ScheduledAudit` (1), `Thing` (1), `ThingGroup` (2), `TopicRule` (5), `TopicRuleDestination` (2) |
244
244
  | **Kinesis** | `ResourcePolicy` (4), `Stream` (5), `StreamConsumer` (2) |
245
245
  | **KinesisAnalyticsV2** | `Application` (20), `ApplicationCloudWatchLoggingOption` (1), `ApplicationOutput` (1), `ApplicationReferenceDataSource` (1) |
@@ -259,7 +259,7 @@ Resource names are relative to `AWS::<Service>::`; the number in parentheses is
259
259
  | **RDS** | `DBCluster` (19), `DBInstance` (33), `DBParameterGroup` (2), `DBProxy` (4), `DBProxyTargetGroup` (2), `DBShardGroup` (1), `DBSubnetGroup` (3), `EventSubscription` (3), `OptionGroup` (1) |
260
260
  | **Redshift** | `Cluster` (20), `EventSubscription` (1), `ScheduledAction` (2) |
261
261
  | **RedshiftServerless** | `Namespace` (5), `Workgroup` (6) |
262
- | **Route53** | `CidrCollection` (11), `DNSSEC` (3), `HealthCheck` (30), `HostedZone` (14), `KeySigningKey` (9), `RecordSet` (74), `RecordSetGroup` (75) |
262
+ | **Route53** | `CidrCollection` (11), `DNSSEC` (3), `HealthCheck` (30), `HostedZone` (14), `KeySigningKey` (9), `RecordSet` (74), `RecordSetGroup` (76) |
263
263
  | **Route53Profiles** | `ProfileAssociation` (1), `ProfileResourceAssociation` (6) |
264
264
  | **Route53Resolver** | `FirewallDomainList` (3), `FirewallRuleGroup` (18), `FirewallRuleGroupAssociation` (4), `ResolverDNSSECConfig` (1), `ResolverEndpoint` (18), `ResolverQueryLoggingConfig` (1), `ResolverQueryLoggingConfigAssociation` (1), `ResolverRule` (15), `ResolverRuleAssociation` (1) |
265
265
  | **S3** | `AccessPoint` (1), `Bucket` (54), `BucketPolicy` (4), `StorageLens` (5), `StorageLensGroup` (3) |
package/docs/rules.md CHANGED
@@ -121,8 +121,8 @@
121
121
  | `pf-apigwv2-model-websocket-only` | AWS::ApiGatewayV2::Model | Models exist on WebSocket APIs only | ERROR | none |
122
122
  | `pf-apigwv2-request-authorizer-payload-version` | AWS::ApiGatewayV2::Authorizer | REQUEST authorizers on HTTP APIs need AuthorizerPayloadFormatVersion | ERROR | none |
123
123
  | `pf-apigwv2-request-authorizer-uri-required` | AWS::ApiGatewayV2::Authorizer | A REQUEST authorizer needs an AuthorizerUri | ERROR | none |
124
- | `pf-apigwv2-request-parameter-action` | AWS::ApiGatewayV2::Integration | Request parameter keys are <action>:<header|querystring|path>.<name> | ERROR | none |
125
- | `pf-apigwv2-response-parameter-action` | AWS::ApiGatewayV2::Integration | Response parameter keys are <action>:<header.name|statuscode> | ERROR | none |
124
+ | `pf-apigwv2-request-parameter-action` | AWS::ApiGatewayV2::Integration | Request parameter keys are <action>:<header\|querystring\|path>.<name> | ERROR | none |
125
+ | `pf-apigwv2-response-parameter-action` | AWS::ApiGatewayV2::Integration | Response parameter keys are <action>:<header.name\|statuscode> | ERROR | none |
126
126
  | `pf-apigwv2-response-parameter-status-code` | AWS::ApiGatewayV2::Integration | Response parameter mappings are keyed by a 200-599 status | ERROR | none |
127
127
  | `pf-apigwv2-route-authorization-scopes-jwt` | AWS::ApiGatewayV2::Route | AuthorizationScopes needs JWT authorization | ERROR | none |
128
128
  | `pf-apigwv2-route-jwt-authorizer-id` | AWS::ApiGatewayV2::Route | JWT authorization needs an AuthorizerId | ERROR | none |
@@ -171,7 +171,7 @@
171
171
  | `pf-appautoscaling-schedule-cron-day-fields` | AWS::ApplicationAutoScaling::ScalableTarget | cron() needs '?' in exactly one of day-of-month and day-of-week | ERROR | none |
172
172
  | `pf-appautoscaling-schedule-expression-form` | AWS::ApplicationAutoScaling::ScalableTarget | ScheduledAction Schedule must be at(), rate() or cron() | ERROR | none |
173
173
  | `pf-appautoscaling-schedule-rate-unit` | AWS::ApplicationAutoScaling::ScalableTarget | rate() takes a positive integer and minute(s), hour(s) or day(s) | ERROR | none |
174
- | `pf-appautoscaling-scheduled-action-name-chars` | AWS::ApplicationAutoScaling::ScalableTarget | ScheduledActionName may not contain ':', '/', '|', control characters or edge spaces | ERROR | none |
174
+ | `pf-appautoscaling-scheduled-action-name-chars` | AWS::ApplicationAutoScaling::ScalableTarget | ScheduledActionName may not contain ':', '/', '\|', control characters or edge spaces | ERROR | none |
175
175
  | `pf-appautoscaling-scheduled-action-start-end-order` | AWS::ApplicationAutoScaling::ScalableTarget | ScheduledAction StartTime must be before EndTime | ERROR | none |
176
176
  | `pf-appautoscaling-scheduled-action-timezone-iana` | AWS::ApplicationAutoScaling::ScalableTarget | ScheduledAction Timezone must be an IANA time zone name | ERROR | none |
177
177
  | `pf-appautoscaling-step-adjustment-both-null` | AWS::ApplicationAutoScaling::ScalingPolicy | A step adjustment must carry at least one bound | ERROR | none |
@@ -1328,7 +1328,7 @@
1328
1328
  | `pf-dynamodb-global-table-write-provisioned-with-ppr` | AWS::DynamoDB::GlobalTable | WriteProvisionedThroughputSettings cannot be used with PAY_PER_REQUEST | ERROR | none |
1329
1329
  | `pf-dynamodb-gsi-billing-throughput` | AWS::DynamoDB::Table | GSI ProvisionedThroughput must match the table BillingMode | ERROR | none |
1330
1330
  | `pf-dynamodb-gsi-projection-nonkey` | AWS::DynamoDB::Table | NonKeyAttributes goes with INCLUDE, and only with INCLUDE | ERROR | none |
1331
- | `pf-dynamodb-import-csv-delimiter` | AWS::DynamoDB::Table | A CSV import delimiter is a single character from , ; : | tab space | ERROR | none |
1331
+ | `pf-dynamodb-import-csv-delimiter` | AWS::DynamoDB::Table | A CSV import delimiter is a single character from , ; : \| tab space | ERROR | none |
1332
1332
  | `pf-dynamodb-import-format-options` | AWS::DynamoDB::Table | InputFormatOptions only applies to CSV imports | ERROR | none |
1333
1333
  | `pf-dynamodb-key-schema-shape` | AWS::DynamoDB::Table | KeySchema must be [HASH] or [HASH, RANGE] | ERROR | none |
1334
1334
  | `pf-dynamodb-kinesis-precision` | AWS::DynamoDB::Table | Kinesis record timestamp precision is MICROSECOND or MILLISECOND | ERROR | pending-engine |
@@ -2038,32 +2038,32 @@
2038
2038
  | `pf-iam-path-service-role-reserved` | AWS::IAM::Role<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::InstanceProfile<br>AWS::IAM::ManagedPolicy | The /aws-service-role/ path is reserved | ERROR | none |
2039
2039
  | `pf-iam-permissions-boundary-policy-arn` | AWS::IAM::Role<br>AWS::IAM::User | A permissions boundary is a policy ARN | ERROR | none |
2040
2040
  | `pf-iam-policy-action-format` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy | Actions must carry a service prefix | ERROR | none |
2041
- | `pf-iam-policy-action-required` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | Every statement needs an Action or NotAction | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2042
- | `pf-iam-policy-action-single-colon` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | An action carries exactly one colon | ERROR | none |
2043
- | `pf-iam-policy-action-vendor-wildcard` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | The service prefix of an action takes no wildcard | ERROR | none |
2041
+ | `pf-iam-policy-action-required` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | Every statement needs an Action or NotAction | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2042
+ | `pf-iam-policy-action-single-colon` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | An action carries exactly one colon | ERROR | none |
2043
+ | `pf-iam-policy-action-vendor-wildcard` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | The service prefix of an action takes no wildcard | ERROR | none |
2044
2044
  | `pf-iam-policy-arn-partition` | AWS::IAM::Role<br>AWS::IAM::User<br>AWS::IAM::Group | Attached policy ARNs live in the deploy partition | ERROR | none |
2045
- | `pf-iam-policy-condition-arn-value` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | Arn condition operators need an ARN | ERROR | none |
2046
- | `pf-iam-policy-condition-binary-value` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | BinaryEquals takes base64 text | ERROR | none |
2047
- | `pf-iam-policy-condition-date-value` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | Date condition operators need ISO 8601 or epoch seconds | ERROR | none |
2048
- | `pf-iam-policy-condition-ip-value` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | IpAddress condition operators need an IP address or CIDR | ERROR | none |
2049
- | `pf-iam-policy-condition-null-ifexists` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | Null does not take the IfExists suffix | ERROR | none |
2050
- | `pf-iam-policy-condition-numeric-value` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | Numeric condition operators need a numeric value | ERROR | none |
2045
+ | `pf-iam-policy-condition-arn-value` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | Arn condition operators need an ARN | ERROR | none |
2046
+ | `pf-iam-policy-condition-binary-value` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | BinaryEquals takes base64 text | ERROR | none |
2047
+ | `pf-iam-policy-condition-date-value` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | Date condition operators need ISO 8601 or epoch seconds | ERROR | none |
2048
+ | `pf-iam-policy-condition-ip-value` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | IpAddress condition operators need an IP address or CIDR | ERROR | none |
2049
+ | `pf-iam-policy-condition-null-ifexists` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | Null does not take the IfExists suffix | ERROR | none |
2050
+ | `pf-iam-policy-condition-numeric-value` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | Numeric condition operators need a numeric value | ERROR | none |
2051
2051
  | `pf-iam-policy-condition-operator` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy | Condition operators come from a closed grammar | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2052
- | `pf-iam-policy-condition-value-type` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | Condition values are strings, not nested objects | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2052
+ | `pf-iam-policy-condition-value-type` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | Condition values are strings, not nested objects | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2053
2053
  | `pf-iam-policy-duplicate-sid` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy | Statement IDs must be unique within a policy | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2054
2054
  | `pf-iam-policy-effect-case` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy | Effect is case-sensitive Allow or Deny | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2055
- | `pf-iam-policy-effect-required` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | Every statement needs an Effect | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2055
+ | `pf-iam-policy-effect-required` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | Every statement needs an Effect | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2056
2056
  | `pf-iam-policy-exclusive-fields` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy | Action/NotAction and Resource/NotResource are exclusive pairs | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2057
- | `pf-iam-policy-resource-arn-segments` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | A resource ARN carries six colon-separated segments | ERROR | none |
2057
+ | `pf-iam-policy-resource-arn-segments` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | A resource ARN carries six colon-separated segments | ERROR | none |
2058
2058
  | `pf-iam-policy-resource-format` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy | Resources must be ARNs or * | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2059
- | `pf-iam-policy-resource-global-service-arn` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | S3, IAM and Route 53 ARNs carry no region | ERROR | none |
2060
- | `pf-iam-policy-resource-partition` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | Resource ARNs live in the deploy partition | ERROR | none |
2061
- | `pf-iam-policy-resource-service-wildcard` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | The service segment of a resource ARN takes no wildcard | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2059
+ | `pf-iam-policy-resource-global-service-arn` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | S3, IAM and Route 53 ARNs carry no region | ERROR | none |
2060
+ | `pf-iam-policy-resource-partition` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | Resource ARNs live in the deploy partition | ERROR | none |
2061
+ | `pf-iam-policy-resource-service-wildcard` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | The service segment of a resource ARN takes no wildcard | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2062
2062
  | `pf-iam-policy-sid-format` | AWS::IAM::Role<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy | Policy statement Sid must be alphanumeric | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2063
- | `pf-iam-policy-statement-required` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | A policy document needs a non-empty Statement | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2063
+ | `pf-iam-policy-statement-required` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | A policy document needs a non-empty Statement | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2064
2064
  | `pf-iam-policy-statement-resource-required` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy | Identity policy statements need Resource or NotResource | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2065
- | `pf-iam-policy-statement-unknown-field` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | A statement holds only the documented policy elements | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2066
- | `pf-iam-policy-unknown-toplevel-field` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group | A policy document holds only Version, Id and Statement | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2065
+ | `pf-iam-policy-statement-unknown-field` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | A statement holds only the documented policy elements | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2066
+ | `pf-iam-policy-unknown-toplevel-field` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy<br>AWS::IAM::User<br>AWS::IAM::Group<br>AWS::IAM::RolePolicy<br>AWS::IAM::UserPolicy<br>AWS::IAM::GroupPolicy | A policy document holds only Version, Id and Statement | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2067
2067
  | `pf-iam-policy-version` | AWS::IAM::Role<br>AWS::IAM::Policy<br>AWS::IAM::ManagedPolicy | Policy Version must be 2012-10-17 or 2008-10-17 | ERROR | retired: engine E3510 covers it from aws-cdk-lib 2.268.0 |
2068
2068
  | `pf-iam-slr-service-name-format` | AWS::IAM::ServiceLinkedRole | AWSServiceName is a service principal | ERROR | none |
2069
2069
  | `pf-iam-trust-policy-action-sts-only` | AWS::IAM::Role | A trust policy allows only the STS AssumeRole family | ERROR | none |
@@ -2147,7 +2147,7 @@
2147
2147
  | `pf-kms-key-policy-statement` | AWS::KMS::Key<br>AWS::KMS::ReplicaKey | Every KeyPolicy statement needs Principal, Action and Resource, the Statement list must not be empty, and AWS principals must be *, an account id or an IAM/STS ARN | ERROR | none |
2148
2148
  | `pf-kms-key-rotation` | AWS::KMS::Key | EnableKeyRotation is only valid for SYMMETRIC_DEFAULT keys with AWS_KMS key material (not asymmetric, HMAC, or Origin EXTERNAL) | ERROR | none |
2149
2149
  | `pf-kms-key-spec-region` | AWS::KMS::Key | KeySpec SM2 exists only in China regions | ERROR | none |
2150
- | `pf-kms-key-spec-usage` | AWS::KMS::Key | KeyUsage must match KeySpec (symmetric: ENCRYPT_DECRYPT, HMAC: GENERATE_VERIFY_MAC, RSA: ENCRYPT_DECRYPT|SIGN_VERIFY, NIST ECC: SIGN_VERIFY|KEY_AGREEMENT, ed25519/secp256k1/ML-DSA: SIGN_VERIFY) and is required for every non-symmetric key | ERROR | none |
2150
+ | `pf-kms-key-spec-usage` | AWS::KMS::Key | KeyUsage must match KeySpec (symmetric: ENCRYPT_DECRYPT, HMAC: GENERATE_VERIFY_MAC, RSA: ENCRYPT_DECRYPT\|SIGN_VERIFY, NIST ECC: SIGN_VERIFY\|KEY_AGREEMENT, ed25519/secp256k1/ML-DSA: SIGN_VERIFY) and is required for every non-symmetric key | ERROR | none |
2151
2151
  | `pf-kms-replica-primary-arn` | AWS::KMS::ReplicaKey | ReplicaKey.PrimaryKeyArn must be the ARN of a multi-Region primary key (key/mrk-...) in a different region of the same partition, so a primary key in the same stack can never be replicated by it | ERROR | none |
2152
2152
  | `pf-lambda-alias-additional-version-distinct` | AWS::Lambda::Alias | A weighted alias must route to a different version | ERROR | none |
2153
2153
  | `pf-lambda-alias-additional-version-not-latest` | AWS::Lambda::Alias | A routing entry cannot name $LATEST | ERROR | none |
@@ -2822,7 +2822,7 @@
2822
2822
  | `pf-route53-weight-range` | AWS::Route53::RecordSet | Weight must be between 0 and 255 | ERROR | pending-engine |
2823
2823
  | `pf-route53-weighted-group-same-ttl` | AWS::Route53::RecordSetGroup | Weighted record sets sharing a name and type must have the same TTL | ERROR | none |
2824
2824
  | `pf-route53-weighted-max-100-same-name-type` | AWS::Route53::RecordSetGroup | A weighted group may hold at most 100 record sets per name and type | ERROR | none |
2825
- | `pf-route53-zonename-trailing-dot` | AWS::Route53::RecordSet | HostedZoneName must end with a trailing dot | ERROR | none |
2825
+ | `pf-route53-zonename-trailing-dot` | AWS::Route53::RecordSet<br>AWS::Route53::RecordSetGroup | HostedZoneName must end with a trailing dot | ERROR | none |
2826
2826
  | `pf-route53profiles-profileassociation-one-per-vpc` | AWS::Route53Profiles::ProfileAssociation | A VPC can only be associated with one Route 53 Profile | ERROR | none |
2827
2827
  | `pf-route53profiles-profileresourceassociation-priority-range` | AWS::Route53Profiles::ProfileResourceAssociation | A ProfileResourceAssociation priority must be between 101 and 9899 | ERROR | none |
2828
2828
  | `pf-route53profiles-profileresourceassociation-querylogging-max` | AWS::Route53Profiles::ProfileResourceAssociation | A Profile cannot hold more than one Resolver query logging config | ERROR | none |
@@ -3057,7 +3057,7 @@
3057
3057
  | `pf-ses-sns-destination-topic-region` | AWS::SES::ConfigurationSetEventDestination | An SNS event destination must publish to a topic in the deployment Region | ERROR | none |
3058
3058
  | `pf-ses-template-html-or-text-part` | AWS::SES::Template | An email template needs an HtmlPart or a TextPart | ERROR | none |
3059
3059
  | `pf-sns-aws-managed-key-delivery` | AWS::SNS::Topic<br>AWS::CloudWatch::Alarm<br>AWS::CloudWatch::CompositeAlarm<br>AWS::Events::Rule | CloudWatch alarms and EventBridge cannot publish to a topic encrypted with the AWS managed key alias/aws/sns | ERROR | none |
3060
- | `pf-sns-delivery-policy` | AWS::SNS::Subscription<br>AWS::SNS::Topic | HTTP/S DeliveryPolicy retry values: minDelayTarget >= 1, maxDelayTarget <= 3600 and >= minDelayTarget, numRetries 0..100 and at least the sum of the phase retries, phase counts >= 0, backoffFunction one of arithmetic|exponential|geometric|linear, maxReceivesPerSecond >= 1 (subscription DeliveryPolicy and topic DeliveryPolicy.http) | ERROR | none |
3060
+ | `pf-sns-delivery-policy` | AWS::SNS::Subscription<br>AWS::SNS::Topic | HTTP/S DeliveryPolicy retry values: minDelayTarget >= 1, maxDelayTarget <= 3600 and >= minDelayTarget, numRetries 0..100 and at least the sum of the phase retries, phase counts >= 0, backoffFunction one of arithmetic\|exponential\|geometric\|linear, maxReceivesPerSecond >= 1 (subscription DeliveryPolicy and topic DeliveryPolicy.http) | ERROR | none |
3061
3061
  | `pf-sns-fifo-only-attributes` | AWS::SNS::Topic | ContentBasedDeduplication, ArchivePolicy and FifoThroughputScope are FIFO-only topic attributes, and ArchivePolicy.MessageRetentionPeriod is 1..365 days | ERROR | none |
3062
3062
  | `pf-sns-fifo-queue-on-standard-topic` | AWS::SNS::Subscription<br>AWS::SNS::Topic | A FIFO SQS queue cannot subscribe to a standard topic (a FIFO topic may fan out to standard queues, not the reverse) | ERROR | none |
3063
3063
  | `pf-sns-fifo-topic-name` | AWS::SNS::Topic | FIFO topic names must end with '.fifo' (and '.fifo' names require FifoTopic) | ERROR | none |
@@ -3074,11 +3074,11 @@
3074
3074
  | `pf-sqs-aws-managed-key-delivery` | AWS::SQS::Queue<br>AWS::SNS::Subscription<br>AWS::SNS::Topic<br>AWS::Events::Rule | SNS and EventBridge cannot deliver to a queue encrypted with the AWS managed key alias/aws/sqs | ERROR | none |
3075
3075
  | `pf-sqs-dlq-same-type` | AWS::SQS::Queue | A dead-letter queue named by a literal ARN must be the same type as its source (FIFO for FIFO, standard for standard); Fn::GetAtt wiring is covered by engine rule E3502 | ERROR | none |
3076
3076
  | `pf-sqs-fifo-name-suffix` | AWS::SQS::Queue | A QueueName ending in .fifo requires FifoQueue: true (a FIFO queue without the suffix is engine rule E2504) | ERROR | none |
3077
- | `pf-sqs-fifo-only-attributes` | AWS::SQS::Queue | ContentBasedDeduplication, DeduplicationScope and FifoThroughputLimit are FIFO-only queue attributes (and the two enums are messageGroup|queue and perQueue|perMessageGroupId) | ERROR | none |
3077
+ | `pf-sqs-fifo-only-attributes` | AWS::SQS::Queue | ContentBasedDeduplication, DeduplicationScope and FifoThroughputLimit are FIFO-only queue attributes (and the two enums are messageGroup\|queue and perQueue\|perMessageGroupId) | ERROR | none |
3078
3078
  | `pf-sqs-high-throughput-pairing` | AWS::SQS::Queue | FifoThroughputLimit perMessageGroupId requires DeduplicationScope messageGroup | ERROR | none |
3079
3079
  | `pf-sqs-queue-name-format` | AWS::SQS::Queue | Queue names allow only letters, numbers, hyphen and underscore (max 80, plus a .fifo suffix) | ERROR | none |
3080
3080
  | `pf-sqs-queue-policy-queues` | AWS::SQS::QueuePolicy | QueuePolicy.Queues takes queue URLs (Ref Queue), never ARNs (Fn::GetAtt Queue.Arn), and must not be empty | ERROR | none |
3081
- | `pf-sqs-redrive-allow-policy` | AWS::SQS::Queue | RedriveAllowPolicy: redrivePermission is required (allowAll|denyAll|byQueue); byQueue needs 1..10 sourceQueueArns and the other two forbid them | ERROR | none |
3081
+ | `pf-sqs-redrive-allow-policy` | AWS::SQS::Queue | RedriveAllowPolicy: redrivePermission is required (allowAll\|denyAll\|byQueue); byQueue needs 1..10 sourceQueueArns and the other two forbid them | ERROR | none |
3082
3082
  | `pf-sqs-redrive-arn-region` | AWS::SQS::Queue | The dead-letter target and every RedriveAllowPolicy source queue must sit in the deploy region | ERROR | none |
3083
3083
  | `pf-sqs-redrive-policy` | AWS::SQS::Queue | RedrivePolicy needs deadLetterTargetArn (an SQS queue ARN) and maxReceiveCount in 1..1000 | ERROR | none |
3084
3084
  | `pf-sqs-sse-exclusive` | AWS::SQS::Queue | SqsManagedSseEnabled: true and KmsMasterKeyId are mutually exclusive (one SSE type per queue) | ERROR | none |
@@ -3090,7 +3090,7 @@
3090
3090
  | `pf-ssm-document-name` | AWS::SSM::Document | Document Name must not start with aws-, amazon or amzn (case-insensitive) | ERROR | none |
3091
3091
  | `pf-ssm-document-schema` | AWS::SSM::Document | schemaVersion must match DocumentType (Command 1.0/1.2/2.0/2.2, Automation 0.3, Session 1.0, Policy 2.0/2.2), 2.2 needs an explicit DocumentType, ChangeCalendar needs DocumentFormat TEXT (and TEXT only ChangeCalendar), and ApplicationConfiguration needs Requires | ERROR | none |
3092
3092
  | `pf-ssm-maintenance-window-cutoff` | AWS::SSM::MaintenanceWindow | Cutoff must be smaller than Duration | ERROR | none |
3093
- | `pf-ssm-maintenance-window-schedule` | AWS::SSM::MaintenanceWindow | Schedule must be rate(N minutes|hours|days), a 6/7-field cron() with in-range fields and one of day-of-month/day-of-week as ?, or at(yyyy-MM-ddTHH:mm:ss); ScheduleOffset only with an nth-/last-weekday cron; StartDate/EndDate need a UTC offset with Start before End; ScheduleTimezone is an IANA name or ±HH:MM | ERROR | none |
3093
+ | `pf-ssm-maintenance-window-schedule` | AWS::SSM::MaintenanceWindow | Schedule must be rate(N minutes\|hours\|days), a 6/7-field cron() with in-range fields and one of day-of-month/day-of-week as ?, or at(yyyy-MM-ddTHH:mm:ss); ScheduleOffset only with an nth-/last-weekday cron; StartDate/EndDate need a UTC offset with Start before End; ScheduleTimezone is an IANA name or ±HH:MM | ERROR | none |
3094
3094
  | `pf-ssm-maintenance-window-target` | AWS::SSM::MaintenanceWindowTarget | MaintenanceWindowTarget with ResourceType INSTANCE takes either a single InstanceIds target or up to five tag:/tag-key targets; RESOURCE_GROUP takes a resource-groups:Name target | ERROR | none |
3095
3095
  | `pf-ssm-maintenance-window-task` | AWS::SSM::MaintenanceWindowTask | RUN_COMMAND tasks need Targets; tasks with Targets need MaxConcurrency and MaxErrors (and targetless tasks must not set them); TaskInvocationParameters may only hold the block for the TaskType; target keys are InstanceIds or WindowTargetIds; a NotificationConfig needs ServiceRoleArn | ERROR | none |
3096
3096
  | `pf-ssm-parameter-allowed-pattern` | AWS::SSM::Parameter | Value (each item of a StringList) must match AllowedPattern | ERROR | none |
@@ -3104,7 +3104,7 @@
3104
3104
  | `pf-sfn-asl-duplicate-state-name` | AWS::StepFunctions::StateMachine | ASL state names must be unique across the whole state machine, including Parallel branches and Map item processors | ERROR | none |
3105
3105
  | `pf-sfn-asl-integration-parameters` | AWS::StepFunctions::StateMachine | An arn:<partition>:states::: integration Task needs Parameters (JSONPath) or Arguments (JSONata), and the common integrations need their required fields (lambda:invoke FunctionName, sqs:sendMessage QueueUrl+MessageBody, sns:publish Message, dynamodb:putItem TableName+Item, dynamodb:getItem TableName+Key, states:startExecution StateMachineArn, events:putEvents Entries, ecs:runTask TaskDefinition, glue:startJobRun JobName) | ERROR | none |
3106
3106
  | `pf-sfn-asl-jsonpath-syntax` | AWS::StepFunctions::StateMachine | JSONPath fields start with $ (InputPath, OutputPath, *Path, Choice Variable), ResultPath/ItemsPath are Reference Paths (no *, .., ?, @), values of ".$" keys are a path or a States.* intrinsic, and Assign variable names are alphanumeric and not the reserved "states" | ERROR | none |
3107
- | `pf-sfn-asl-map-distributed` | AWS::StepFunctions::StateMachine | Map ProcessorConfig and ItemReader shape: DISTRIBUTED requires ExecutionType (STANDARD|EXPRESS) and INLINE forbids it; ItemReader.Resource is s3:getObject or s3:listObjectsV2; CSV input needs CSVHeaderLocation (GIVEN needs CSVHeaders) and JSON input forbids it | ERROR | none |
3107
+ | `pf-sfn-asl-map-distributed` | AWS::StepFunctions::StateMachine | Map ProcessorConfig and ItemReader shape: DISTRIBUTED requires ExecutionType (STANDARD\|EXPRESS) and INLINE forbids it; ItemReader.Resource is s3:getObject or s3:listObjectsV2; CSV input needs CSVHeaderLocation (GIVEN needs CSVHeaders) and JSON input forbids it | ERROR | none |
3108
3108
  | `pf-sfn-asl-missing-state` | AWS::StepFunctions::StateMachine | ASL Next/Default/Choices[].Next/Catch[].Next must reference a state defined in the same States block, at every nesting level (a dangling StartAt is covered by engine rule E3601) | ERROR | none |
3109
3109
  | `pf-sfn-asl-name-limits` | AWS::StepFunctions::StateMachine | ASL state names are at most 80 characters; a Distributed Map Label is at most 40 characters and unique within the state machine | ERROR | none |
3110
3110
  | `pf-sfn-asl-next-end` | AWS::StepFunctions::StateMachine | A Task/Pass/Wait/Parallel/Map state carries exactly one of Next or End: true | ERROR | none |
@@ -3123,7 +3123,7 @@
3123
3123
  | `pf-sfn-kms-key-region` | AWS::StepFunctions::StateMachine<br>AWS::StepFunctions::Activity | EncryptionConfiguration.KmsKeyId must be a key in the deploy region (state machines and activities) | ERROR | none |
3124
3124
  | `pf-sfn-logging-destination` | AWS::StepFunctions::StateMachine | LoggingConfiguration with Level other than OFF needs exactly one Destinations entry carrying CloudWatchLogsLogGroup.LogGroupArn, and a literal ARN must be a CloudWatch Logs log-group ARN ending in :* | ERROR | none |
3125
3125
  | `pf-sfn-logging-name-ascii` | AWS::StepFunctions::StateMachine | With CloudWatch Logs logging enabled (Level other than OFF) the StateMachineName may only contain 0-9, A-Z, a-z, . - and _ (non-ASCII names are accepted only without logging) | ERROR | none |
3126
- | `pf-sfn-name-charset` | AWS::StepFunctions::StateMachine<br>AWS::StepFunctions::Activity | StateMachineName and Activity Name may not contain whitespace, brackets (< > { } [ ]), wildcards (? *), the special characters " # % \ ^ | ~ ` $ & , ; : / or control characters (non-ASCII letters are accepted) | ERROR | none |
3126
+ | `pf-sfn-name-charset` | AWS::StepFunctions::StateMachine<br>AWS::StepFunctions::Activity | StateMachineName and Activity Name may not contain whitespace, brackets (< > { } [ ]), wildcards (? *), the special characters " # % \ ^ \| ~ ` $ & , ; : / or control characters (non-ASCII letters are accepted) | ERROR | none |
3127
3127
  | `pf-synthetics-canary-active-tracing-runtime` | AWS::Synthetics::Canary | ActiveTracing is not available on the Python runtimes | ERROR | none |
3128
3128
  | `pf-synthetics-canary-code-handler-blueprint-exclusive` | AWS::Synthetics::Canary | Code.Handler cannot be combined with Code.BlueprintTypes | ERROR | none |
3129
3129
  | `pf-synthetics-canary-code-s3-key-required` | AWS::Synthetics::Canary | Code.S3Bucket requires Code.S3Key | ERROR | none |
package/lib/index.js CHANGED
@@ -18,7 +18,7 @@ const rules_generated_1 = require("./rules.generated");
18
18
  * Preflight.apply(app);
19
19
  */
20
20
  class Preflight {
21
- static [JSII_RTTI_SYMBOL_1] = { fqn: "cdk-preflight.Preflight", version: "1.7.3" };
21
+ static [JSII_RTTI_SYMBOL_1] = { fqn: "cdk-preflight.Preflight", version: "1.7.4" };
22
22
  /**
23
23
  * Register the cdk-preflight rules on an App or Stage.
24
24
  */