@intentius/chant-lexicon-terraform 0.124.31 → 0.124.32

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.
Files changed (141) hide show
  1. package/dist/__fixtures__/terragrunt/affected-cases.js +111 -0
  2. package/dist/__fixtures__/terragrunt/record.js +57 -0
  3. package/dist/behaviour/index.js +6 -0
  4. package/dist/behaviour/kinds.js +194 -0
  5. package/dist/behaviour/predict.js +297 -0
  6. package/dist/behaviour/reference-catalog.js +178 -0
  7. package/dist/behaviour/request.js +482 -0
  8. package/dist/change-set.js +234 -0
  9. package/dist/changed-roots.js +97 -0
  10. package/dist/codegen/docs-cli.js +3 -0
  11. package/dist/codegen/docs.js +44 -0
  12. package/dist/codegen/generate-cli.js +9 -0
  13. package/dist/codegen/generate.js +59 -0
  14. package/dist/codegen/package.js +43 -0
  15. package/dist/commands.js +106 -0
  16. package/dist/components/capability-plugin.d.ts +38 -2
  17. package/dist/components/capability-plugin.d.ts.map +1 -1
  18. package/dist/components/capability-plugin.js +260 -0
  19. package/dist/composites/terraform-adopt-op.js +152 -0
  20. package/dist/composites/terraform-apply-op.js +183 -0
  21. package/dist/composites/terraform-pin-rollout-op.js +45 -0
  22. package/dist/composites/terraform-watch-op.js +219 -0
  23. package/dist/config.js +118 -0
  24. package/dist/describe-resources.js +984 -0
  25. package/dist/generated/index.js +1 -0
  26. package/dist/hcl/descend.js +208 -0
  27. package/dist/hcl/edges.js +287 -0
  28. package/dist/hcl/jsonencode.js +442 -0
  29. package/dist/hcl/marker.js +212 -0
  30. package/dist/hcl/parse.js +372 -0
  31. package/dist/hcl/references.js +156 -0
  32. package/dist/hcl/roots.js +98 -0
  33. package/dist/hcl/suppressions.js +189 -0
  34. package/dist/hcl/value.js +72 -0
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +46 -0
  38. package/dist/integrity.json +2 -2
  39. package/dist/lint/audit-catalog.js +501 -0
  40. package/dist/lint/audit-lineage.js +442 -0
  41. package/dist/lint/post-synth/blocks.js +87 -0
  42. package/dist/lint/post-synth/fixtures/load.js +94 -0
  43. package/dist/lint/post-synth/flat-attributes.js +135 -0
  44. package/dist/lint/post-synth/index.js +90 -0
  45. package/dist/lint/post-synth/module-source.js +153 -0
  46. package/dist/lint/post-synth/scope.js +104 -0
  47. package/dist/lint/post-synth/terragrunt.js +63 -0
  48. package/dist/lint/post-synth/tf001.js +138 -0
  49. package/dist/lint/post-synth/tf002.js +143 -0
  50. package/dist/lint/post-synth/tf003.js +60 -0
  51. package/dist/lint/post-synth/tf004.js +54 -0
  52. package/dist/lint/post-synth/tf005.js +68 -0
  53. package/dist/lint/post-synth/tf006.js +43 -0
  54. package/dist/lint/post-synth/tf007.js +66 -0
  55. package/dist/lint/post-synth/tf008.js +64 -0
  56. package/dist/lint/post-synth/tf009.js +49 -0
  57. package/dist/lint/post-synth/tf010.js +40 -0
  58. package/dist/lint/post-synth/tf011.js +38 -0
  59. package/dist/lint/post-synth/tf012.js +36 -0
  60. package/dist/lint/post-synth/tf013.js +53 -0
  61. package/dist/lint/post-synth/tf014.js +61 -0
  62. package/dist/lint/post-synth/tf015.js +62 -0
  63. package/dist/lint/post-synth/tf016.js +66 -0
  64. package/dist/lint/post-synth/tf017.js +39 -0
  65. package/dist/lint/post-synth/tf018.js +63 -0
  66. package/dist/lint/post-synth/tf019.js +60 -0
  67. package/dist/lint/post-synth/tf020.js +103 -0
  68. package/dist/lint/post-synth/tf021.js +117 -0
  69. package/dist/lint/post-synth/tf022.js +51 -0
  70. package/dist/lint/post-synth/tf024.js +62 -0
  71. package/dist/lint/post-synth/tf025.js +83 -0
  72. package/dist/lint/post-synth/tf026.js +74 -0
  73. package/dist/lint/post-synth/tf027.js +66 -0
  74. package/dist/lint/post-synth/tf028.js +83 -0
  75. package/dist/lint/post-synth/tf029.js +73 -0
  76. package/dist/lint/post-synth/tf030.js +253 -0
  77. package/dist/lint/post-synth/tf031.js +332 -0
  78. package/dist/lint/post-synth/tf032.js +201 -0
  79. package/dist/lint/post-synth/tf033.js +126 -0
  80. package/dist/lint/post-synth/tf034.js +45 -0
  81. package/dist/lint/post-synth/tf035.js +63 -0
  82. package/dist/lint/post-synth/tf036.js +97 -0
  83. package/dist/lint/post-synth/tf037.js +80 -0
  84. package/dist/lint/post-synth/tf038.js +63 -0
  85. package/dist/lint/post-synth/tf039.js +55 -0
  86. package/dist/lint/post-synth/tf040.js +67 -0
  87. package/dist/lint/post-synth/tf041.js +53 -0
  88. package/dist/lint/post-synth/tf042.js +38 -0
  89. package/dist/lint/post-synth/tf043.js +45 -0
  90. package/dist/lint/post-synth/tf044.js +45 -0
  91. package/dist/lint/post-synth/tf045.js +44 -0
  92. package/dist/lint/rules/fixtures/load.js +50 -0
  93. package/dist/lint/rules/index.js +4 -0
  94. package/dist/lint/rules/plan-before-apply.js +136 -0
  95. package/dist/lint/secret-shape.js +149 -0
  96. package/dist/live-ownership.js +57 -0
  97. package/dist/lsp/completions.js +95 -0
  98. package/dist/lsp/context.js +358 -0
  99. package/dist/lsp/hover.js +45 -0
  100. package/dist/lsp/option-keys.js +93 -0
  101. package/dist/manifest.json +1 -1
  102. package/dist/op/activities/index.js +30 -0
  103. package/dist/op/activities/live-detect.js +143 -0
  104. package/dist/op/activities/pin-rollout.js +45 -0
  105. package/dist/op/activities/terraform.js +1270 -0
  106. package/dist/op/activity-contracts.js +162 -0
  107. package/dist/op/adoption.js +272 -0
  108. package/dist/op/builders.js +125 -0
  109. package/dist/op/resolve-root-mode.js +77 -0
  110. package/dist/package-cli.js +16 -0
  111. package/dist/pin/edit-ts.js +76 -0
  112. package/dist/pin/edit.js +118 -0
  113. package/dist/pin/forge.js +65 -0
  114. package/dist/pin/index.js +16 -0
  115. package/dist/pin/rollout.d.ts.map +1 -1
  116. package/dist/pin/rollout.js +382 -0
  117. package/dist/pin/scan.js +287 -0
  118. package/dist/pin/source.js +171 -0
  119. package/dist/pin/waves.js +109 -0
  120. package/dist/plan-digest.js +67 -0
  121. package/dist/plugin.js +323 -0
  122. package/dist/serializer.js +23 -0
  123. package/dist/state-ownership.js +29 -0
  124. package/dist/terragrunt/affected.d.ts.map +1 -1
  125. package/dist/terragrunt/affected.js +290 -0
  126. package/dist/terragrunt/index.js +10 -0
  127. package/dist/terragrunt/mocks.js +270 -0
  128. package/dist/terragrunt/run.js +264 -0
  129. package/dist/terragrunt/units.js +195 -0
  130. package/dist/terragrunt/wave.js +125 -0
  131. package/dist/validate-cli.js +4 -0
  132. package/dist/validate.js +31 -0
  133. package/package.json +10 -10
  134. package/src/codegen/package.ts +2 -2
  135. package/src/components/capability-plugin.test.ts +97 -2
  136. package/src/components/capability-plugin.ts +117 -12
  137. package/src/components/pr-loop.tofu.test.ts +51 -11
  138. package/src/index.ts +1 -0
  139. package/src/pin/rollout.ts +2 -1
  140. package/src/terragrunt/affected.test.ts +11 -0
  141. package/src/terragrunt/affected.ts +2 -1
@@ -0,0 +1,442 @@
1
+ const TFSEC_V061 = "https://github.com/aquasecurity/tfsec/blob/v0.61.3/docs/checks/general/secrets";
2
+ /** tflint's official language ruleset. */
3
+ const TFLINT_RULES = "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules";
4
+ /** The community ruleset with the richest set of language rules outside the official one. */
5
+ const REDEPLOY_RULES = "https://github.com/RedeployAB/tflint-ruleset-redeploy/blob/main/docs/rules";
6
+ export const terraformAuditLineage = {
7
+ TF027: [
8
+ {
9
+ tool: "choudoufu",
10
+ rule: "RulePolicyScope",
11
+ url: "https://github.com/INTENTIUS/choudoufu/blob/main/internal/live/lint/policy.go",
12
+ relation: "extends",
13
+ },
14
+ ],
15
+ TF024: [
16
+ {
17
+ tool: "choudoufu",
18
+ rule: "RuleStateBackend",
19
+ url: "https://github.com/INTENTIUS/choudoufu/blob/main/internal/live/lint/issue.go",
20
+ relation: "equivalent",
21
+ },
22
+ ],
23
+ TF002: [
24
+ {
25
+ tool: "tflint-ruleset-terraform",
26
+ rule: "terraform_required_providers",
27
+ url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_required_providers.md",
28
+ relation: "equivalent",
29
+ },
30
+ {
31
+ tool: "terraform-sentinel-policies",
32
+ rule: "require-all-providers-have-version-constraint",
33
+ url: "https://github.com/hashicorp/terraform-sentinel-policies/tree/main/cloud-agnostic",
34
+ relation: "equivalent",
35
+ },
36
+ {
37
+ tool: "aws-terraform-prescriptive-guidance",
38
+ rule: "Add automated version checks",
39
+ url: "https://docs.aws.amazon.com/prescriptive-guidance/latest/terraform-aws-provider-best-practices/version.html#add-automated-version-checks",
40
+ relation: "equivalent",
41
+ },
42
+ ],
43
+ // Sentinel's restrict-terraform-versions checks that required_version is
44
+ // within an allowed range, not that it is present at all: a narrower
45
+ // question than TF003's, so "overlaps" rather than "equivalent" (#2109).
46
+ TF003: [
47
+ {
48
+ tool: "tflint-ruleset-terraform",
49
+ rule: "terraform_required_version",
50
+ url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_required_version.md",
51
+ relation: "equivalent",
52
+ },
53
+ {
54
+ tool: "terraform-sentinel-policies",
55
+ rule: "restrict-terraform-versions",
56
+ url: "https://github.com/hashicorp/terraform-sentinel-policies/tree/main/cloud-agnostic",
57
+ relation: "overlaps",
58
+ },
59
+ {
60
+ tool: "hashicorp-style-guide",
61
+ rule: "Version pinning",
62
+ url: "https://developer.hashicorp.com/terraform/language/style#version-pinning",
63
+ relation: "overlaps",
64
+ },
65
+ ],
66
+ TF004: [
67
+ {
68
+ tool: "tflint-ruleset-terraform",
69
+ rule: "terraform_module_version",
70
+ url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_module_version.md",
71
+ relation: "equivalent",
72
+ },
73
+ {
74
+ tool: "checkov",
75
+ rule: "CKV_TF_2",
76
+ url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/module/generic/RevisionVersionTag.py",
77
+ relation: "equivalent",
78
+ },
79
+ ],
80
+ // checkov's CKV_TF_1 accepts any `?ref=`/`&ref=` matching `[?&](ref=).*(\d\.\d).*`,
81
+ // which a branch literally named `v1.2-dev` passes; TF005 requires a full
82
+ // semver tag or a 40-hex SHA, so "extends" (a strict superset), not
83
+ // "equivalent". KICS's query only matches the `git::` prefix, not the
84
+ // github.com/bitbucket.org shorthands or scp-style sources TF005 also
85
+ // covers, so "overlaps" (#2109).
86
+ TF005: [
87
+ {
88
+ tool: "tflint-ruleset-terraform",
89
+ rule: "terraform_module_pinned_source",
90
+ url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_module_pinned_source.md",
91
+ relation: "equivalent",
92
+ },
93
+ {
94
+ tool: "checkov",
95
+ rule: "CKV_TF_1",
96
+ url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/module/generic/RevisionHash.py",
97
+ relation: "extends",
98
+ },
99
+ {
100
+ tool: "kics",
101
+ rule: "3a81fc06-566f-492a-91dd-7448e409e2cd",
102
+ url: "https://docs.kics.io/latest/queries/terraform-queries/3a81fc06-566f-492a-91dd-7448e409e2cd/",
103
+ relation: "overlaps",
104
+ },
105
+ ],
106
+ TF006: [
107
+ {
108
+ tool: "tflint-ruleset-avm",
109
+ rule: "avm_terraform_sensitive_variable_default_disallowed",
110
+ url: "https://github.com/Azure/tflint-ruleset-avm/blob/main/docs/basic/avm_terraform_sensitive_variable_default_disallowed.md",
111
+ relation: "equivalent",
112
+ },
113
+ ],
114
+ TF007: [
115
+ { tool: "tfsec", rule: "general-secrets-sensitive-in-variable", url: `${TFSEC_V061}/sensitive-in-variable.md`, relation: "equivalent" },
116
+ { tool: "tfsec", rule: "general-secrets-sensitive-in-local", url: `${TFSEC_V061}/sensitive-in-local.md`, relation: "equivalent" },
117
+ ],
118
+ // #2107's rule table credits TF008 with Snyk `SNYK-CC-TF-74` ("Credentials
119
+ // are configured via provider attributes") alongside these three. That
120
+ // credit is deliberately absent here, and the absence is the epic's own
121
+ // decision rather than an oversight: Snyk IaC is a product, not a rule
122
+ // source, which is why it sits in this file's "deliberately excluded" list
123
+ // above and has no `PRIOR_ART` entry to cite. The rules have no open
124
+ // repository behind them, and the public index (security.snyk.io/rules/
125
+ // cloud) does not even enumerate the legacy `SNYK-CC-TF-*` ids, so a
126
+ // credit here could not be swept the way every other one in this file is
127
+ // (scripts/prior-art-sweep.ts). The condition loses nothing by it:
128
+ // `SNYK-CC-TF-74`, checkov's `CKV_AWS_41` and semgrep's
129
+ // `aws-provider-static-credentials` are the same check three times over,
130
+ // and the other two are credited below (#2220).
131
+ TF008: [
132
+ { tool: "checkov", rule: "CKV_AWS_41", url: "https://www.checkov.io/5.Policy%20Index/terraform.html", relation: "overlaps" },
133
+ { tool: "kics", rule: "d7b9d850-3e06-4a75-852f-c46c2e92240b", url: "https://docs.kics.io/latest/queries/terraform-queries/aws/d7b9d850-3e06-4a75-852f-c46c2e92240b/", relation: "overlaps" },
134
+ {
135
+ tool: "semgrep",
136
+ rule: "terraform.aws.security.aws-provider-static-credentials",
137
+ url: "https://github.com/semgrep/semgrep-rules/blob/develop/terraform/aws/security/aws-provider-static-credentials.yaml",
138
+ relation: "overlaps",
139
+ },
140
+ ],
141
+ TF009: [
142
+ {
143
+ tool: "hashicorp-style-guide",
144
+ rule: "Variables: for sensitive variables, such as passwords and private keys, set the sensitive parameter to true",
145
+ url: "https://developer.hashicorp.com/terraform/language/style#variables",
146
+ relation: "equivalent",
147
+ },
148
+ ],
149
+ TF010: [
150
+ { tool: "tflint-ruleset-terraform", rule: "terraform_typed_variables", url: `${TFLINT_RULES}/terraform_typed_variables.md`, relation: "equivalent" },
151
+ { tool: "kics", rule: "fc5109bf-01fd-49fb-8bde-4492b543c34a", url: "https://docs.kics.io/latest/queries/terraform-queries/fc5109bf-01fd-49fb-8bde-4492b543c34a/", relation: "equivalent" },
152
+ ],
153
+ TF011: [
154
+ { tool: "tflint-ruleset-terraform", rule: "terraform_documented_variables", url: `${TFLINT_RULES}/terraform_documented_variables.md`, relation: "equivalent" },
155
+ { tool: "kics", rule: "2a153952-2544-4687-bcc9-cc8fea814a9b", url: "https://docs.kics.io/latest/queries/terraform-queries/2a153952-2544-4687-bcc9-cc8fea814a9b/", relation: "equivalent" },
156
+ {
157
+ tool: "terraform-sentinel-policies",
158
+ rule: "validate-variables-have-descriptions",
159
+ url: "https://github.com/hashicorp/terraform-sentinel-policies/blob/main/cloud-agnostic/validate-variables-have-descriptions.sentinel",
160
+ relation: "equivalent",
161
+ },
162
+ ],
163
+ TF012: [
164
+ { tool: "tflint-ruleset-terraform", rule: "terraform_documented_outputs", url: `${TFLINT_RULES}/terraform_documented_outputs.md`, relation: "equivalent" },
165
+ { tool: "kics", rule: "59312e8a-a64e-41e7-a252-618533dd1ea8", url: "https://docs.kics.io/latest/queries/terraform-queries/59312e8a-a64e-41e7-a252-618533dd1ea8/", relation: "equivalent" },
166
+ ],
167
+ TF013: [
168
+ { tool: "tflint-ruleset-redeploy", rule: "terraform_ignore_changes_all", url: `${REDEPLOY_RULES}/terraform_ignore_changes_all.md`, relation: "equivalent" },
169
+ ],
170
+ // The two vendor documents below (and the one on TF015, and the HashiCorp
171
+ // style guide on TF021) are `Lineage` entries, not `RuleMeta.authority`
172
+ // ones, though #2107's table calls them "authority". `authority` is not a
173
+ // free-standing "this document says so" field: an authority citation forces
174
+ // `category: "security"` (`packages/core/src/audit/catalog.ts`), and
175
+ // `catalog.test.ts` binds the two together in both directions, "an
176
+ // authority citation always means security" and "authority citations only
177
+ // attach to merge-worthy entries". A provider block in a child module is a
178
+ // structural rule, not a security finding, so citing these guides as
179
+ // authority would have relabelled three rules as security to get a citation
180
+ // rendered. Lineage carries the same URLs and never touches tier or
181
+ // category, so that is where they live (#2112, recorded here by #2220).
182
+ TF014: [
183
+ {
184
+ tool: "tflint-ruleset-avm",
185
+ rule: "avm_terraform_provider_block_disallowed",
186
+ url: "https://github.com/Azure/tflint-ruleset-avm/blob/main/docs/basic/avm_terraform_provider_block_disallowed.md",
187
+ relation: "equivalent",
188
+ },
189
+ {
190
+ tool: "gcp-terraform-best-practices",
191
+ rule: "Build reusable modules: modules must not configure providers",
192
+ url: "https://docs.cloud.google.com/docs/terraform/best-practices/general-style-structure#reusable-modules",
193
+ relation: "equivalent",
194
+ },
195
+ {
196
+ tool: "aws-terraform-prescriptive-guidance",
197
+ rule: "Structure: declare provider configurations in the root module",
198
+ url: "https://docs.aws.amazon.com/prescriptive-guidance/latest/terraform-aws-provider-best-practices/structure.html",
199
+ relation: "equivalent",
200
+ },
201
+ ],
202
+ // TF015 has no scanner to credit: no ruleset in the survey checks for a
203
+ // backend or cloud block inside a child module.
204
+ // tflint-ruleset-terraform's index has nothing about backends at all,
205
+ // Redeploy's has nothing either, and AVM's module rules stop at the
206
+ // provider block TF014 credits. What states the rule is Google Cloud's
207
+ // reusable-modules guide, so that document is the single credit here, and
208
+ // the page section says plainly that the credit is a document rather than
209
+ // a tool (#2112). It is lineage rather than `authority` for the reason on
210
+ // TF014 above: an authority citation would force `category: "security"` on
211
+ // a rule about where state configuration belongs.
212
+ TF015: [
213
+ {
214
+ tool: "gcp-terraform-best-practices",
215
+ rule: "Build reusable modules: state configuration belongs to the root module",
216
+ url: "https://docs.cloud.google.com/docs/terraform/best-practices/general-style-structure#reusable-modules",
217
+ relation: "equivalent",
218
+ },
219
+ ],
220
+ TF016: [
221
+ // `overlaps`, not `equivalent`: tflint reports the same `x = "${y}"` form
222
+ // and also the object-key case (`"${var.k}" = v`), which this rule leaves
223
+ // alone. See tf016.ts.
224
+ { tool: "tflint-ruleset-terraform", rule: "terraform_deprecated_interpolation", url: `${TFLINT_RULES}/terraform_deprecated_interpolation.md`, relation: "overlaps" },
225
+ ],
226
+ TF017: [
227
+ { tool: "tflint-ruleset-redeploy", rule: "terraform_module_depends_on", url: `${REDEPLOY_RULES}/terraform_module_depends_on.md`, relation: "equivalent" },
228
+ ],
229
+ TF018: [
230
+ { tool: "tflint-ruleset-redeploy", rule: "terraform_output_resource", url: `${REDEPLOY_RULES}/terraform_output_resource.md`, relation: "equivalent" },
231
+ ],
232
+ TF019: [
233
+ { tool: "tflint-ruleset-redeploy", rule: "terraform_redundant_default", url: `${REDEPLOY_RULES}/terraform_redundant_default.md`, relation: "equivalent" },
234
+ ],
235
+ TF020: [
236
+ // `overlaps`: tflint evaluates expressions and reports the same four
237
+ // kinds of declaration; chant's index is a string scan over hcl2json's
238
+ // preserved expression strings, so it is generous where tflint is exact
239
+ // (see hcl/references.ts and tf020.ts).
240
+ {
241
+ tool: "tflint-ruleset-terraform",
242
+ rule: "terraform_unused_declarations",
243
+ url: `${TFLINT_RULES}/terraform_unused_declarations.md`,
244
+ relation: "overlaps",
245
+ },
246
+ ],
247
+ TF021: [
248
+ // Redeploy reports every `count` over a collection; TF021 reports only
249
+ // the ones that build an identity out of `count.index`, which is a
250
+ // strict subset, hence `overlaps` on both credits rather than
251
+ // `equivalent`. choudoufu's own rule is the survey's deepest reading of
252
+ // the identity-bearing question (#2112). The HashiCorp style guide entry
253
+ // is lineage rather than `authority` for the reason recorded on TF014: an
254
+ // authority citation forces `category: "security"`, and `count` versus
255
+ // `for_each` is not a security finding.
256
+ {
257
+ tool: "tflint-ruleset-redeploy",
258
+ rule: "terraform_prefer_for_each",
259
+ url: `${REDEPLOY_RULES}/terraform_prefer_for_each.md`,
260
+ relation: "overlaps",
261
+ },
262
+ {
263
+ tool: "choudoufu",
264
+ rule: "RuleCountIndex",
265
+ url: "https://github.com/INTENTIUS/choudoufu/blob/main/internal/live/lint/count_index.go",
266
+ relation: "overlaps",
267
+ },
268
+ {
269
+ tool: "hashicorp-style-guide",
270
+ rule: "Resources: use for_each for a dynamic resource count",
271
+ url: "https://developer.hashicorp.com/terraform/language/style#dynamic-resource-count",
272
+ relation: "overlaps",
273
+ },
274
+ ],
275
+ TF022: [
276
+ // tfsec consolidated its three `general/secrets` checks into
277
+ // `no-plaintext-exposure` in v1, which is the one still in the repository's
278
+ // `master` docs; `overlaps` because that consolidation also covers the
279
+ // variable and locals cases TF007 reports separately.
280
+ {
281
+ tool: "tfsec",
282
+ rule: "general-secrets-no-plaintext-exposure",
283
+ url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/general/secrets/no-plaintext-exposure/index.md",
284
+ relation: "overlaps",
285
+ },
286
+ { tool: "kics", rule: "a88baa34-e2ad-44ea-ad6f-8cac87bc7c71", url: "https://docs.kics.io/latest/secrets/", relation: "overlaps" },
287
+ ],
288
+ TF030: [
289
+ // Every credit overlaps; none is equivalent. TF030 is WAW019's port list
290
+ // (22, 3389, 3306, 5432) on 0.0.0.0/0 or ::/0, read across the three
291
+ // Terraform shapes. Checkov checks one port per id and, unlike TF030,
292
+ // reads tcp 0-0 as every port and passes any rule with `self = true`.
293
+ // tfsec flags a /0 on any port; trivy-checks only SSH and RDP; KICS a
294
+ // longer port list; the Sentinel policies only `aws_security_group_rule`
295
+ // with an exact `to_port`. Each upstream id was read from its source on
296
+ // 2026-10-02 (checkov `AbsSecurityGroupUnrestrictedIngress.py`, trivy-checks
297
+ // `no_public_ingress_sgr.rego` metadata, KICS `metadata.json`, tfsec
298
+ // `rules.md`).
299
+ { tool: "checkov", rule: "CKV_AWS_24", url: "https://www.checkov.io/5.Policy%20Index/terraform.html", relation: "overlaps" },
300
+ { tool: "checkov", rule: "CKV_AWS_25", url: "https://www.checkov.io/5.Policy%20Index/terraform.html", relation: "overlaps" },
301
+ {
302
+ tool: "tfsec",
303
+ rule: "aws-vpc-no-public-ingress-sgr",
304
+ url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/aws/vpc/no-public-ingress-sgr/index.md",
305
+ relation: "overlaps",
306
+ },
307
+ {
308
+ tool: "trivy-checks",
309
+ rule: "aws-ec2-no-public-ingress-sgr",
310
+ url: "https://github.com/aquasecurity/trivy-checks/blob/main/checks/cloud/aws/ec2/no_public_ingress_sgr.rego",
311
+ relation: "overlaps",
312
+ },
313
+ {
314
+ tool: "kics",
315
+ rule: "381c3f2a-ef6f-4eff-99f7-b169cda3422c",
316
+ url: "https://docs.kics.io/latest/queries/terraform-queries/aws/381c3f2a-ef6f-4eff-99f7-b169cda3422c/",
317
+ relation: "overlaps",
318
+ },
319
+ {
320
+ tool: "terraform-sentinel-policies",
321
+ rule: "restrict-ingress-sg-rule-ssh",
322
+ url: "https://github.com/hashicorp/terraform-sentinel-policies/blob/main/aws/restrict-ingress-sg-rule-ssh.sentinel",
323
+ relation: "overlaps",
324
+ },
325
+ {
326
+ tool: "terraform-sentinel-policies",
327
+ rule: "restrict-ingress-sg-rule-rdp",
328
+ url: "https://github.com/hashicorp/terraform-sentinel-policies/blob/main/aws/restrict-ingress-sg-rule-rdp.sentinel",
329
+ relation: "overlaps",
330
+ },
331
+ ],
332
+ // tfsec's check also flags a service wildcard (`s3:*`), which TF031 does
333
+ // not, so "overlaps". checkov splits the condition four ways: CKV_AWS_63
334
+ // (Action "*" on the policy resources, plus aws_ssoadmin_permission_set_
335
+ // inline_policy, which TF031 does not read) and CKV_AWS_49 (the same on the
336
+ // data source, which TF031 extends with Resource "*"); CKV_AWS_355/356 ask
337
+ // cloudsplaining whether Resource "*" meets a RESTRICTABLE action, a
338
+ // narrower test than TF031's literal one. trivy-checks has no credit:
339
+ // `aws-iam-no-policy-wildcards` (AWS-0057) survives there only as a
340
+ // `deprecated: true` metadata stub with no rule body, added by
341
+ // aquasecurity/trivy-checks c292eb6737e0 ("add removed Go checks as
342
+ // deprecated"), so it checks nothing to be equivalent to (#2286).
343
+ TF031: [
344
+ {
345
+ tool: "tfsec",
346
+ rule: "aws-iam-no-policy-wildcards",
347
+ url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/aws/iam/no-policy-wildcards/index.md",
348
+ relation: "overlaps",
349
+ },
350
+ {
351
+ tool: "checkov",
352
+ rule: "CKV_AWS_63",
353
+ url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/resource/aws/IAMStarActionPolicyDocument.py",
354
+ relation: "overlaps",
355
+ },
356
+ {
357
+ tool: "checkov",
358
+ rule: "CKV_AWS_49",
359
+ url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/data/aws/StarActionPolicyDocument.py",
360
+ relation: "extends",
361
+ },
362
+ {
363
+ tool: "checkov",
364
+ rule: "CKV_AWS_355",
365
+ url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/resource/aws/IAMStarResourcePolicyDocument.py",
366
+ relation: "overlaps",
367
+ },
368
+ {
369
+ tool: "checkov",
370
+ rule: "CKV_AWS_356",
371
+ url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/data/aws/ResourcePolicyDocument.py",
372
+ relation: "overlaps",
373
+ },
374
+ ],
375
+ // tfsec's `aws-ecs-no-plaintext-secrets` and its successor in trivy-checks
376
+ // (`long_id` aws-ecs-no-plaintext-secrets, AVD-AWS-0036) fire on the same
377
+ // condition: an environment entry whose name is sensitive or whose value a
378
+ // secret scanner flags. `overlaps`, not `equivalent`, because both evaluate
379
+ // the definition first and so also read what TF032 reports as not
380
+ // determined, and their name list differs from secret-shape.ts's. Checkov
381
+ // has no Terraform check for ECS environment values (its ECS checks cover
382
+ // privilege, read-only root, host process, task role and EFS encryption,
383
+ // `checkov/terraform/checks/resource/aws/ECS*.py`), so it gets no credit.
384
+ TF032: [
385
+ {
386
+ tool: "tfsec",
387
+ rule: "aws-ecs-no-plaintext-secrets",
388
+ url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/aws/ecs/no-plaintext-secrets/index.md",
389
+ relation: "overlaps",
390
+ },
391
+ {
392
+ tool: "trivy-checks",
393
+ rule: "aws-ecs-no-plaintext-secrets",
394
+ url: "https://github.com/aquasecurity/trivy-checks/blob/main/checks/cloud/aws/ecs/no_plaintext_secrets.rego",
395
+ relation: "overlaps",
396
+ },
397
+ ],
398
+ // TF033-TF037 (#2288). Every tool below reports an absent attribute the
399
+ // same as an insecure one, which is right for SNS (TF034) and ECR (TF037)
400
+ // and wrong for the rest: SQS encrypts new queues by default, an unset
401
+ // EBS `encrypted` follows the Region's encryption by default, and Aurora
402
+ // encrypts new clusters whatever `storage_encrypted` says. Where chant's
403
+ // answer to absence differs the credit is "overlaps"; where it agrees and
404
+ // the condition is the same, "equivalent". The ECR credits are "overlaps"
405
+ // too: none of them knows provider v6's `IMMUTABLE_WITH_EXCLUSION`, which
406
+ // TF037 passes and they report. trivy-checks ids are the live
407
+ // Rego checks (not `deprecated` stubs) at aquasecurity/trivy-checks
408
+ // 71c05d02845c. tfsec has no TF036 credit: its frozen `rules.md`, which
409
+ // the prior-art sweep indexes, no longer lists an EBS volume check.
410
+ TF033: [
411
+ { tool: "tfsec", rule: "aws-rds-encrypt-instance-storage-data", url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/aws/rds/encrypt-instance-storage-data/index.md", relation: "overlaps" },
412
+ { tool: "tfsec", rule: "aws-rds-encrypt-cluster-storage-data", url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/aws/rds/encrypt-cluster-storage-data/index.md", relation: "overlaps" },
413
+ { tool: "trivy-checks", rule: "aws-rds-encrypt-instance-storage-data", url: "https://github.com/aquasecurity/trivy-checks/blob/main/checks/cloud/aws/rds/encrypt_instance_storage_data.rego", relation: "overlaps" },
414
+ { tool: "trivy-checks", rule: "aws-rds-encrypt-cluster-storage-data", url: "https://github.com/aquasecurity/trivy-checks/blob/main/checks/cloud/aws/rds/encrypt_cluster_storage_data.rego", relation: "overlaps" },
415
+ { tool: "checkov", rule: "CKV_AWS_16", url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/resource/aws/RDSEncryption.py", relation: "overlaps" },
416
+ { tool: "checkov", rule: "CKV_AWS_96", url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/resource/aws/AuroraEncryption.py", relation: "overlaps" },
417
+ { tool: "kics", rule: "08bd0760-8752-44e1-9779-7bb369b2b4e4", url: "https://docs.kics.io/latest/queries/terraform-queries/aws/08bd0760-8752-44e1-9779-7bb369b2b4e4/", relation: "overlaps" },
418
+ ],
419
+ TF034: [
420
+ { tool: "tfsec", rule: "aws-sns-enable-topic-encryption", url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/aws/sns/enable-topic-encryption/index.md", relation: "equivalent" },
421
+ { tool: "trivy-checks", rule: "aws-sns-enable-topic-encryption", url: "https://github.com/aquasecurity/trivy-checks/blob/main/checks/cloud/aws/sns/enable_topic_encryption.rego", relation: "equivalent" },
422
+ { tool: "checkov", rule: "CKV_AWS_26", url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/resource/aws/SNSTopicEncryption.py", relation: "equivalent" },
423
+ { tool: "kics", rule: "28545147-2fc6-42d5-a1f9-cf226658e591", url: "https://docs.kics.io/latest/queries/terraform-queries/aws/28545147-2fc6-42d5-a1f9-cf226658e591/", relation: "equivalent" },
424
+ ],
425
+ TF035: [
426
+ { tool: "tfsec", rule: "aws-sqs-enable-queue-encryption", url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/aws/sqs/enable-queue-encryption/index.md", relation: "overlaps" },
427
+ { tool: "trivy-checks", rule: "aws-sqs-enable-queue-encryption", url: "https://github.com/aquasecurity/trivy-checks/blob/main/checks/cloud/aws/sqs/enable_queue_encryption.rego", relation: "overlaps" },
428
+ { tool: "checkov", rule: "CKV_AWS_27", url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/resource/aws/SQSQueueEncryption.py", relation: "overlaps" },
429
+ { tool: "kics", rule: "6e8849c1-3aa7-40e3-9063-b85ee300f29f", url: "https://docs.kics.io/latest/queries/terraform-queries/aws/6e8849c1-3aa7-40e3-9063-b85ee300f29f/", relation: "overlaps" },
430
+ ],
431
+ TF036: [
432
+ { tool: "trivy-checks", rule: "aws-ec2-enable-volume-encryption", url: "https://github.com/aquasecurity/trivy-checks/blob/main/checks/cloud/aws/ec2/enable_volume_encryption.rego", relation: "overlaps" },
433
+ { tool: "checkov", rule: "CKV_AWS_3", url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/resource/aws/EBSEncryption.py", relation: "overlaps" },
434
+ { tool: "kics", rule: "cc997676-481b-4e93-aa81-d19f8c5e9b12", url: "https://docs.kics.io/latest/queries/terraform-queries/aws/cc997676-481b-4e93-aa81-d19f8c5e9b12/", relation: "overlaps" },
435
+ ],
436
+ TF037: [
437
+ { tool: "tfsec", rule: "aws-ecr-enforce-immutable-repository", url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/aws/ecr/enforce-immutable-repository/index.md", relation: "overlaps" },
438
+ { tool: "trivy-checks", rule: "aws-ecr-enforce-immutable-repository", url: "https://github.com/aquasecurity/trivy-checks/blob/main/checks/cloud/aws/ecr/enforce_immutable_repository.rego", relation: "overlaps" },
439
+ { tool: "checkov", rule: "CKV_AWS_51", url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/resource/aws/ECRImmutableTags.py", relation: "overlaps" },
440
+ { tool: "kics", rule: "d1846b12-20c5-4d45-8798-fc35b79268eb", url: "https://docs.kics.io/latest/queries/terraform-queries/aws/d1846b12-20c5-4d45-8798-fc35b79268eb/", relation: "overlaps" },
441
+ ],
442
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Shared reading helpers for the single-block post-synth checks (#2110).
3
+ *
4
+ * Thirteen of the fourteen rules in that issue do the same three things: pick
5
+ * the entities of one block type out of `ctx.entities`, read one attribute of
6
+ * each body, and report. This module is those three things, so a rule file is
7
+ * its condition and its message and nothing else. It exports no
8
+ * `PostSynthCheck`, so the generated barrel skips it (see
9
+ * `packages/core/src/codegen/generate-post-synth-barrel.ts`).
10
+ */
11
+ import { isResourceDeclarable } from "@intentius/chant/declarable";
12
+ /** Every block of one entity type, in `ctx.entities` order. */
13
+ export function blocksOfType(ctx, entityType) {
14
+ const blocks = [];
15
+ for (const [key, entity] of ctx.entities) {
16
+ if (entity.entityType !== entityType)
17
+ continue;
18
+ if (!isResourceDeclarable(entity))
19
+ continue;
20
+ const props = entity.props;
21
+ blocks.push({
22
+ key,
23
+ address: typeof props.address === "string" ? props.address : key,
24
+ body: (typeof props.body === "object" && props.body !== null ? props.body : {}),
25
+ file: typeof props.file === "string" ? props.file : "",
26
+ root: typeof props.root === "string" ? props.root : "",
27
+ source: typeof props.source === "string" ? props.source : "",
28
+ });
29
+ }
30
+ return blocks;
31
+ }
32
+ /** Blocks of several types at once, in the order the types are given. */
33
+ export function blocksOfTypes(ctx, entityTypes) {
34
+ return entityTypes.flatMap((t) => blocksOfType(ctx, t));
35
+ }
36
+ /** The name half of a `var.<name>` / `output.<name>` address. */
37
+ export function blockName(address) {
38
+ const dot = address.indexOf(".");
39
+ return dot === -1 ? address : address.slice(dot + 1);
40
+ }
41
+ /**
42
+ * The bodies of a nested block (`lifecycle`, `validation`, a provider's
43
+ * `assume_role`). hcl2json encodes a repeatable block as an array of bodies
44
+ * and a singleton as a bare object, so both are normalized to an array here.
45
+ */
46
+ export function nestedBodies(body, key) {
47
+ const value = body[key];
48
+ if (Array.isArray(value))
49
+ return value.filter((v) => typeof v === "object" && v !== null && !Array.isArray(v));
50
+ if (typeof value === "object" && value !== null)
51
+ return [value];
52
+ return [];
53
+ }
54
+ /** Is this attribute value a string that says nothing? */
55
+ export function isBlank(value) {
56
+ return value === undefined || value === null || (typeof value === "string" && value.trim() === "");
57
+ }
58
+ /**
59
+ * Every scalar attribute in a block body, descending through nested blocks so
60
+ * a credential inside `connection {}` or `assume_role {}` is not invisible.
61
+ * Depth-limited: a Terraform block nests a handful of levels at most, and an
62
+ * unbounded walk over a large body buys nothing.
63
+ */
64
+ export function walkAttributes(body, maxDepth = 3) {
65
+ const out = [];
66
+ const visit = (value, path, name, depth) => {
67
+ if (Array.isArray(value)) {
68
+ if (depth > maxDepth)
69
+ return;
70
+ for (const item of value)
71
+ visit(item, path, name, depth);
72
+ return;
73
+ }
74
+ if (typeof value === "object" && value !== null) {
75
+ if (depth >= maxDepth)
76
+ return;
77
+ for (const [k, v] of Object.entries(value)) {
78
+ visit(v, path === "" ? k : `${path}.${k}`, k, depth + 1);
79
+ }
80
+ return;
81
+ }
82
+ if (path !== "")
83
+ out.push({ name, path, value });
84
+ };
85
+ visit(body, "", "", 0);
86
+ return out;
87
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Shared post-synth fixture loader.
3
+ *
4
+ * KICS lays its own query fixtures out as `positive.tf`/`negative.tf` pairs
5
+ * beside the query that tests them (survey, #2107): every query is
6
+ * externally auditable because a rule and its evidence live in the same
7
+ * directory. This lexicon adopts that layout: `fixtures/<id>/positive.tf`
8
+ * triggers the check, `fixtures/<id>/negative.tf` does not, and a rule with
9
+ * more than one triggering condition adds `positive-<case>.tf` beside them.
10
+ *
11
+ * KICS itself scans a whole fixture directory in one pass and tells its
12
+ * fixtures apart by the file each finding came from. That doesn't transfer
13
+ * here: TF001 (and most post-synth checks with per-root state, not
14
+ * per-resource) fire once per *root*, deduplicated by root name, so parsing
15
+ * `positive.tf` and `negative.tf` together as one root would let whichever
16
+ * file's terraform block is read first decide the outcome for both, silently
17
+ * discarding the other. `loadFixture` therefore reads exactly the one named
18
+ * file and parses it alone, through the same `blocksToEntities` pass
19
+ * `parseTerraformRootDir` wraps for a real root directory, so sibling
20
+ * fixtures never mix into one build.
21
+ */
22
+ import { readFileSync } from "node:fs";
23
+ import { dirname, join } from "node:path";
24
+ import { fileURLToPath } from "node:url";
25
+ import { blocksToEntities } from "../../../hcl/parse.js";
26
+ import { renderTerraformRoots } from "../../../hcl/roots.js";
27
+ const FIXTURES_DIR = dirname(fileURLToPath(import.meta.url));
28
+ /**
29
+ * Parse `fixtures/<checkId>/<name>.tf` into a {@link PostSynthContext} whose
30
+ * `entities` are exactly what a real root module of that one file would
31
+ * produce. `root` (the entity key prefix and the `TerraformEntity.props.root`
32
+ * value) defaults to `checkId`; pass a distinct value to load two fixtures
33
+ * into one context under different root names.
34
+ *
35
+ * Three lines per fixture in a test:
36
+ * ```ts
37
+ * const diags = tf001.check(await loadFixture("TF001", "positive"));
38
+ * expect(diags).toHaveLength(1);
39
+ * ```
40
+ */
41
+ export async function loadFixture(checkId, name, root = checkId) {
42
+ const file = `${name}.tf`;
43
+ const source = readFileSync(join(FIXTURES_DIR, checkId, file), "utf-8");
44
+ const entities = await blocksToEntities([{ name: file, source }], root);
45
+ return {
46
+ outputs: new Map(),
47
+ entities,
48
+ buildResult: {
49
+ outputs: new Map(),
50
+ entities,
51
+ warnings: [],
52
+ errors: [],
53
+ sourceFileCount: 1,
54
+ },
55
+ };
56
+ }
57
+ /**
58
+ * Parse a whole fixture DIRECTORY, `fixtures/<checkId>/<name>/`, the way
59
+ * `buildRoots()` parses a configured root: its own `.tf` files, then every
60
+ * local `module` call followed into its directory (chant #2112). A rule about
61
+ * child modules (TF014, TF015) or about a whole scope's references (TF020)
62
+ * cannot be shown a single file and still be tested honestly, so its fixture
63
+ * is a tree:
64
+ *
65
+ * ```
66
+ * fixtures/TF014/positive/main.tf # module "cdn" { source = "./modules/cdn" }
67
+ * fixtures/TF014/positive/modules/cdn/main.tf # the provider block the rule reports
68
+ * ```
69
+ *
70
+ * The fixture directory is also the project root for the descent, so a
71
+ * fixture can exercise the outside-the-project refusal with a `../` source
72
+ * without reaching into the repository around it. `warnings` from the render
73
+ * lands on `buildResult.warnings`, where the descent's own refusals are
74
+ * readable by a test.
75
+ */
76
+ export async function loadTreeFixture(checkId, name, root = checkId, callModuleType) {
77
+ const dir = join(FIXTURES_DIR, checkId, name);
78
+ const { entities, warnings } = await renderTerraformRoots({
79
+ projectRoot: dir,
80
+ roots: { [root]: { dir } },
81
+ ...(callModuleType ? { callModuleType } : {}),
82
+ });
83
+ return {
84
+ outputs: new Map(),
85
+ entities,
86
+ buildResult: {
87
+ outputs: new Map(),
88
+ entities,
89
+ warnings,
90
+ errors: [],
91
+ sourceFileCount: entities.size,
92
+ },
93
+ };
94
+ }