@ploaness/governance 1.2.0 → 1.4.0

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 (65) hide show
  1. package/dist/checkov-check.d.ts +9 -0
  2. package/dist/checkov-check.d.ts.map +1 -0
  3. package/dist/checkov-check.js +4 -0
  4. package/dist/checkov-check.js.map +1 -0
  5. package/dist/checkov-checks-aws.d.ts +4 -0
  6. package/dist/checkov-checks-aws.d.ts.map +1 -0
  7. package/dist/checkov-checks-aws.js +93 -0
  8. package/dist/checkov-checks-aws.js.map +1 -0
  9. package/dist/checkov-checks-azure.d.ts +4 -0
  10. package/dist/checkov-checks-azure.d.ts.map +1 -0
  11. package/dist/checkov-checks-azure.js +32 -0
  12. package/dist/checkov-checks-azure.js.map +1 -0
  13. package/dist/checkov-checks-google.d.ts +6 -0
  14. package/dist/checkov-checks-google.d.ts.map +1 -0
  15. package/dist/checkov-checks-google.js +53 -0
  16. package/dist/checkov-checks-google.js.map +1 -0
  17. package/dist/checkov-policy.d.ts +63 -0
  18. package/dist/checkov-policy.d.ts.map +1 -0
  19. package/dist/checkov-policy.js +105 -0
  20. package/dist/checkov-policy.js.map +1 -0
  21. package/dist/environment-coherence.d.ts +59 -5
  22. package/dist/environment-coherence.d.ts.map +1 -1
  23. package/dist/environment-coherence.js +107 -5
  24. package/dist/environment-coherence.js.map +1 -1
  25. package/dist/index.d.ts +8 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +8 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/payload-bootstrap.d.ts +52 -0
  30. package/dist/payload-bootstrap.d.ts.map +1 -0
  31. package/dist/payload-bootstrap.js +195 -0
  32. package/dist/payload-bootstrap.js.map +1 -0
  33. package/dist/payload-database.d.ts +80 -0
  34. package/dist/payload-database.d.ts.map +1 -0
  35. package/dist/payload-database.js +164 -0
  36. package/dist/payload-database.js.map +1 -0
  37. package/dist/payload-policy.d.ts +8 -0
  38. package/dist/payload-policy.d.ts.map +1 -1
  39. package/dist/payload-policy.js +12 -4
  40. package/dist/payload-policy.js.map +1 -1
  41. package/dist/shell-files.d.ts +45 -0
  42. package/dist/shell-files.d.ts.map +1 -0
  43. package/dist/shell-files.js +105 -0
  44. package/dist/shell-files.js.map +1 -0
  45. package/dist/source-security.d.ts +10 -0
  46. package/dist/source-security.d.ts.map +1 -1
  47. package/dist/source-security.js +88 -1
  48. package/dist/source-security.js.map +1 -1
  49. package/dist/terraform-files.d.ts +29 -0
  50. package/dist/terraform-files.d.ts.map +1 -0
  51. package/dist/terraform-files.js +50 -0
  52. package/dist/terraform-files.js.map +1 -0
  53. package/dist/terraform-policy.d.ts +13 -0
  54. package/dist/terraform-policy.d.ts.map +1 -0
  55. package/dist/terraform-policy.js +261 -0
  56. package/dist/terraform-policy.js.map +1 -0
  57. package/dist/terraform-providers.d.ts +28 -0
  58. package/dist/terraform-providers.d.ts.map +1 -0
  59. package/dist/terraform-providers.js +68 -0
  60. package/dist/terraform-providers.js.map +1 -0
  61. package/dist/toolchain-pins.d.ts +1 -1
  62. package/dist/toolchain-pins.d.ts.map +1 -1
  63. package/dist/toolchain-pins.js +9 -2
  64. package/dist/toolchain-pins.js.map +1 -1
  65. package/package.json +1 -1
@@ -0,0 +1,50 @@
1
+ // Identify OpenTofu and Terraform sources, and the analyzer configuration that would shadow the rules.
2
+ //
3
+ // The same shape as the container-file reader beside it: predicates over the working-tree inventory,
4
+ // so the gate discovers what a repository actually tracks rather than reading a list somebody wrote
5
+ // once. A `.tf` added later is analysed the day it appears rather than the day somebody remembers it.
6
+ const basenameOf = (file) => file.slice(file.lastIndexOf('/') + 1);
7
+ // `.tf` is the configuration and `.tfvars` carries the values a placeholder credential would sit in.
8
+ // `.tf.json` is deliberately absent: it is JSON rather than HCL, so the text rules would misread it,
9
+ // and the analyzer reads it anyway through the directory it is pointed at.
10
+ const TERRAFORM_EXTENSIONS = ['.tf', '.tfvars'];
11
+ /**
12
+ * Decide whether a path names an OpenTofu or Terraform source.
13
+ *
14
+ * An example file is not one. `terraform.tfvars.example` documents the variables an environment needs
15
+ * and is meant to carry placeholders, so reading it as configuration would report a project for the
16
+ * one file whose job is to hold the values it tells a reader to replace.
17
+ * @param file a repo-relative path.
18
+ * @returns true when the file is configuration the gate judges.
19
+ */
20
+ export const isTerraformFile = (file) => TERRAFORM_EXTENSIONS.some((extension) => file.endsWith(extension));
21
+ /**
22
+ * Every OpenTofu or Terraform source the repository tracks.
23
+ * @param tracked the repo-relative paths of the tracked files.
24
+ * @returns the sources, ordered so a report reads the same on every machine.
25
+ */
26
+ export const terraformFilesIn = (tracked) => tracked
27
+ .filter((file) => isTerraformFile(file))
28
+ .toSorted((left, right) => left.localeCompare(right));
29
+ // The names checkov reads on its own. A project that commits one can turn off the curated checks from
30
+ // inside the tree the gate judges, which is the shadowing the harness refuses for every other analyzer.
31
+ const CHECKOV_CONFIG_BASENAMES = new Set([
32
+ '.checkov.yaml',
33
+ '.checkov.yml',
34
+ '.checkov.json',
35
+ ]);
36
+ /**
37
+ * Decide whether a path names a checkov configuration.
38
+ * @param file a repo-relative path.
39
+ * @returns true for a configuration checkov would read on its own, at any depth.
40
+ */
41
+ export const isCheckovConfigFile = (file) => CHECKOV_CONFIG_BASENAMES.has(basenameOf(file));
42
+ /**
43
+ * Every checkov configuration the repository tracks.
44
+ * @param tracked the repo-relative paths of the tracked files.
45
+ * @returns the configurations, ordered so a report reads the same on every machine.
46
+ */
47
+ export const checkovConfigsIn = (tracked) => tracked
48
+ .filter((file) => isCheckovConfigFile(file))
49
+ .toSorted((left, right) => left.localeCompare(right));
50
+ //# sourceMappingURL=terraform-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terraform-files.js","sourceRoot":"","sources":["../src/terraform-files.ts"],"names":[],"mappings":"AAAA,uGAAuG;AACvG,EAAE;AACF,qGAAqG;AACrG,oGAAoG;AACpG,sGAAsG;AAEtG,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;AAElF,qGAAqG;AACrG,qGAAqG;AACrG,2EAA2E;AAC3E,MAAM,oBAAoB,GAAsB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;AAElE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAW,EAAE,CACvD,oBAAoB,CAAC,IAAI,CAAC,CAAC,SAAiB,EAAW,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;AAErF;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAA0B,EAAqB,EAAE,CAChF,OAAO;KACJ,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;KACxD,QAAQ,CAAC,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA;AAEjF,sGAAsG;AACtG,wGAAwG;AACxG,MAAM,wBAAwB,GAAwB,IAAI,GAAG,CAAC;IAC5D,eAAe;IACf,cAAc;IACd,eAAe;CAChB,CAAC,CAAA;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAW,EAAE,CAC3D,wBAAwB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAA0B,EAAqB,EAAE,CAChF,OAAO;KACJ,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;KAC5D,QAAQ,CAAC,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA"}
@@ -0,0 +1,13 @@
1
+ /** An infrastructure defect found in a project's own configuration. */
2
+ export interface TerraformViolation {
3
+ readonly line: number;
4
+ readonly rule: string;
5
+ readonly reason: string;
6
+ }
7
+ /**
8
+ * Report every infrastructure defect a text reader can decide.
9
+ * @param source the file's text, as tracked.
10
+ * @returns one violation per defect, ordered by the line it sits on.
11
+ */
12
+ export declare const findTerraformViolations: (source: string) => readonly TerraformViolation[];
13
+ //# sourceMappingURL=terraform-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terraform-policy.d.ts","sourceRoot":"","sources":["../src/terraform-policy.ts"],"names":[],"mappings":"AAiBA,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAySD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,MAAM,KAAG,SAAS,kBAAkB,EAcnF,CAAA"}
@@ -0,0 +1,261 @@
1
+ // Infrastructure defects a text reader can decide on its own, and only those.
2
+ //
3
+ // Every rule here owns a question the analyzer beside it does not answer. Where checkov has a check,
4
+ // checkov keeps it: one semantic rule has one owner, and a second opinion written as a regular
5
+ // expression would be the weaker of the two. So `AdministratorAccess`, wildcard IAM actions and a
6
+ // publicly reachable database are absent from this file deliberately - they are enabled as curated
7
+ // checks instead, where the resource graph is understood rather than guessed at from one line.
8
+ //
9
+ // `0.0.0.0/0` is read in exactly one place: the standalone ingress rule resource, whose TYPE names its
10
+ // direction. Everywhere else text cannot decide it. The same address is correct on egress and fatal on
11
+ // ingress, and an `ingress` block or an `aws_security_group_rule` carries its direction in a nested
12
+ // block or a sibling attribute that a line reader cannot tie to the address - so those forms stay
13
+ // with the analyzer, which knows which side of the rule it is looking at. The standalone resource is
14
+ // this file's because the analyzer does not read its idiomatic spelling at all, and its own SSH and RDP
15
+ // checks read a rule naming another security group as its source as open to the world.
16
+ import { stripComments } from './source-text.js';
17
+ const FIRST_LINE = 1;
18
+ // An argument assigned one literal value, bare or quoted, on its own line. Terraform accepts both
19
+ // spellings of a boolean, and a quoted string is what the other values are.
20
+ const assigned = (attribute, value) => new RegExp(String.raw `^[ \t]*${attribute}[ \t]*=[ \t]*"?${value}"?(?:[ \t]|$)`);
21
+ // The Owner definition has one id in every Azure tenant, and a role assignment may name it by id
22
+ // rather than by name.
23
+ const AZURE_OWNER_DEFINITION = '8e3af657-a8ff-443c-a75c-2fe8c4bcb635';
24
+ // Each rule refuses an explicit literal and never an absence, which is what keeps every one of them
25
+ // free of false positives: a value that arrives through a variable or a reference never matches. The
26
+ // first two arguments exist only where they destroy data, and checkov ships no check for either. The
27
+ // last two stand in for checks that fail the rubric - `CKV_GCP_6` rejects the correct
28
+ // `ENCRYPTED_ONLY`, `CKV_AZURE_3` and `CKV_AZURE_91` read only the argument names azurerm retired,
29
+ // and every project-role check on Google Cloud also flags `viewer` or a documented deploy role,
30
+ // while Azure has no check for an Owner assignment at all. A checkov check that fails the rubric
31
+ // returns its question to text. The patterns are built once here rather than per line, so the scan
32
+ // is one pass over the text.
33
+ const FLAG_RULES = [
34
+ {
35
+ patterns: [assigned('force_destroy', 'true')],
36
+ rule: 'no-force-destroy',
37
+ reason: 'force_destroy lets a destroy remove a bucket or repository that still holds objects, so a ' +
38
+ 'replacement-forcing edit deletes the data rather than failing - remove it, and empty the ' +
39
+ 'store deliberately when it is genuinely meant to go',
40
+ },
41
+ {
42
+ patterns: [assigned('skip_final_snapshot', 'true')],
43
+ rule: 'no-skipped-final-snapshot',
44
+ reason: 'skip_final_snapshot leaves a deleted database with nothing to restore from, and a destroy ' +
45
+ 'that was never meant to reach production is exactly when that snapshot is wanted - remove it ' +
46
+ 'and let the final snapshot be taken',
47
+ },
48
+ {
49
+ patterns: [
50
+ assigned('role', 'roles/owner'),
51
+ assigned('role', 'roles/editor'),
52
+ assigned('role_definition_name', 'Owner'),
53
+ new RegExp(String.raw `^[ \t]*role_definition_id[ \t]*=[ \t]*"[^"]*${AZURE_OWNER_DEFINITION}"`),
54
+ ],
55
+ rule: 'no-administrative-role-grant',
56
+ reason: 'an administrative role is granted outright, which is every permission on everything in its ' +
57
+ 'scope - grant the specific roles the principal needs, and no more',
58
+ },
59
+ {
60
+ patterns: [
61
+ assigned('ssl_mode', 'ALLOW_UNENCRYPTED_AND_ENCRYPTED'),
62
+ assigned('require_ssl', 'false'),
63
+ assigned('https_traffic_only_enabled', 'false'),
64
+ assigned('enable_https_traffic_only', 'false'),
65
+ assigned('non_ssl_port_enabled', 'true'),
66
+ assigned('enable_non_ssl_port', 'true'),
67
+ ],
68
+ rule: 'no-unencrypted-transport',
69
+ reason: 'the service explicitly accepts unencrypted connections - remove the argument or set its ' +
70
+ 'encrypted-only value; every client the harness governs speaks TLS',
71
+ },
72
+ ];
73
+ // A quoted assignment, whatever it names. WHICH names hold a credential is decided below rather than
74
+ // inside the pattern: one alternation covering every spelling was unreadable and past the complexity
75
+ // the lint budget allows, and it is the name's segments that carry the meaning anyway.
76
+ const QUOTED_ASSIGNMENT = /^[ \t]*(?<name>\w+)[ \t]*=[ \t]*"(?<value>[^"]*)"/;
77
+ // Read by the last `_`-segment of the name, the way the credential guard in `source-security.ts` reads
78
+ // an identifier. `key` alone is excluded: an S3 object's `key` is a path, and reading it as a
79
+ // credential would report every upload in the tree - so only a QUALIFIED key counts.
80
+ const CREDENTIAL_WORDS = new Set([
81
+ 'password',
82
+ 'passphrase',
83
+ 'secret',
84
+ 'token',
85
+ 'credential',
86
+ 'credentials',
87
+ ]);
88
+ const QUALIFIED_KEYS = new Set([
89
+ 'api',
90
+ 'access',
91
+ 'secret',
92
+ 'private',
93
+ 'encryption',
94
+ ]);
95
+ // A secret's CONTENT argument is named the other way round: `secret_string`, `secret_data` and
96
+ // `secret_binary` are what a secret-store version holds, so the first segment carries the meaning and
97
+ // the last says what shape the content takes. `secret_id` and `secret_arn` name a secret rather than
98
+ // hold one, and stay outside.
99
+ const SECRET_CONTENT = new Set(['string', 'data', 'binary', 'value']);
100
+ const PENULTIMATE = -2;
101
+ const isCredentialName = (name) => {
102
+ const segments = name.toLowerCase().split('_');
103
+ const first = segments.at(0) ?? '';
104
+ const last = segments.at(-1) ?? '';
105
+ if (last === 'key') {
106
+ return QUALIFIED_KEYS.has(segments.at(PENULTIMATE) ?? '');
107
+ }
108
+ return CREDENTIAL_WORDS.has(last) || (first === 'secret' && SECRET_CONTENT.has(last));
109
+ };
110
+ // What a placeholder looks like once punctuation and case stop mattering. An angle-bracketed value is
111
+ // the other conventional spelling, and it needs no vocabulary.
112
+ const PLACEHOLDER_VALUES = new Set([
113
+ 'REPLACEME',
114
+ 'CHANGEME',
115
+ 'TODO',
116
+ 'FIXME',
117
+ 'XXX',
118
+ 'PLACEHOLDER',
119
+ 'TBD',
120
+ 'YOURVALUEHERE',
121
+ ]);
122
+ const isPlaceholder = (value) => {
123
+ if (value.startsWith('<') && value.endsWith('>')) {
124
+ return true;
125
+ }
126
+ return PLACEHOLDER_VALUES.has(value.toUpperCase().replaceAll(/[^A-Z0-9]/g, ''));
127
+ };
128
+ // An analyzer's own inline skip. Without this the curated checks are advisory: one comment turns the
129
+ // flagship check off and the run still passes. gitleaks cannot see a placeholder either - it carries no
130
+ // entropy - which is why the value rule above is here rather than left to the secret scan.
131
+ const ANALYZER_SUPPRESSION = /#[ \t]*(?<analyzer>checkov:skip=|tfsec:ignore:|trivy:ignore:)/;
132
+ const flagViolations = (code) => code.split('\n').flatMap((line, index) => FLAG_RULES.filter((flag) => flag.patterns.some((pattern) => pattern.test(line))).map((flag) => ({
133
+ line: index + FIRST_LINE,
134
+ rule: flag.rule,
135
+ reason: flag.reason,
136
+ })));
137
+ // The attribute a line leaves as a placeholder, or nothing. Separated from the walk below so each reads
138
+ // as the one question it asks.
139
+ const placeholderNameIn = (line) => {
140
+ const found = QUOTED_ASSIGNMENT.exec(line);
141
+ if (found === null) {
142
+ return undefined;
143
+ }
144
+ const name = found.groups?.['name'] ?? '';
145
+ const value = found.groups?.['value'] ?? '';
146
+ return isCredentialName(name) && isPlaceholder(value) ? name : undefined;
147
+ };
148
+ const placeholderViolations = (code) => code.split('\n').flatMap((line, index) => {
149
+ const name = placeholderNameIn(line);
150
+ return name === undefined
151
+ ? []
152
+ : [
153
+ {
154
+ line: index + FIRST_LINE,
155
+ rule: 'no-placeholder-secret',
156
+ reason: `${name} is set to a placeholder, so whatever it guards is guarded by a value written ` +
157
+ 'into the repository - supply it from a secret store, and let the configuration name ' +
158
+ 'the reference rather than the value',
159
+ },
160
+ ];
161
+ });
162
+ // Read from the RAW text, unlike every rule above: a comment is this rule's subject rather than
163
+ // something to see past.
164
+ const suppressionViolations = (source) => source.split('\n').flatMap((line, index) => {
165
+ const analyzer = ANALYZER_SUPPRESSION.exec(line)?.groups?.['analyzer'];
166
+ return analyzer === undefined
167
+ ? []
168
+ : [
169
+ {
170
+ line: index + FIRST_LINE,
171
+ rule: 'no-analyzer-suppression',
172
+ reason: `"${analyzer}" turns off a check this harness enabled, from inside the file the check ` +
173
+ 'judges - repair what the analyzer found, because a gate a project can switch off is ' +
174
+ 'not a gate',
175
+ },
176
+ ];
177
+ });
178
+ // The rule resource is read from its header to the next line that closes or opens a top-level block.
179
+ // Formatted HCL closes a resource at column 0; unformatted HCL over-reads at worst the blank lines
180
+ // before the next block, never the next block's attributes.
181
+ const INGRESS_RULE_HEADER = /^[ \t]*resource[ \t]+"aws_vpc_security_group_ingress_rule"/;
182
+ const BLOCK_BOUNDARY = /^(?:\}|(?:resource|data|module|variable|output|locals|provider|terraform)\b)/;
183
+ const ANY_ADDRESS = /^[ \t]*cidr_ipv[46][ \t]*=[ \t]*"(?:0\.0\.0\.0\/0|::\/0)"/;
184
+ const EVERY_PROTOCOL = /^[ \t]*ip_protocol[ \t]*=[ \t]*"?-1"?/;
185
+ const PORT_EDGE = /^[ \t]*(?<edge>from_port|to_port)[ \t]*=[ \t]*(?<port>\d+)/;
186
+ const SSH_PORT = 22;
187
+ const RDP_PORT = 3389;
188
+ const LAST_PORT = 65_535;
189
+ const ingressRulesIn = (lines) => lines.flatMap((line, index) => {
190
+ if (!INGRESS_RULE_HEADER.test(line)) {
191
+ return [];
192
+ }
193
+ const rest = lines.slice(index + 1);
194
+ const end = rest.findIndex((candidate) => BLOCK_BOUNDARY.test(candidate));
195
+ return [{ line: index + FIRST_LINE, body: end === -1 ? rest : rest.slice(0, end) }];
196
+ });
197
+ const portRangeOf = (body) => {
198
+ const edges = new Map(body.flatMap((line) => {
199
+ const found = PORT_EDGE.exec(line);
200
+ return found === null
201
+ ? []
202
+ : [[found.groups?.['edge'] ?? '', Number(found.groups?.['port'] ?? '')]];
203
+ }));
204
+ const from = edges.get('from_port');
205
+ const to = edges.get('to_port');
206
+ return from === undefined || to === undefined ? undefined : { from, to };
207
+ };
208
+ const coversPort = (range, port) => range.from <= port && port <= range.to;
209
+ // What a port range exposes when its source is every address, or nothing when its ports are ones a
210
+ // public site legitimately opens.
211
+ const exposureOfRange = (range) => {
212
+ if (range.from === 0 && range.to === LAST_PORT) {
213
+ return 'every port';
214
+ }
215
+ if (coversPort(range, SSH_PORT)) {
216
+ return 'SSH';
217
+ }
218
+ return coversPort(range, RDP_PORT) ? 'RDP' : undefined;
219
+ };
220
+ const exposureOf = (body) => {
221
+ if (!body.some((line) => ANY_ADDRESS.test(line))) {
222
+ return undefined;
223
+ }
224
+ if (body.some((line) => EVERY_PROTOCOL.test(line))) {
225
+ return 'every port';
226
+ }
227
+ const range = portRangeOf(body);
228
+ return range === undefined ? undefined : exposureOfRange(range);
229
+ };
230
+ const openIngressViolations = (code) => ingressRulesIn(code.split('\n')).flatMap((rule) => {
231
+ const exposure = exposureOf(rule.body);
232
+ return exposure === undefined
233
+ ? []
234
+ : [
235
+ {
236
+ line: rule.line,
237
+ rule: 'no-open-ingress',
238
+ reason: `an ingress rule admits ${exposure} from the whole internet - name the security ` +
239
+ 'group or address range that needs it as the source',
240
+ },
241
+ ];
242
+ });
243
+ /**
244
+ * Report every infrastructure defect a text reader can decide.
245
+ * @param source the file's text, as tracked.
246
+ * @returns one violation per defect, ordered by the line it sits on.
247
+ */
248
+ export const findTerraformViolations = (source) => {
249
+ // `stripComments` covers the `//` and `/* */` forms HCL shares with the languages it was written
250
+ // for. It does NOT read `#`, which is HCL's own spelling - what keeps `# force_destroy = true` out
251
+ // of the value rules is that each pattern is anchored at the attribute, so a leading `#` cannot
252
+ // match. The suppression rule reads the raw text instead, because the comment is its subject.
253
+ const code = stripComments(source);
254
+ return [
255
+ ...flagViolations(code),
256
+ ...placeholderViolations(code),
257
+ ...openIngressViolations(code),
258
+ ...suppressionViolations(source),
259
+ ].toSorted((left, right) => left.line - right.line);
260
+ };
261
+ //# sourceMappingURL=terraform-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terraform-policy.js","sourceRoot":"","sources":["../src/terraform-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /** One line that names a provider: a resource, a data source, or a provider block. */
2
+ export interface ProviderDeclaration {
3
+ readonly line: number;
4
+ readonly provider: string;
5
+ /** What the line declares, as a finding names it: the resource type, or `provider "name"`. */
6
+ readonly subject: string;
7
+ readonly kind: 'resource' | 'data' | 'provider';
8
+ }
9
+ /**
10
+ * Every line of a source that names a provider.
11
+ * @param source the file's text, as tracked.
12
+ * @returns the declarations, ordered by the line they sit on.
13
+ */
14
+ export declare const providerDeclarationsIn: (source: string) => readonly ProviderDeclaration[];
15
+ /**
16
+ * The providers a set of declarations names.
17
+ * @param declarations declarations from one or many sources.
18
+ * @returns each provider once, ordered so a report reads the same on every machine.
19
+ */
20
+ export declare const providersOf: (declarations: readonly ProviderDeclaration[]) => readonly string[];
21
+ /**
22
+ * How many resources and data sources the declarations carry - what an analyzer has to judge. A
23
+ * provider block configures a provider and declares nothing it could check.
24
+ * @param declarations declarations from one or many sources.
25
+ * @returns the count of resource and data declarations.
26
+ */
27
+ export declare const resourceCountOf: (declarations: readonly ProviderDeclaration[]) => number;
28
+ //# sourceMappingURL=terraform-providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terraform-providers.d.ts","sourceRoot":"","sources":["../src/terraform-providers.ts"],"names":[],"mappings":"AAUA,sFAAsF;AACtF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,UAAU,CAAA;CAChD;AA6CD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,SAAS,mBAAmB,EAKnF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,cAAc,SAAS,mBAAmB,EAAE,KAAG,SAAS,MAAM,EAKV,CAAA;AAEhF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,cAAc,SAAS,mBAAmB,EAAE,KAAG,MAGrE,CAAA"}
@@ -0,0 +1,68 @@
1
+ // Which providers an OpenTofu or Terraform source declares, read from block headers alone.
2
+ //
3
+ // The resource type is the key, because it is what the analyzer's own catalogue binds a check to:
4
+ // `aws_iam_role` belongs to `aws` whatever local name a `required_providers` block gives the provider.
5
+ // That block is deliberately not read. Modules inherit it from their caller, so it sits in a minority
6
+ // of files; its `source = "..."` line cannot be told from a module's without block parsing; and a
7
+ // provider declared there with no resource in the tree leaves the analyzer nothing to judge anyway.
8
+ // Nothing here parses HCL structure - this stays a line reader, like the policy beside it.
9
+ import { lineOf, stripComments } from './source-text.js';
10
+ // Anchored at the start of a line so HCL's own `#` comment, which `stripComments` does not read, cannot
11
+ // match; a `//` or `/* */` comment is blanked before the scan (see terraform-policy.ts).
12
+ const RESOURCE_HEADER = /^[ \t]*(?<kind>resource|data)[ \t]+"(?<type>[a-z][a-z0-9_]*)"/gm;
13
+ const PROVIDER_HEADER = /^[ \t]*provider[ \t]+"(?<name>[a-z][a-z0-9-]*)"/gm;
14
+ // A provider whose resources carry another provider's prefix. `google-beta` declares `google_*`
15
+ // resources, so its block names the provider the resource types already name.
16
+ const PROVIDER_ALIASES = new Map([['google-beta', 'google']]);
17
+ const providerOfType = (type) => {
18
+ const separator = type.indexOf('_');
19
+ return separator === -1 ? type : type.slice(0, separator);
20
+ };
21
+ // A matched header always carries its groups; the fallback satisfies the type checker, not a case.
22
+ const groupOf = (found, group) => found[group] ?? '';
23
+ const KIND_GROUP = 1;
24
+ const TYPE_GROUP = 2;
25
+ const NAME_GROUP = 1;
26
+ const resourceDeclarations = (code) => [...code.matchAll(RESOURCE_HEADER)].map((found) => {
27
+ const type = groupOf(found, TYPE_GROUP);
28
+ return {
29
+ line: lineOf(code, found.index),
30
+ provider: providerOfType(type),
31
+ subject: type,
32
+ kind: groupOf(found, KIND_GROUP) === 'data' ? 'data' : 'resource',
33
+ };
34
+ });
35
+ const providerBlocks = (code) => [...code.matchAll(PROVIDER_HEADER)].map((found) => {
36
+ const name = groupOf(found, NAME_GROUP);
37
+ return {
38
+ line: lineOf(code, found.index),
39
+ provider: PROVIDER_ALIASES.get(name) ?? name,
40
+ subject: `provider "${name}"`,
41
+ kind: 'provider',
42
+ };
43
+ });
44
+ /**
45
+ * Every line of a source that names a provider.
46
+ * @param source the file's text, as tracked.
47
+ * @returns the declarations, ordered by the line they sit on.
48
+ */
49
+ export const providerDeclarationsIn = (source) => {
50
+ const code = stripComments(source);
51
+ return [...resourceDeclarations(code), ...providerBlocks(code)].toSorted((left, right) => left.line - right.line);
52
+ };
53
+ /**
54
+ * The providers a set of declarations names.
55
+ * @param declarations declarations from one or many sources.
56
+ * @returns each provider once, ordered so a report reads the same on every machine.
57
+ */
58
+ export const providersOf = (declarations) => [
59
+ ...new Set(declarations.map((declaration) => declaration.provider)),
60
+ ].toSorted((left, right) => left.localeCompare(right));
61
+ /**
62
+ * How many resources and data sources the declarations carry - what an analyzer has to judge. A
63
+ * provider block configures a provider and declares nothing it could check.
64
+ * @param declarations declarations from one or many sources.
65
+ * @returns the count of resource and data declarations.
66
+ */
67
+ export const resourceCountOf = (declarations) => declarations.filter((declaration) => declaration.kind !== 'provider').length;
68
+ //# sourceMappingURL=terraform-providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terraform-providers.js","sourceRoot":"","sources":["../src/terraform-providers.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /** An analyzer ploaness runs in a container rather than installing from the registry. */
2
- export type ContainerTool = 'gitleaks' | 'hadolint' | 'actionlint' | 'shellcheck';
2
+ export type ContainerTool = 'gitleaks' | 'hadolint' | 'actionlint' | 'shellcheck' | 'checkov';
3
3
  /** The exact image each containerised analyzer runs, by digest. */
4
4
  export declare const CONTAINER_IMAGES: Readonly<Record<ContainerTool, string>>;
5
5
  /** Matches a reference that names image bytes AND the release they are, and only such a reference. */
@@ -1 +1 @@
1
- {"version":3,"file":"toolchain-pins.d.ts","sourceRoot":"","sources":["../src/toolchain-pins.ts"],"names":[],"mappings":"AAGA,yFAAyF;AACzF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,CAAA;AAEjF,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAWpE,CAAA;AAED,sGAAsG;AACtG,eAAO,MAAM,aAAa,EAAE,MACsC,CAAA;AAElE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAG,SAAS,MAAM,EAOxF,CAAA;AAOL;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAG3E,CAAA;AAOD;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,gBAAgB,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAG/E,CAAA;AAMD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GACpC,WAAW,MAAM,GAAG,SAAS,EAC7B,UAAU,MAAM,GAAG,SAAS,KAC3B,SAAS,MAAM,EAajB,CAAA"}
1
+ {"version":3,"file":"toolchain-pins.d.ts","sourceRoot":"","sources":["../src/toolchain-pins.ts"],"names":[],"mappings":"AAGA,yFAAyF;AACzF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAA;AAE7F,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAmBpE,CAAA;AAED,sGAAsG;AACtG,eAAO,MAAM,aAAa,EAAE,MACsC,CAAA;AAElE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAG,SAAS,MAAM,EAOxF,CAAA;AAOL;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAG3E,CAAA;AAOD;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,gBAAgB,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAG/E,CAAA;AAMD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GACpC,WAAW,MAAM,GAAG,SAAS,EAC7B,UAAU,MAAM,GAAG,SAAS,KAC3B,SAAS,MAAM,EAajB,CAAA"}
@@ -5,9 +5,16 @@ export const CONTAINER_IMAGES = {
5
5
  gitleaks: 'zricethezav/gitleaks:v8.30.1@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f',
6
6
  hadolint: 'hadolint/hadolint:v2.15.1@sha256:32dac94127fd60b7b7e3fbfc65e1383b9b5e25c9bfd7b8536de7a539fe68a12d',
7
7
  actionlint: 'rhysd/actionlint:1.7.12@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667',
8
- // The standard makes a check a repository implements itself into its source code, and the ploaness
9
- // verification command is a shell script that no analyzer read.
8
+ // A project's operational scripts are code it runs against its own database and its own deployments,
9
+ // and the standard makes a check a repository implements itself into its source code. The `shell`
10
+ // gate reads both: a consumer's scripts, and the ones this verification command is written in.
10
11
  shellcheck: 'koalaman/shellcheck:v0.11.0@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d',
12
+ // The infrastructure gate enables named checks rather than a category, and a `--check` id that does
13
+ // not exist matches nothing and raises no error. The digest is what makes that audit durable: the
14
+ // catalogue cannot change underneath it, so the enabled ids stay the ones somebody confirmed with
15
+ // `--list`. The provider standings beside the catalogue rest on the same listing: which providers
16
+ // the image has no check for at all. Re-confirm both whenever this pin moves.
17
+ checkov: 'bridgecrew/checkov:3.3.17@sha256:41c4701c6a56d8952e5aba7a420f871c8b70b57da94eb4f142dcdf7295bb0be3',
11
18
  };
12
19
  /** Matches a reference that names image bytes AND the release they are, and only such a reference. */
13
20
  export const DIGEST_PINNED = /^[a-z0-9][a-z0-9._/-]*:[A-Za-z0-9][\w.-]*@sha256:[0-9a-f]{64}$/;
@@ -1 +1 @@
1
- {"version":3,"file":"toolchain-pins.js","sourceRoot":"","sources":["../src/toolchain-pins.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,wEAAwE;AAKxE,mEAAmE;AACnE,MAAM,CAAC,MAAM,gBAAgB,GAA4C;IACvE,QAAQ,EACN,sGAAsG;IACxG,QAAQ,EACN,mGAAmG;IACrG,UAAU,EACR,iGAAiG;IACnG,mGAAmG;IACnG,gEAAgE;IAChE,UAAU,EACR,qGAAqG;CACxG,CAAA;AAED,sGAAsG;AACtG,MAAM,CAAC,MAAM,aAAa,GACxB,gEAAgE,CAAA;AAElE;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAwC,EAAqB,EAAE,CAChG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;KACnB,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,CAA4B,EAAW,EAAE,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;KAC7F,GAAG,CACF,CAAC,CAAC,IAAI,EAAE,SAAS,CAA4B,EAAU,EAAE,CACvD,GAAG,IAAI,UAAU,SAAS,yDAAyD;IACnF,wCAAwC,CAC3C,CAAA;AAEL,0FAA0F;AAC1F,wGAAwG;AACxG,sGAAsG;AACtG,MAAM,aAAa,GAAW,OAAO,CAAA;AAErC;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,WAA+B,EAAsB,EAAE;IACtF,MAAM,KAAK,GAA2B,aAAa,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAA;IAC3E,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AACtD,CAAC,CAAA;AAED,oGAAoG;AACpG,iGAAiG;AACjG,0DAA0D;AAC1D,MAAM,cAAc,GAAW,4BAA4B,CAAA;AAE3D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,cAAkC,EAAsB,EAAE;IAC1F,MAAM,KAAK,GAA2B,cAAc,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,CAAA;IAC/E,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;AACnB,CAAC,CAAA;AAED,qGAAqG;AACrG,oGAAoG;AACpG,MAAM,eAAe,GAAW,qBAAqB,CAAA;AAErD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,SAA6B,EAC7B,QAA4B,EACT,EAAE;IACrB,MAAM,KAAK,GAA2B,eAAe,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAA;IAC3E,MAAM,OAAO,GAAuB,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;IAC9C,gGAAgG;IAChG,oGAAoG;IACpG,8FAA8F;IAC9F,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5E,OAAO,EAAE,CAAA;IACX,CAAC;IACD,OAAO;QACL,QAAQ,OAAO,mDAAmD,QAAQ,IAAI;YAC5E,0FAA0F;KAC7F,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"toolchain-pins.js","sourceRoot":"","sources":["../src/toolchain-pins.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,wEAAwE;AAKxE,mEAAmE;AACnE,MAAM,CAAC,MAAM,gBAAgB,GAA4C;IACvE,QAAQ,EACN,sGAAsG;IACxG,QAAQ,EACN,mGAAmG;IACrG,UAAU,EACR,iGAAiG;IACnG,qGAAqG;IACrG,kGAAkG;IAClG,+FAA+F;IAC/F,UAAU,EACR,qGAAqG;IACvG,oGAAoG;IACpG,kGAAkG;IAClG,kGAAkG;IAClG,kGAAkG;IAClG,8EAA8E;IAC9E,OAAO,EACL,mGAAmG;CACtG,CAAA;AAED,sGAAsG;AACtG,MAAM,CAAC,MAAM,aAAa,GACxB,gEAAgE,CAAA;AAElE;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAwC,EAAqB,EAAE,CAChG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;KACnB,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,CAA4B,EAAW,EAAE,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;KAC7F,GAAG,CACF,CAAC,CAAC,IAAI,EAAE,SAAS,CAA4B,EAAU,EAAE,CACvD,GAAG,IAAI,UAAU,SAAS,yDAAyD;IACnF,wCAAwC,CAC3C,CAAA;AAEL,0FAA0F;AAC1F,wGAAwG;AACxG,sGAAsG;AACtG,MAAM,aAAa,GAAW,OAAO,CAAA;AAErC;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,WAA+B,EAAsB,EAAE;IACtF,MAAM,KAAK,GAA2B,aAAa,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAA;IAC3E,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AACtD,CAAC,CAAA;AAED,oGAAoG;AACpG,iGAAiG;AACjG,0DAA0D;AAC1D,MAAM,cAAc,GAAW,4BAA4B,CAAA;AAE3D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,cAAkC,EAAsB,EAAE;IAC1F,MAAM,KAAK,GAA2B,cAAc,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,CAAA;IAC/E,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;AACnB,CAAC,CAAA;AAED,qGAAqG;AACrG,oGAAoG;AACpG,MAAM,eAAe,GAAW,qBAAqB,CAAA;AAErD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,SAA6B,EAC7B,QAA4B,EACT,EAAE;IACrB,MAAM,KAAK,GAA2B,eAAe,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAA;IAC3E,MAAM,OAAO,GAAuB,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;IAC9C,gGAAgG;IAChG,oGAAoG;IACpG,8FAA8F;IAC9F,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5E,OAAO,EAAE,CAAA;IACX,CAAC;IACD,OAAO;QACL,QAAQ,OAAO,mDAAmD,QAAQ,IAAI;YAC5E,0FAA0F;KAC7F,CAAA;AACH,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ploaness/governance",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Pure, unit-tested governance rules for ploaness. No filesystem, process, or network access.",
5
5
  "license": "MIT",
6
6
  "author": "Herman Ciechanowiec <herman@ciechanowiec.eu>",