cdk-preflight 0.0.149 → 0.0.150

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
@@ -9416,7 +9416,7 @@
9416
9416
  },
9417
9417
  "name": "cdk-preflight",
9418
9418
  "readme": {
9419
- "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-2606-blue\" alt=\"2606 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 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.\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\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## 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>333 resource types across 59 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)** | `*` (2) |\n| **ApiGateway** | `ApiKey` (1), `Authorizer` (5), `Deployment` (1), `DocumentationPart` (1), `DomainName` (4), `GatewayResponse` (1), `Method` (12), `Model` (3), `Resource` (1), `RestApi` (4), `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| **Athena** | `DataCatalog` (6), `WorkGroup` (8) |\n| **AutoScaling** | `AutoScalingGroup` (38), `LifecycleHook` (8), `ScalingPolicy` (41), `ScheduledAction` (13), `WarmPool` (4) |\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| **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| **CloudWatch** | `Alarm` (21), `AnomalyDetector` (6), `CompositeAlarm` (6), `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| **DocDB** | `DBCluster` (14), `DBInstance` (1), `DBSubnetGroup` (2), `EventSubscription` (1) |\n| **DocDBElastic** | `Cluster` (7) |\n| **DynamoDB** | `GlobalTable` (26), `Table` (28) |\n| **EC2** | `ClientVpnAuthorizationRule` (1), `ClientVpnEndpoint` (6), `DHCPOptions` (2), `EIPAssociation` (1), `FlowLog` (4), `Instance` (10), `KeyPair` (1), `LaunchTemplate` (5), `NatGateway` (2), `NetworkAclEntry` (1), `NetworkInterface` (2), `PlacementGroup` (3), `PrefixList` (3), `Route` (1), `SecurityGroup` (6), `SecurityGroupEgress` (5), `SecurityGroupIngress` (5), `Subnet` (10), `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` (64), `TaskSet` (2) |\n| **EFS** | `AccessPoint` (1), `FileSystem` (7), `MountTarget` (4) |\n| **ElastiCache** | `CacheCluster` (9), `ReplicationGroup` (14), `User` (2), `UserGroup` (1) |\n| **ElasticLoadBalancingV2** | `Listener` (57), `ListenerCertificate` (2), `ListenerRule` (37), `LoadBalancer` (37), `TargetGroup` (58), `TrustStore` (1), `TrustStoreRevocation` (1) |\n| **Events** | `ApiDestination` (1), `Archive` (5), `Connection` (1), `Endpoint` (2), `EventBus` (2), `Rule` (23) |\n| **EventSchemas** | `Discoverer` (1), `Registry` (1), `RegistryPolicy` (1), `Schema` (1) |\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| **IAM** | `Group` (23), `GroupPolicy` (1), `InstanceProfile` (2), `ManagedPolicy` (31), `OIDCProvider` (2), `Policy` (29), `Role` (40), `RolePolicy` (1), `ServiceLinkedRole` (1), `User` (24), `UserPolicy` (1) |\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` (46), `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| **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| **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` (75), `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` (10), `ScheduleGroup` (1) |\n| **SecretsManager** | `RotationSchedule` (4), `Secret` (5), `SecretTargetAttachment` (1) |\n| **ServiceDiscovery** | `HttpNamespace` (3), `PrivateDnsNamespace` (4), `PublicDnsNamespace` (4), `Service` (12) |\n| **SNS** | `Subscription` (9), `Topic` (12), `TopicPolicy` (1) |\n| **SQS** | `Queue` (9), `QueuePolicy` (1) |\n| **SSM** | `Association` (3), `Document` (3), `MaintenanceWindow` (2), `MaintenanceWindowTarget` (1), `MaintenanceWindowTask` (1), `Parameter` (5) |\n| **StepFunctions** | `Activity` (3), `StateMachine` (26) |\n| **Synthetics** | `Canary` (16) |\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 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 *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` (or CloudFormation's own pre-deploy validation) starts blocking a constraint, the rule is deleted rather than kept as a duplicate — staying on an older `aws-cdk-lib` and an older cdk-preflight keeps the old behavior.\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, no network access at synth time. 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"
9419
+ "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-2733-blue\" alt=\"2733 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 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.\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\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## 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>359 resource types across 65 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)** | `*` (2) |\n| **ApiGateway** | `ApiKey` (1), `Authorizer` (5), `Deployment` (1), `DocumentationPart` (1), `DomainName` (4), `GatewayResponse` (1), `Method` (12), `Model` (3), `Resource` (1), `RestApi` (4), `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| **Athena** | `DataCatalog` (6), `WorkGroup` (8) |\n| **AutoScaling** | `AutoScalingGroup` (38), `LifecycleHook` (8), `ScalingPolicy` (41), `ScheduledAction` (13), `WarmPool` (4) |\n| **Backup** | `BackupPlan` (12), `BackupSelection` (4), `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| **CertificateManager** | `Certificate` (4) |\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` (21), `AnomalyDetector` (6), `CompositeAlarm` (6), `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` (26), `Table` (28) |\n| **EC2** | `ClientVpnAuthorizationRule` (1), `ClientVpnEndpoint` (6), `DHCPOptions` (2), `EIPAssociation` (1), `FlowLog` (4), `Instance` (10), `KeyPair` (1), `LaunchTemplate` (5), `NatGateway` (2), `NetworkAclEntry` (1), `NetworkInterface` (2), `PlacementGroup` (3), `PrefixList` (3), `Route` (1), `SecurityGroup` (6), `SecurityGroupEgress` (5), `SecurityGroupIngress` (5), `Subnet` (10), `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` (64), `TaskSet` (2) |\n| **EFS** | `AccessPoint` (1), `FileSystem` (7), `MountTarget` (4) |\n| **ElastiCache** | `CacheCluster` (9), `ReplicationGroup` (14), `User` (2), `UserGroup` (1) |\n| **ElasticLoadBalancingV2** | `Listener` (57), `ListenerCertificate` (2), `ListenerRule` (37), `LoadBalancer` (37), `TargetGroup` (58), `TrustStore` (1), `TrustStoreRevocation` (1) |\n| **Events** | `ApiDestination` (1), `Archive` (5), `Connection` (1), `Endpoint` (2), `EventBus` (2), `Rule` (23) |\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| **IAM** | `Group` (23), `GroupPolicy` (1), `InstanceProfile` (2), `ManagedPolicy` (31), `OIDCProvider` (2), `Policy` (29), `Role` (40), `RolePolicy` (1), `ServiceLinkedRole` (1), `User` (24), `UserPolicy` (1) |\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` (46), `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| **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| **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` (75), `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` (10), `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` (9), `Topic` (12), `TopicPolicy` (1) |\n| **SQS** | `Queue` (9), `QueuePolicy` (1) |\n| **SSM** | `Association` (3), `Document` (3), `MaintenanceWindow` (2), `MaintenanceWindowTarget` (1), `MaintenanceWindowTask` (1), `Parameter` (5) |\n| **StepFunctions** | `Activity` (3), `StateMachine` (26) |\n| **Synthetics** | `Canary` (16) |\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 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 *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` (or CloudFormation's own pre-deploy validation) starts blocking a constraint, the rule is deleted rather than kept as a duplicate — staying on an older `aws-cdk-lib` and an older cdk-preflight keeps the old behavior.\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, no network access at synth time. 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"
9420
9420
  },
9421
9421
  "repository": {
9422
9422
  "type": "git",
@@ -9600,6 +9600,6 @@
9600
9600
  "symbolId": "src/index:PreflightOptions"
9601
9601
  }
9602
9602
  },
9603
- "version": "0.0.149",
9604
- "fingerprint": "A4otnslEwYOLkGP7QUmPL/JzqJvizsWuVlb2ePO1aW0="
9603
+ "version": "0.0.150",
9604
+ "fingerprint": "mlBHBlm1JMVN28D/RZM5jCpv430ORK6RMB3DcdZ6YlY="
9605
9605
  }
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  <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>
13
13
  <a href="https://www.npmjs.com/package/cdk-preflight"><img src="https://img.shields.io/npm/v/cdk-preflight.svg" alt="npm version"></a>
14
14
  <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>
15
- <a href="docs/rules.md"><img src="https://img.shields.io/badge/rules-2606-blue" alt="2606 bundled rules"></a>
15
+ <a href="docs/rules.md"><img src="https://img.shields.io/badge/rules-2733-blue" alt="2733 bundled rules"></a>
16
16
  </p>
17
17
 
18
18
  Some 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.
@@ -155,7 +155,7 @@ See [docs/rules.md](docs/rules.md) for the generated rule table.
155
155
 
156
156
  <!-- supported-resources:start -->
157
157
  <details>
158
- <summary><b>333 resource types across 59 services</b> — click to expand</summary>
158
+ <summary><b>359 resource types across 65 services</b> — click to expand</summary>
159
159
 
160
160
  Resource names are relative to `AWS::<Service>::`; the number in parentheses is how many rules target that type.
161
161
 
@@ -168,16 +168,20 @@ Resource names are relative to `AWS::<Service>::`; the number in parentheses is
168
168
  | **AppSync** | `Api` (7), `ApiCache` (4), `ApiKey` (2), `ChannelNamespace` (5), `DataSource` (15), `DomainName` (1), `FunctionConfiguration` (24), `GraphQLApi` (16), `GraphQLSchema` (12), `Resolver` (32), `SourceApiAssociation` (3) |
169
169
  | **Athena** | `DataCatalog` (6), `WorkGroup` (8) |
170
170
  | **AutoScaling** | `AutoScalingGroup` (38), `LifecycleHook` (8), `ScalingPolicy` (41), `ScheduledAction` (13), `WarmPool` (4) |
171
+ | **Backup** | `BackupPlan` (12), `BackupSelection` (4), `BackupVault` (3), `Framework` (3), `LogicallyAirGappedBackupVault` (2), `ReportPlan` (2), `RestoreTestingPlan` (5), `RestoreTestingSelection` (3) |
171
172
  | **Batch** | `ComputeEnvironment` (40), `ConsumableResource` (2), `JobDefinition` (109), `JobQueue` (18), `SchedulingPolicy` (8), `ServiceEnvironment` (6) |
172
173
  | **Bedrock** | `ApplicationInferenceProfile` (2), `AutomatedReasoningPolicy` (3), `Blueprint` (3), `DataAutomationProject` (8), `DataSource` (12), `Flow` (14), `Guardrail` (19), `IntelligentPromptRouter` (5), `KnowledgeBase` (18), `Prompt` (4) |
173
174
  | **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) |
175
+ | **CertificateManager** | `Certificate` (4) |
174
176
  | **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) |
177
+ | **CloudTrail** | `EventDataStore` (1), `Trail` (19) |
175
178
  | **CloudWatch** | `Alarm` (21), `AnomalyDetector` (6), `CompositeAlarm` (6), `Dashboard` (14), `InsightRule` (7), `MetricStream` (6) |
176
179
  | **CodeBuild** | `Project` (37), `ReportGroup` (2), `SourceCredential` (4) |
177
180
  | **CodeCommit** | `Repository` (11) |
178
181
  | **CodeDeploy** | `Application` (7), `DeploymentConfig` (12), `DeploymentGroup` (23) |
179
182
  | **CodePipeline** | `CustomActionType` (3), `Pipeline` (25), `Webhook` (2) |
180
183
  | **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) |
184
+ | **Config** | `ConfigRule` (6), `ConfigurationAggregator` (2), `ConfigurationRecorder` (7), `ConformancePack` (1), `RemediationConfiguration` (2) |
181
185
  | **DocDB** | `DBCluster` (14), `DBInstance` (1), `DBSubnetGroup` (2), `EventSubscription` (1) |
182
186
  | **DocDBElastic** | `Cluster` (7) |
183
187
  | **DynamoDB** | `GlobalTable` (26), `Table` (28) |
@@ -189,6 +193,7 @@ Resource names are relative to `AWS::<Service>::`; the number in parentheses is
189
193
  | **ElasticLoadBalancingV2** | `Listener` (57), `ListenerCertificate` (2), `ListenerRule` (37), `LoadBalancer` (37), `TargetGroup` (58), `TrustStore` (1), `TrustStoreRevocation` (1) |
190
194
  | **Events** | `ApiDestination` (1), `Archive` (5), `Connection` (1), `Endpoint` (2), `EventBus` (2), `Rule` (23) |
191
195
  | **EventSchemas** | `Discoverer` (1), `Registry` (1), `RegistryPolicy` (1), `Schema` (1) |
196
+ | **GlobalAccelerator** | `Accelerator` (1), `EndpointGroup` (5), `Listener` (2) |
192
197
  | **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) |
193
198
  | **IAM** | `Group` (23), `GroupPolicy` (1), `InstanceProfile` (2), `ManagedPolicy` (31), `OIDCProvider` (2), `Policy` (29), `Role` (40), `RolePolicy` (1), `ServiceLinkedRole` (1), `User` (24), `UserPolicy` (1) |
194
199
  | **Kinesis** | `ResourcePolicy` (4), `Stream` (5), `StreamConsumer` (2) |
@@ -212,7 +217,8 @@ Resource names are relative to `AWS::<Service>::`; the number in parentheses is
212
217
  | **S3Express** | `AccessPoint` (3), `DirectoryBucket` (11) |
213
218
  | **Scheduler** | `Schedule` (10), `ScheduleGroup` (1) |
214
219
  | **SecretsManager** | `RotationSchedule` (4), `Secret` (5), `SecretTargetAttachment` (1) |
215
- | **ServiceDiscovery** | `HttpNamespace` (3), `PrivateDnsNamespace` (4), `PublicDnsNamespace` (4), `Service` (12) |
220
+ | **ServiceDiscovery** | `HttpNamespace` (3), `Instance` (19), `PrivateDnsNamespace` (4), `PublicDnsNamespace` (4), `Service` (13) |
221
+ | **SES** | `ConfigurationSetEventDestination` (8), `ContactList` (1), `EmailIdentity` (6), `ReceiptRule` (6), `ReceiptRuleSet` (1), `Template` (1) |
216
222
  | **SNS** | `Subscription` (9), `Topic` (12), `TopicPolicy` (1) |
217
223
  | **SQS** | `Queue` (9), `QueuePolicy` (1) |
218
224
  | **SSM** | `Association` (3), `Document` (3), `MaintenanceWindow` (2), `MaintenanceWindowTarget` (1), `MaintenanceWindowTask` (1), `Parameter` (5) |
package/docs/rules.md CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  | Rule | Resource types | Constraint | Severity | Upstream status |
6
6
  |---|---|---|---|---|
7
+ | `pf-acm-domain-name-cn-64-octets` | AWS::CertificateManager::Certificate | A certificate's DomainName must be at most 64 characters | ERROR | none |
8
+ | `pf-acm-domain-validation-option-known-domain` | AWS::CertificateManager::Certificate | Every DomainValidationOptions entry must name the certificate's own DomainName or a SubjectAlternativeName | ERROR | none |
9
+ | `pf-acm-public-key-algorithm` | AWS::CertificateManager::Certificate | A public ACM certificate takes only RSA_2048, EC_prime256v1 or EC_secp384r1 | ERROR | none |
10
+ | `pf-acm-validation-domain-superdomain` | AWS::CertificateManager::Certificate | An email-validated certificate's ValidationDomain must be a superdomain of the domain it validates | ERROR | none |
7
11
  | `pf-apigw-access-log-format-request-id` | AWS::ApiGateway::Stage | Access log format must include a request id variable | ERROR | none |
8
12
  | `pf-apigw-api-key-value-length` | AWS::ApiGateway::ApiKey | An API key value is at least 20 characters | ERROR | none |
9
13
  | `pf-apigw-authorizer-ttl-range` | AWS::ApiGateway::Authorizer | AuthorizerResultTtlInSeconds tops out at 3600 | ERROR | none |
@@ -375,6 +379,40 @@
375
379
  | `pf-asg-wp-weighted-mixed-instances-incompatible` | AWS::AutoScaling::WarmPool<br>AWS::AutoScaling::AutoScalingGroup | A weighted mixed instances policy takes no warm pool | ERROR | none |
376
380
  | `pf-asg-zonalshift-cross-zone-disabled-requires-skip-validation` | AWS::AutoScaling::AutoScalingGroup | Zonal shift needs cross-zone load balancing on its target groups | ERROR | none |
377
381
  | `pf-asg-zone-or-subnet-required` | AWS::AutoScaling::AutoScalingGroup | A group needs AvailabilityZones, AvailabilityZoneIds, or subnets | ERROR | none |
382
+ | `pf-backup-advanced-setting-backup-options-values` | AWS::Backup::BackupPlan | BackupOptions only accepts WindowsVSS set to enabled or disabled | ERROR | none |
383
+ | `pf-backup-advanced-setting-resource-type-ec2-only` | AWS::Backup::BackupPlan | AdvancedBackupSettings only accepts the EC2 resource type | ERROR | none |
384
+ | `pf-backup-completion-window-gt-start-window` | AWS::Backup::BackupPlan | CompletionWindowMinutes must be at least 60 minutes greater than StartWindowMinutes | ERROR | none |
385
+ | `pf-backup-continuous-delete-after-days-max` | AWS::Backup::BackupPlan | A continuous backup rule cannot retain recovery points for more than 35 days | ERROR | none |
386
+ | `pf-backup-continuous-no-cold-storage` | AWS::Backup::BackupPlan | A continuous backup rule cannot move recovery points to cold storage | ERROR | none |
387
+ | `pf-backup-copy-action-lifecycle-cold-to-delete-gap` | AWS::Backup::BackupPlan | A copy action lifecycle must keep DeleteAfterDays at least 90 days after MoveToColdStorageAfterDays | ERROR | none |
388
+ | `pf-backup-copy-destination-vault-arn-format` | AWS::Backup::BackupPlan | A copy action destination must be a backup vault ARN | ERROR | none |
389
+ | `pf-backup-framework-control-scope-tags-max-one` | AWS::Backup::Framework | A framework control scope may carry at most one tag key/value pair | ERROR | none |
390
+ | `pf-backup-framework-controls-min-one` | AWS::Backup::Framework | A backup audit framework must define at least one control | ERROR | none |
391
+ | `pf-backup-framework-name-charset` | AWS::Backup::Framework | A framework name must start with a letter and use only letters, digits and underscores | ERROR | none |
392
+ | `pf-backup-lag-vault-min-le-max-retention` | AWS::Backup::LogicallyAirGappedBackupVault | A logically air-gapped vault MinRetentionDays may not exceed MaxRetentionDays | ERROR | none |
393
+ | `pf-backup-lag-vault-min-retention-days` | AWS::Backup::LogicallyAirGappedBackupVault | A logically air-gapped vault must retain recovery points for at least 7 days | ERROR | none |
394
+ | `pf-backup-lifecycle-cold-to-delete-gap` | AWS::Backup::BackupPlan | A backup rule lifecycle must keep DeleteAfterDays at least 90 days after MoveToColdStorageAfterDays | ERROR | none |
395
+ | `pf-backup-plan-name-charset` | AWS::Backup::BackupPlan | A backup plan name may only contain alphanumerics, hyphens, underscores and periods | ERROR | none |
396
+ | `pf-backup-report-plan-name-charset` | AWS::Backup::ReportPlan | A report plan name must start with a letter and use only letters, digits and underscores | ERROR | none |
397
+ | `pf-backup-report-setting-framework-arns-required` | AWS::Backup::ReportPlan | A compliance report plan must list the framework ARNs it reports on | ERROR | none |
398
+ | `pf-backup-restore-testing-plan-include-vaults-required` | AWS::Backup::RestoreTestingPlan | A restore testing plan must include at least one backup vault | ERROR | none |
399
+ | `pf-backup-restore-testing-plan-name-charset` | AWS::Backup::RestoreTestingPlan | A restore testing plan name may only contain letters, digits and underscores | ERROR | none |
400
+ | `pf-backup-restore-testing-plan-recovery-point-types` | AWS::Backup::RestoreTestingPlan | RecoveryPointTypes accepts only SNAPSHOT and CONTINUOUS | ERROR | cfn-schema |
401
+ | `pf-backup-restore-testing-plan-selection-window-days` | AWS::Backup::RestoreTestingPlan | A restore testing plan may look back at most 365 days for recovery points | ERROR | none |
402
+ | `pf-backup-restore-testing-plan-start-window-hours` | AWS::Backup::RestoreTestingPlan | A restore testing plan start window may not exceed 168 hours | ERROR | none |
403
+ | `pf-backup-restore-testing-selection-arns-xor-conditions` | AWS::Backup::RestoreTestingSelection | A restore testing selection uses either protected resource ARNs or conditions, not both | ERROR | none |
404
+ | `pf-backup-restore-testing-selection-name-charset` | AWS::Backup::RestoreTestingSelection | A restore testing selection name may only contain letters, digits and underscores | ERROR | none |
405
+ | `pf-backup-restore-testing-selection-protected-resource-type` | AWS::Backup::RestoreTestingSelection | A restore testing selection resource type must be one of the types AWS Backup can restore-test | WARN | none |
406
+ | `pf-backup-rule-name-charset` | AWS::Backup::BackupPlan | A backup rule name may only contain alphanumerics, hyphens, underscores and periods | ERROR | none |
407
+ | `pf-backup-schedule-expression-cron-only` | AWS::Backup::BackupPlan | A backup rule schedule must be a cron() expression | ERROR | none |
408
+ | `pf-backup-selection-conditions-key-prefix` | AWS::Backup::BackupSelection | A selection condition key must be prefixed with aws:ResourceTag/ | ERROR | none |
409
+ | `pf-backup-selection-list-of-tags-string-equals-only` | AWS::Backup::BackupSelection | ListOfTags only accepts the STRINGEQUALS condition type | ERROR | none |
410
+ | `pf-backup-selection-not-resources-max` | AWS::Backup::BackupSelection | NotResources accepts at most 30 wildcard selectors | ERROR | none |
411
+ | `pf-backup-selection-requires-resources-or-tags` | AWS::Backup::BackupSelection | A backup selection must list resources or tags | ERROR | none |
412
+ | `pf-backup-start-window-minutes-min` | AWS::Backup::BackupPlan | StartWindowMinutes must be at least 60 minutes | ERROR | none |
413
+ | `pf-backup-vault-encryption-key-region` | AWS::Backup::BackupVault | A backup vault encryption key must live in the deploy region | ERROR | none |
414
+ | `pf-backup-vault-lock-changeable-for-days-min` | AWS::Backup::BackupVault | A compliance-mode vault lock needs a cooling-off period of at least 3 days | ERROR | none |
415
+ | `pf-backup-vault-lock-min-le-max-retention` | AWS::Backup::BackupVault | A vault lock MinRetentionDays may not exceed MaxRetentionDays | ERROR | none |
378
416
  | `pf-batch-ce-allocation-strategy-compute-type` | AWS::Batch::ComputeEnvironment | The allocation strategy must match the compute resource type | ERROR | none |
379
417
  | `pf-batch-ce-bid-percentage-range` | AWS::Batch::ComputeEnvironment | BidPercentage must be between 0 and 100 | ERROR | none |
380
418
  | `pf-batch-ce-desired-vcpus-range` | AWS::Batch::ComputeEnvironment | DesiredvCpus must sit between MinvCpus and MaxvCpus | ERROR | none |
@@ -784,6 +822,26 @@
784
822
  | `pf-cloudfront-vpc-origin-port-range` | AWS::CloudFront::VpcOrigin | VPC origin ports must be 80, 443 or 1024-65535 | ERROR | none |
785
823
  | `pf-cloudfront-vpc-origin-ssl-protocols-required` | AWS::CloudFront::VpcOrigin | An HTTPS-capable VPC origin must list OriginSSLProtocols | ERROR | none |
786
824
  | `pf-cloudfront-wafv2-webacl-scope` | AWS::CloudFront::Distribution | A WAFv2 web ACL attached to CloudFront must be globally scoped | ERROR | none |
825
+ | `pf-cloudtrail-eds-context-key-requires-large-event-size` | AWS::CloudTrail::EventDataStore | Context key selectors require MaxEventSize Large | ERROR | none |
826
+ | `pf-cloudtrail-trail-aes-conditions-max` | AWS::CloudTrail::Trail | A trail holds at most 500 advanced event selector condition values | ERROR | none |
827
+ | `pf-cloudtrail-trail-aes-data-requires-resources-type` | AWS::CloudTrail::Trail | A Data event selector needs a resources.type field | ERROR | none |
828
+ | `pf-cloudtrail-trail-aes-error-code-value` | AWS::CloudTrail::Trail | VpceAccessDenied is the only errorCode a selector can filter on | ERROR | none |
829
+ | `pf-cloudtrail-trail-aes-event-category-equals-only` | AWS::CloudTrail::Trail | eventCategory, readOnly and resources.type only accept the Equals operator | ERROR | none |
830
+ | `pf-cloudtrail-trail-aes-event-category-required` | AWS::CloudTrail::Trail | Every advanced event selector needs an eventCategory field | ERROR | none |
831
+ | `pf-cloudtrail-trail-aes-event-category-value` | AWS::CloudTrail::Trail | A trail only logs the Management, Data and NetworkActivity event categories | ERROR | none |
832
+ | `pf-cloudtrail-trail-aes-field-unknown` | AWS::CloudTrail::Trail | An advanced field selector only names CloudTrail's defined fields | WARN | none |
833
+ | `pf-cloudtrail-trail-aes-network-requires-event-source` | AWS::CloudTrail::Trail | A NetworkActivity event selector needs an eventSource field | ERROR | none |
834
+ | `pf-cloudtrail-trail-aes-resources-type-single` | AWS::CloudTrail::Trail | An advanced event selector holds at most one resources.type field | ERROR | none |
835
+ | `pf-cloudtrail-trail-cwl-loggroup-region` | AWS::CloudTrail::Trail | The CloudWatch Logs log group must be in the deployment Region | ERROR | none |
836
+ | `pf-cloudtrail-trail-cwl-pair` | AWS::CloudTrail::Trail | CloudWatch Logs delivery needs both the log group ARN and the role ARN | ERROR | none |
837
+ | `pf-cloudtrail-trail-data-resource-type` | AWS::CloudTrail::Trail | A basic event selector only logs three data resource types | ERROR | none |
838
+ | `pf-cloudtrail-trail-data-resources-max` | AWS::CloudTrail::Trail | A trail logs at most 250 data resource values across all event selectors | ERROR | none |
839
+ | `pf-cloudtrail-trail-kms-key-region` | AWS::CloudTrail::Trail | A trail's KMS key must be in the deployment Region | ERROR | none |
840
+ | `pf-cloudtrail-trail-name-adjacent-separators` | AWS::CloudTrail::Trail | A trail name cannot have adjacent periods, hyphens or underscores | ERROR | none |
841
+ | `pf-cloudtrail-trail-name-not-ip-format` | AWS::CloudTrail::Trail | A trail name must not look like an IP address | ERROR | none |
842
+ | `pf-cloudtrail-trail-s3-bucket-policy-missing` | AWS::CloudTrail::Trail | The trail's S3 bucket needs a policy that lets CloudTrail write to it | ERROR | none |
843
+ | `pf-cloudtrail-trail-s3-data-resource-value-suffix` | AWS::CloudTrail::Trail | An S3 data resource value must name an object prefix, not a bare bucket ARN | ERROR | none |
844
+ | `pf-cloudtrail-trail-selectors-exclusive` | AWS::CloudTrail::Trail | A trail cannot set both EventSelectors and AdvancedEventSelectors | ERROR | none |
787
845
  | `pf-cloudwatch-alarm-action-vendor` | AWS::CloudWatch::Alarm | Alarm actions accept only the CloudWatch action vendors | ERROR | none |
788
846
  | `pf-cloudwatch-alarm-actions-max` | AWS::CloudWatch::Alarm | Each alarm action list holds at most 5 ARNs | ERROR | none |
789
847
  | `pf-cloudwatch-alarm-anomaly-autoscaling-action` | AWS::CloudWatch::Alarm | An anomaly-detection alarm cannot carry an Auto Scaling action | ERROR | none |
@@ -1073,6 +1131,24 @@
1073
1131
  | `pf-cognito-verification-sms-placeholder` | AWS::Cognito::UserPool | The verification SMS needs the {####} code placeholder | ERROR | none |
1074
1132
  | `pf-cognito-web-authn-relying-party-format` | AWS::Cognito::UserPool | WebAuthnRelyingPartyID is a bare domain name | ERROR | none |
1075
1133
  | `pf-cognito-write-attributes-immutable` | AWS::Cognito::UserPoolClient | Verified-status attributes cannot be written by a client | ERROR | none |
1134
+ | `pf-config-aggregator-account-source-regions-exclusive` | AWS::Config::ConfigurationAggregator | An account aggregation source takes AllAwsRegions or AwsRegions, not both | ERROR | none |
1135
+ | `pf-config-aggregator-source-exactly-one` | AWS::Config::ConfigurationAggregator | An aggregator takes account sources or an organization source, not both | ERROR | none |
1136
+ | `pf-config-conformance-pack-template-body-resource-types` | AWS::Config::ConformancePack | A conformance pack template holds only Config rules and remediations | ERROR | none |
1137
+ | `pf-config-recorder-all-supported-with-resource-types` | AWS::Config::ConfigurationRecorder | RecordingGroup cannot list ResourceTypes while AllSupported is true | ERROR | none |
1138
+ | `pf-config-recorder-exclusion-requires-strategy` | AWS::Config::ConfigurationRecorder | ExclusionByResourceTypes needs the EXCLUSION_BY_RESOURCE_TYPES recording strategy | ERROR | none |
1139
+ | `pf-config-recorder-exclusion-strategy-with-resource-types` | AWS::Config::ConfigurationRecorder | The EXCLUSION_BY_RESOURCE_TYPES strategy cannot also list ResourceTypes | ERROR | none |
1140
+ | `pf-config-recorder-include-global-requires-all-supported` | AWS::Config::ConfigurationRecorder | IncludeGlobalResourceTypes needs AllSupported to be true | ERROR | none |
1141
+ | `pf-config-recorder-recording-mode-daily-resource-types` | AWS::Config::ConfigurationRecorder | Three Config-owned resource types cannot be recorded daily | ERROR | none |
1142
+ | `pf-config-recorder-strategy-all-supported-requires-all-supported` | AWS::Config::ConfigurationRecorder | The ALL_SUPPORTED_RESOURCE_TYPES strategy needs AllSupported to be true | ERROR | none |
1143
+ | `pf-config-recorder-strategy-inclusion-requires-resource-types` | AWS::Config::ConfigurationRecorder | The INCLUSION_BY_RESOURCE_TYPES strategy needs ResourceTypes | ERROR | none |
1144
+ | `pf-config-remediation-automatic-requires-attempts-and-retry` | AWS::Config::RemediationConfiguration | Automatic remediation needs MaximumAutomaticAttempts and RetryAttemptSeconds | ERROR | none |
1145
+ | `pf-config-remediation-resource-value-allowed` | AWS::Config::RemediationConfiguration | ResourceValue.Value only takes RESOURCE_ID | ERROR | none |
1146
+ | `pf-config-rule-custom-lambda-requires-source-details` | AWS::Config::ConfigRule | A custom Config rule needs Source.SourceDetails | ERROR | none |
1147
+ | `pf-config-rule-custom-policy-message-type` | AWS::Config::ConfigRule | A CUSTOM_POLICY rule takes only the two change-notification message types | ERROR | none |
1148
+ | `pf-config-rule-custom-policy-requires-details` | AWS::Config::ConfigRule | A CUSTOM_POLICY rule needs Source.CustomPolicyDetails | ERROR | none |
1149
+ | `pf-config-rule-scope-resource-id-requires-single-type` | AWS::Config::ConfigRule | Scope.ComplianceResourceId needs exactly one ComplianceResourceTypes entry | ERROR | none |
1150
+ | `pf-config-rule-scope-tag-value-requires-tag-key` | AWS::Config::ConfigRule | Scope.TagValue is only read together with Scope.TagKey | ERROR | none |
1151
+ | `pf-config-rule-source-detail-frequency-requires-scheduled` | AWS::Config::ConfigRule | A SourceDetail's MaximumExecutionFrequency only applies to ScheduledNotification | ERROR | none |
1076
1152
  | `pf-docdb-backup-maintenance-overlap` | AWS::DocDB::DBCluster | The backup window and the maintenance window must not overlap | ERROR | none |
1077
1153
  | `pf-docdb-backup-window-duration` | AWS::DocDB::DBCluster | The backup window must be at least 30 minutes | ERROR | none |
1078
1154
  | `pf-docdb-backup-window-format` | AWS::DocDB::DBCluster | PreferredBackupWindow must be hh24:mi-hh24:mi | ERROR | none |
@@ -1639,6 +1715,14 @@
1639
1715
  | `pf-firehose-snowflake-user` | AWS::KinesisFirehose::DeliveryStream | A Snowflake destination needs a user or a secret | ERROR | none |
1640
1716
  | `pf-firehose-snowflake-variant-columns` | AWS::KinesisFirehose::DeliveryStream | Variant content and metadata mapping needs both column names | ERROR | none |
1641
1717
  | `pf-firehose-splunk-hec-endpoint-https` | AWS::KinesisFirehose::DeliveryStream | A Splunk HEC endpoint must be an HTTPS URL | ERROR | none |
1718
+ | `pf-ga-endpoint-group-region-supported` | AWS::GlobalAccelerator::EndpointGroup | EndpointGroupRegion must be a real AWS Region | ERROR | none |
1719
+ | `pf-ga-endpoint-group-unique-per-listener-region` | AWS::GlobalAccelerator::EndpointGroup | A listener holds at most one endpoint group per Region | ERROR | none |
1720
+ | `pf-ga-flow-logs-require-bucket-and-prefix` | AWS::GlobalAccelerator::Accelerator | Flow logs need both FlowLogsS3Bucket and FlowLogsS3Prefix | ERROR | none |
1721
+ | `pf-ga-health-check-interval-10-or-30` | AWS::GlobalAccelerator::EndpointGroup | HealthCheckIntervalSeconds must be 10 or 30 | ERROR | none |
1722
+ | `pf-ga-listener-port-range-order` | AWS::GlobalAccelerator::Listener | A listener PortRange must have FromPort <= ToPort | ERROR | none |
1723
+ | `pf-ga-listener-port-ranges-unique-per-accelerator` | AWS::GlobalAccelerator::Listener | Listeners on one accelerator must not share port ranges | ERROR | none |
1724
+ | `pf-ga-port-override-endpoint-port-not-in-listener-range` | AWS::GlobalAccelerator::EndpointGroup | A port override must not target a listener port of the accelerator | ERROR | none |
1725
+ | `pf-ga-port-override-listener-port-in-range` | AWS::GlobalAccelerator::EndpointGroup | A port override must name a listener port the listener accepts | ERROR | none |
1642
1726
  | `pf-glue-classifier-csv-custom-datatype` | AWS::Glue::Classifier | Custom CSV datatypes come from the supported set | ERROR | pending-engine |
1643
1727
  | `pf-glue-classifier-csv-custom-datatype-flag` | AWS::Glue::Classifier | ContainsCustomDatatype and CustomDatatypeConfigured travel together | ERROR | none |
1644
1728
  | `pf-glue-classifier-csv-quote-symbol` | AWS::Glue::Classifier | The CSV quote symbol differs from the delimiter | ERROR | none |
@@ -2470,6 +2554,25 @@
2470
2554
  | `pf-secretsmanager-target-attachment` | AWS::SecretsManager::SecretTargetAttachment | SecretTargetAttachment needs a TargetType from the documented list and a secret whose value is a JSON object (SecretString JSON or GenerateSecretString with SecretStringTemplate) | ERROR | none |
2471
2555
  | `pf-servicediscovery-http-namespace-name-charset` | AWS::ServiceDiscovery::HttpNamespace | An HTTP namespace name must be printable ASCII | ERROR | none |
2472
2556
  | `pf-servicediscovery-http-namespace-name-length` | AWS::ServiceDiscovery::HttpNamespace | An HTTP namespace name is limited to 1024 characters | ERROR | cfn-schema |
2557
+ | `pf-servicediscovery-instance-a-requires-ipv4` | AWS::ServiceDiscovery::Instance | An A record service needs AWS_INSTANCE_IPV4 on every instance | ERROR | none |
2558
+ | `pf-servicediscovery-instance-aaaa-requires-ipv6` | AWS::ServiceDiscovery::Instance | An AAAA record service needs AWS_INSTANCE_IPV6 on every instance | ERROR | none |
2559
+ | `pf-servicediscovery-instance-alias-requires-weighted` | AWS::ServiceDiscovery::Instance | An ALIAS instance needs a WEIGHTED service | ERROR | none |
2560
+ | `pf-servicediscovery-instance-attribute-key-length` | AWS::ServiceDiscovery::Instance | An instance attribute key is at most 255 characters | ERROR | none |
2561
+ | `pf-servicediscovery-instance-attribute-key-pattern` | AWS::ServiceDiscovery::Instance | An instance attribute key is printable ASCII with no spaces | ERROR | none |
2562
+ | `pf-servicediscovery-instance-attribute-value-length` | AWS::ServiceDiscovery::Instance | An instance attribute value is at most 1024 characters | ERROR | none |
2563
+ | `pf-servicediscovery-instance-attributes-total-size` | AWS::ServiceDiscovery::Instance | All instance attributes together are at most 5000 characters | ERROR | none |
2564
+ | `pf-servicediscovery-instance-cname-requires-cname-attr` | AWS::ServiceDiscovery::Instance | A CNAME record service needs AWS_INSTANCE_CNAME on every instance | ERROR | none |
2565
+ | `pf-servicediscovery-instance-custom-attributes-max` | AWS::ServiceDiscovery::Instance | An instance carries at most 30 custom attributes | ERROR | none |
2566
+ | `pf-servicediscovery-instance-ec2-id-exclusive` | AWS::ServiceDiscovery::Instance | AWS_EC2_INSTANCE_ID takes no companion attribute but AWS_INIT_HEALTH_STATUS | ERROR | none |
2567
+ | `pf-servicediscovery-instance-ec2-id-http-namespace-only` | AWS::ServiceDiscovery::Instance | AWS_EC2_INSTANCE_ID only works in an HTTP namespace | ERROR | none |
2568
+ | `pf-servicediscovery-instance-healthcheck-forbids-nonroutable-ipv4` | AWS::ServiceDiscovery::Instance | A health-checked service rejects a non-routable AWS_INSTANCE_IPV4 | ERROR | none |
2569
+ | `pf-servicediscovery-instance-healthcheck-requires-port` | AWS::ServiceDiscovery::Instance | An instance of a health-checked service needs AWS_INSTANCE_PORT | ERROR | none |
2570
+ | `pf-servicediscovery-instance-id-length` | AWS::ServiceDiscovery::Instance | An instance id is at most 64 characters | ERROR | cfn-schema |
2571
+ | `pf-servicediscovery-instance-id-pattern` | AWS::ServiceDiscovery::Instance | An instance id takes a restricted character set | ERROR | none |
2572
+ | `pf-servicediscovery-instance-init-health-status-enum` | AWS::ServiceDiscovery::Instance | AWS_INIT_HEALTH_STATUS is HEALTHY or UNHEALTHY | ERROR | none |
2573
+ | `pf-servicediscovery-instance-srv-requires-ip` | AWS::ServiceDiscovery::Instance | An SRV record service needs an IP address on every instance | ERROR | none |
2574
+ | `pf-servicediscovery-instance-srv-requires-port` | AWS::ServiceDiscovery::Instance | An SRV record service needs AWS_INSTANCE_PORT on every instance | ERROR | none |
2575
+ | `pf-servicediscovery-instance-unknown-aws-attribute` | AWS::ServiceDiscovery::Instance | The AWS_ attribute prefix is reserved | ERROR | none |
2473
2576
  | `pf-servicediscovery-namespace-description-length` | AWS::ServiceDiscovery::HttpNamespace<br>AWS::ServiceDiscovery::PublicDnsNamespace<br>AWS::ServiceDiscovery::PrivateDnsNamespace | A namespace description is limited to 1024 characters | ERROR | cfn-schema |
2474
2577
  | `pf-servicediscovery-namespace-soa-ttl-range` | AWS::ServiceDiscovery::PublicDnsNamespace<br>AWS::ServiceDiscovery::PrivateDnsNamespace | The SOA record TTL of a DNS namespace may not exceed 2147483647 | ERROR | cfn-schema |
2475
2578
  | `pf-servicediscovery-private-namespace-name-charset` | AWS::ServiceDiscovery::PrivateDnsNamespace | A private DNS namespace name must be printable ASCII | ERROR | none |
@@ -2483,11 +2586,35 @@
2483
2586
  | `pf-servicediscovery-service-dnsrecord-type-combination` | AWS::ServiceDiscovery::Service | Only a few DnsRecords type combinations are valid | ERROR | none |
2484
2587
  | `pf-servicediscovery-service-dnsrecord-type-duplicate` | AWS::ServiceDiscovery::Service | DnsRecords may not repeat the same record type | ERROR | none |
2485
2588
  | `pf-servicediscovery-service-healthcheck-in-private-namespace` | AWS::ServiceDiscovery::Service | A service in a private DNS namespace cannot carry a health check | ERROR | none |
2589
+ | `pf-servicediscovery-service-healthcheck-requires-failure-threshold` | AWS::ServiceDiscovery::Service | A HealthCheckConfig without FailureThreshold breaks the service handler | ERROR | none |
2486
2590
  | `pf-servicediscovery-service-http-type-with-dnsconfig` | AWS::ServiceDiscovery::Service | A service of Type HTTP cannot declare DnsConfig | ERROR | none |
2487
2591
  | `pf-servicediscovery-service-name-case-collision` | AWS::ServiceDiscovery::Service | Two services in one DNS namespace may not differ only by case | ERROR | none |
2488
2592
  | `pf-servicediscovery-service-name-duplicate-in-namespace` | AWS::ServiceDiscovery::Service | Two services in one namespace may not share a name | ERROR | none |
2489
2593
  | `pf-servicediscovery-service-namespace-required` | AWS::ServiceDiscovery::Service | A service must name the namespace it belongs to | ERROR | none |
2490
2594
  | `pf-servicediscovery-service-tcp-healthcheck-resourcepath` | AWS::ServiceDiscovery::Service | A TCP health check may not carry a resource path | ERROR | none |
2595
+ | `pf-ses-bounce-action-smtp-reply-code` | AWS::SES::ReceiptRule | A bounce action's SmtpReplyCode must be a three-digit 4xx or 5xx reply code | ERROR | none |
2596
+ | `pf-ses-bounce-action-status-code` | AWS::SES::ReceiptRule | A bounce action's StatusCode must be 4.x.y or 5.x.y with a single-digit subject and detail | ERROR | none |
2597
+ | `pf-ses-byodkim-requires-domain-identity` | AWS::SES::EmailIdentity | BYODKIM works only on a domain identity, not an email address | ERROR | none |
2598
+ | `pf-ses-byodkim-requires-selector-and-key` | AWS::SES::EmailIdentity | BYODKIM needs both DomainSigningSelector and DomainSigningPrivateKey | ERROR | none |
2599
+ | `pf-ses-contact-list-topic-subscription-status-enum` | AWS::SES::ContactList | A contact list topic's DefaultSubscriptionStatus is OPT_IN or OPT_OUT | ERROR | none |
2600
+ | `pf-ses-dkim-next-signing-key-length-easy-dkim-only` | AWS::SES::EmailIdentity | NextSigningKeyLength cannot be combined with the BYODKIM selector and key | ERROR | none |
2601
+ | `pf-ses-dkim-private-key-base64` | AWS::SES::EmailIdentity | A BYODKIM private key must be bare base64, not a PEM block | ERROR | none |
2602
+ | `pf-ses-dkim-signing-selector-charset` | AWS::SES::EmailIdentity | A BYODKIM selector must be a single DNS label without a leading or trailing hyphen | ERROR | none |
2603
+ | `pf-ses-event-destination-matching-event-types-enum` | AWS::SES::ConfigurationSetEventDestination | MatchingEventTypes takes only the ten SES event types | ERROR | none |
2604
+ | `pf-ses-event-destination-matching-event-types-nonempty` | AWS::SES::ConfigurationSetEventDestination | An event destination must match at least one event type | ERROR | none |
2605
+ | `pf-ses-event-destination-requires-destination` | AWS::SES::ConfigurationSetEventDestination | An event destination must carry one destination block | ERROR | none |
2606
+ | `pf-ses-eventbridge-destination-bus-region` | AWS::SES::ConfigurationSetEventDestination | An EventBridge event destination must be in the deployment Region | ERROR | none |
2607
+ | `pf-ses-eventbridge-destination-default-bus-only` | AWS::SES::ConfigurationSetEventDestination | An EventBridge event destination must point at the default event bus | ERROR | none |
2608
+ | `pf-ses-firehose-destination-arn-format` | AWS::SES::ConfigurationSetEventDestination | A Firehose event destination needs a deliverystream ARN | ERROR | none |
2609
+ | `pf-ses-firehose-destination-stream-region` | AWS::SES::ConfigurationSetEventDestination | A Firehose event destination must stream to the deployment Region | ERROR | none |
2610
+ | `pf-ses-mail-from-domain-subdomain-of-identity` | AWS::SES::EmailIdentity | A custom MAIL FROM domain must be a subdomain of the identity it belongs to | ERROR | none |
2611
+ | `pf-ses-receipt-action-exactly-one` | AWS::SES::ReceiptRule | Each entry of a receipt rule's Actions carries exactly one action | ERROR | none |
2612
+ | `pf-ses-receipt-rule-actions-max-10` | AWS::SES::ReceiptRule | A receipt rule holds at most 10 actions | ERROR | none |
2613
+ | `pf-ses-receipt-rule-after-exists` | AWS::SES::ReceiptRule | A receipt rule's After must name a rule that the template actually creates | ERROR | none |
2614
+ | `pf-ses-receipt-rule-name-charset` | AWS::SES::ReceiptRule | A receipt rule's name takes only letters, digits, underscores, dashes and periods | ERROR | none |
2615
+ | `pf-ses-receipt-rule-set-name-charset` | AWS::SES::ReceiptRuleSet | A receipt rule set's name must start and end with a letter or digit | ERROR | none |
2616
+ | `pf-ses-sns-destination-topic-region` | AWS::SES::ConfigurationSetEventDestination | An SNS event destination must publish to a topic in the deployment Region | ERROR | none |
2617
+ | `pf-ses-template-html-or-text-part` | AWS::SES::Template | An email template needs an HtmlPart or a TextPart | ERROR | none |
2491
2618
  | `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 |
2492
2619
  | `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 |
2493
2620
  | `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 |
package/lib/index.js CHANGED
@@ -17,7 +17,7 @@ const rules_generated_1 = require("./rules.generated");
17
17
  * Preflight.apply(app);
18
18
  */
19
19
  class Preflight {
20
- static [JSII_RTTI_SYMBOL_1] = { fqn: "cdk-preflight.Preflight", version: "0.0.149" };
20
+ static [JSII_RTTI_SYMBOL_1] = { fqn: "cdk-preflight.Preflight", version: "0.0.150" };
21
21
  /**
22
22
  * Register the cdk-preflight rules on an App or Stage.
23
23
  */