| 1 | Least-privilege grants (grantRead over grantReadWrite/grantFullAccess) | High | grantReadWrite|grantFullAccess|grantWrite |
| 2 | No broad IAM actions (*, iam:*, s3:*) in PolicyStatement | High | actions:.*\*|'\*'|"\*" near PolicyStatement |
| 3 | Recognize the IAM-role approach (CDK-managed / pre-created / customized) | — | Role.fromRoleArn|new Role |
| 3b/4 | No hardcoded physical names | High | bucketName:|tableName:|functionName:|queueName:|roleName: |
| 5 | Decide at synthesis time, not deploy time (no CfnParameter/CfnCondition/Fn.if) | Medium | CfnParameter|CfnCondition|Fn\.conditionIf|Fn\.if |
| 6 | Explicit removal policy + log retention on stateful resources | High | new Bucket|new Table|new Database; check for removalPolicy/RETAIN/retention |
| 7 | Don't change/churn logical IDs of stateful resources (don't nest in volatile constructs) | High | stateful resources inside deeply nested/renamed custom constructs |
| 8 | Three key directories (bin/, lib/, test/); organized lib/ as it grows | Low | project layout |
| 9 | cdk.json committed w/ feature flags intact; cdk.context.json NOT committed | Medium | cdk.json context block; .gitignore |
| 10 | Single purpose per package (App vs Construct Library); one app per repo | Low | stacks + entry point mixed with publishable library |
| 11 | Project-local tooling via npx (avoid global CDK CLI version drift) | Low | package.json devDeps aws-cdk; scripts using global cdk |
| 12 | Multi-account separation; start simple | Low | env/account config |
| 13 | Model with constructs, deploy with stacks (logical units = Construct, not Stack) | Medium | many new Cfn/L2 resources directly in a Stack subclass |
| 14 | Configure via typed props, not process.env inside constructs/stacks (only bin/) | Medium | process.env (flag outside bin/); constructors lacking a Props interface |
| 15 | Custom construct anatomy: props + ctor + public attrs + JSDoc + secure defaults | Medium | custom extends Construct missing props/JSDoc/public readonly |
| 16 | Prefer L2; escape hatch (node.defaultChild) / overrides; wrap bare L1 (CfnXXXX) | Low | direct new Cfn usage where an L2 exists |
| 17 | Specific imports, not wildcard (import * as cdk) | Low | import \* as |
| 18 | Co-locate infrastructure + runtime code (Code.fromAsset local path) | Low | Code.fromAsset |
| 19 | Evaluate third-party constructs before adopting | Low | non-aws-cdk-lib/@cdklabs construct deps in package.json |
| 20 | Fine-grained assertion tests exist (aws-cdk-lib/assertions, Template.fromStack) | Medium | presence of test/, Template.fromStack |
| 21 | Don't rely on snapshot-only testing (toMatchSnapshot) | Low | toMatchSnapshot without hasResourceProperties |
| 22 | Testing hygiene incl. asserting stateful logical IDs stay stable; no synth-time lookups | Medium | tests referencing logical IDs; beforeEach |
| 23 | CDK Nag wired in (AwsSolutionsChecks via Aspects.of) | Medium | cdk-nag|AwsSolutionsChecks |
| 24 | CDK Nag suppressions have a reason, scoped (resource > stack) | Medium | NagSuppressions with/without reason: |
| 25 | Mutation Aspects cautious; validation/tagging Aspects safe | Medium | implements IAspect; visit( that mutates props |
| 26 | Mutation Aspects justified only for org-wide policy, not per-resource config | Medium | IAspect mutating individual resource config |
| 27 | Compliance is layered (constructs + Nag + boundaries/SCPs), not wrapper-construct alone | Medium | wrapper constructs as sole enforcement |