@aws/cloudformation-validate 1.5.0-beta → 1.6.0-beta

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/README.md CHANGED
@@ -1,31 +1,46 @@
1
- # CloudFormation Validate WASM Bindings
1
+ # CloudFormation Validate for Node.js
2
2
 
3
- WASM bindings for [`cloudformation-validate`](https://github.com/aws-cloudformation/cloudformation-validate). Compiles
4
- the full validation pipeline
5
- template parser, schema validator, Rego engine, and CEL engine — into a single `.wasm` module for Node.js.
3
+ Validate AWS CloudFormation templates from JavaScript or TypeScript and catch schema violations, security risks, and
4
+ best-practice findings before deployment in your editor, build, or CI.
6
5
 
7
- All WASM objects must be explicitly freed via `.free()` to release memory.
6
+ - **Offline** all rules and resource schemas are bundled.
7
+ - **Fast** — sub-second validation per template.
8
8
 
9
- For a complete, runnable example, see
10
- [examples](https://github.com/aws-cloudformation/cloudformation-validate/tree/main/src/bindings-wasm/examples).
9
+ ## Installation
11
10
 
12
- ## Engine
11
+ Available on [npm](https://www.npmjs.com/package/@aws/cloudformation-validate) as `@aws/cloudformation-validate`.
13
12
 
14
- `RegoEngine` and `CelEngine` both implement the `Engine` interface. They are interchangeable — both produce identical
15
- diagnostics for the same template and config.
13
+ ```bash
14
+ npm install @aws/cloudformation-validate
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ Engines, models, and validators hold off-heap memory — call `.free()` when done with each object:
16
20
 
17
21
  ```typescript
18
- import { RegoEngine, CelEngine, TemplateFile } from "@aws/cloudformation-validate";
22
+ import { RegoEngine, TemplateFile } from "@aws/cloudformation-validate";
19
23
 
20
24
  const engine = new RegoEngine();
21
- const report = engine.validateStandard(new TemplateFile("template.yaml"));
22
-
23
- for (const d of report.diagnostics) {
24
- console.log(`[${d.severity}] ${d.ruleId}: ${d.message}`);
25
+ try {
26
+ const report = engine.validateStandard(new TemplateFile("template.yaml"));
27
+ for (const d of report.diagnostics) {
28
+ console.log(`[${d.severity}] ${d.ruleId}: ${d.message}`);
29
+ }
30
+ } finally {
31
+ engine.free();
25
32
  }
26
- engine.free();
27
33
  ```
28
34
 
35
+ Each diagnostic identifies the rule, severity, affected resource and property, and source location — see
36
+ [StandardDiagnostic](#standarddiagnostic). A complete, runnable project is in
37
+ [examples](https://github.com/aws-cloudformation/cloudformation-validate/tree/main/src/bindings-wasm/examples).
38
+
39
+ ## Engine
40
+
41
+ `RegoEngine` and `CelEngine` both implement the `Engine` interface and are interchangeable — they produce identical
42
+ diagnostics for the same template and config.
43
+
29
44
  ### `Engine` interface
30
45
 
31
46
  | Method | Returns | Description |
@@ -34,7 +49,7 @@ engine.free();
34
49
  | `validateDetailed(template, config?)` | `DetailedReport` | Validates and returns diagnostics with documentation URLs, rule descriptions, phase tags, and `ViolationContext` |
35
50
  | `listRules()` | `RuleInfo[]` | Returns metadata for every built-in and loaded custom rule |
36
51
  | `engineName()` | `string` | `"rego"` or `"cel"` |
37
- | `free()` | `void` | Releases WASM memory |
52
+ | `free()` | `void` | Releases the engine's off-heap memory |
38
53
 
39
54
  ### `EngineConfig`
40
55
 
@@ -42,8 +57,14 @@ Passed to the constructor. All fields optional, default to empty arrays.
42
57
 
43
58
  ```typescript
44
59
  interface EngineConfig {
45
- customRules?: ExternalRuleSource[]; // engine-native rules (Rego for RegoEngine, CEL for CelEngine)
46
- guardRules?: ExternalRuleSource[]; // CloudFormation Guard DSL rules — translated internally by each engine
60
+ customRules?: RuleSource[]; // engine-native rules (Rego for RegoEngine, CEL for CelEngine)
61
+ guardRules?: RuleSource[]; // CloudFormation Guard DSL rules — translated internally by each engine
62
+ }
63
+
64
+ type RuleSource = ExternalRuleSource | RuleFile;
65
+
66
+ class RuleFile {
67
+ constructor(path: string); // rule file read from disk; the path becomes the rule source name
47
68
  }
48
69
 
49
70
  interface ExternalRuleSource {
@@ -52,6 +73,16 @@ interface ExternalRuleSource {
52
73
  }
53
74
  ```
54
75
 
76
+ Pass a `RuleFile` to load a rule from disk — the same pattern as `TemplateFile` for templates — or an
77
+ `ExternalRuleSource` when you already have the rule text in memory. The two can be mixed freely:
78
+
79
+ ```typescript
80
+ const engine = new CelEngine({
81
+ customRules: [new RuleFile("rules/s3_encryption.json")],
82
+ guardRules: [new RuleFile("rules/compliance.guard")],
83
+ });
84
+ ```
85
+
55
86
  ## ValidateConfig
56
87
 
57
88
  Controls filtering, severity, parameter overrides, and behavior. All fields optional — omitting the config or passing
@@ -90,13 +121,15 @@ interface RuleFilterConfig {
90
121
  idRanges?: IdRange[]; // numeric ranges, e.g. { prefix: "E", start: 3000, end: 3099 }
91
122
  idPatterns?: string[]; // regex patterns matched against rule IDs
92
123
  resourceIds?: ResourceIdFilter[]; // a rule (or every rule) on a logical resource ID
124
+ logicalIds?: LogicalIdFilter[]; // a rule (or every rule) on a named template entity
93
125
  resourceTypes?: ResourceTypeFilter[]; // a rule (or every rule) on a resource type
94
126
  services?: ServiceFilter[]; // a rule (or every rule) on a service, e.g. "AWS::AutoScaling"
95
127
  }
96
128
 
97
- // resourceIds / resourceTypes / services each carry an optional ruleId:
129
+ // resourceIds / logicalIds / resourceTypes / services each carry an optional ruleId:
98
130
  // set it to scope the filter to one rule, or omit it for every rule on the target.
99
131
  interface ResourceIdFilter { ruleId?: string; resourceId: string; }
132
+ interface LogicalIdFilter { ruleId?: string; logicalId: string; entityType?: EntityType; }
100
133
  interface ResourceTypeFilter { ruleId?: string; resourceType: string; }
101
134
  interface ServiceFilter { ruleId?: string; service: string; }
102
135
  ```
@@ -104,6 +137,11 @@ interface ServiceFilter { ruleId?: string; service: string; }
104
137
  The `service` is matched verbatim against the `service-provider::service-name` prefix of the resource type — its first
105
138
  two `::`-delimited segments (e.g. `AWS::AutoScaling` in `AWS::AutoScaling::LaunchConfiguration`).
106
139
 
140
+ The `resourceIds` dimension matches only diagnostics attributed to a resource; `logicalIds` additionally matches
141
+ diagnostics on parameters, outputs, mappings, conditions, and template rules (for resource diagnostics the two carry
142
+ the same value). An optional `entityType` scopes a `LogicalIdFilter` to entities of one type, so `MyThing` as a
143
+ `"Parameter"` is matched without touching a same-named entity of another type.
144
+
107
145
  ### PseudoParameterOverrides
108
146
 
109
147
  Override CloudFormation pseudo-parameters used during intrinsic function resolution. All fields optional — when
@@ -185,7 +223,7 @@ interface StandardReport {
185
223
  ```
186
224
 
187
225
  `DetailedReport` has the same structure but its diagnostics include additional fields: `documentationUrl`,
188
- `ruleDescription`, `phase` (`PARSE` | `SCHEMA` | `LINT`), `section`, and `context` (`ViolationContext` with
226
+ `ruleDescription`, `phase` (`PARSE` | `SCHEMA` | `LINT`), and `context` (`ViolationContext` with
189
227
  `actualValue`, `expectedConstraint`, `resolutionSource`, etc.).
190
228
 
191
229
  ### StandardDiagnostic
@@ -196,9 +234,8 @@ interface StandardDiagnostic {
196
234
  severity: Severity; // "FATAL" | "ERROR" | "WARN" | "INFO" | "DEBUG"
197
235
  message: string;
198
236
  source: RuleOrigin; // "SCHEMA" | "CFN_LINT" | "ENGINE" | "CUSTOM" | "GUARD"
199
- resourceId?: string; // logical resource ID
200
- resourceType?: string; // e.g. "AWS::S3::Bucket"
201
- propertyPath?: string; // e.g. "Properties/BucketName"
237
+ entity?: Entity; // the named template entity the finding targets, if any
238
+ propertyPath?: string; // e.g. "Properties.BucketName", or section-absolute like "Parameters/MyParam/Type"
202
239
  suggestedFix?: string;
203
240
  category?: string;
204
241
  startLine?: number;
@@ -208,4 +245,15 @@ interface StandardDiagnostic {
208
245
  relatedResources?: RelatedResource[];
209
246
  conditionScenario?: Record<string, boolean>; // condition truth assignment that triggers this diagnostic
210
247
  }
248
+
249
+ // The named template entity a diagnostic is attributed to. The entity type is the
250
+ // singular form of the top-level template section the entity is declared in.
251
+ interface Entity {
252
+ logicalId: string; // logical ID as declared in the template
253
+ entityType: EntityType;
254
+ resourceType?: string; // CloudFormation type, when the entity is a resource whose type is known
255
+ }
256
+
257
+ type EntityType = "Resource" | "Parameter" | "Output" | "Mapping" | "Metadata"
258
+ | "Rule" | "Condition" | "Transform" | "FormatVersion" | "Description";
211
259
  ```
@@ -191,6 +191,18 @@ export interface DiagnosticResource {
191
191
  * Property paths containing ${...} text that is not an Fn::Sub variable and is left as a literal.
192
192
  */
193
193
  unsubstitutedVariables: PathVariable[];
194
+ /**
195
+ * Fn::Sub map keys not referenced in the template string, each with its path and the unused key name.
196
+ */
197
+ unusedSubKeys: PathVariable[];
198
+ /**
199
+ * Property values that are a raw pseudo-parameter string instead of using Ref, each with its path and the pseudo-parameter name.
200
+ */
201
+ rawPseudoParams: PathVariable[];
202
+ /**
203
+ * Property paths containing a {{resolve:secretsmanager:...}} dynamic reference.
204
+ */
205
+ secretsmanagerRefPaths: string[];
194
206
  /**
195
207
  * References whose target does not resolve to any resource or parameter, each with its path and unresolved target.
196
208
  */
@@ -235,6 +247,7 @@ export interface RuleFilterConfig {
235
247
  */
236
248
  idPatterns?: string[];
237
249
  resourceIds?: ResourceIdFilter[];
250
+ logicalIds?: LogicalIdFilter[];
238
251
  resourceTypes?: ResourceTypeFilter[];
239
252
  services?: ServiceFilter[];
240
253
  }
@@ -386,6 +399,25 @@ export interface ResolvedResource {
386
399
  diagnostics: ResourceDiagnostics;
387
400
  }
388
401
 
402
+ /**
403
+ * A top-level CloudFormation template section, as documented in the template
404
+ * anatomy (<https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-anatomy.html>).
405
+ *
406
+ * This is the canonical, single definition of the section names — section
407
+ * constants in other crates derive from it.
408
+ */
409
+ export type TopLevelSection =
410
+ | 'Resources'
411
+ | 'Parameters'
412
+ | 'Outputs'
413
+ | 'Mappings'
414
+ | 'Metadata'
415
+ | 'Rules'
416
+ | 'Conditions'
417
+ | 'Transform'
418
+ | 'FormatVersion'
419
+ | 'Description';
420
+
389
421
  /**
390
422
  * Advanced introspection view of a parsed CloudFormation template: its reference graph,
391
423
  * conditions, and resolved resources and outputs.
@@ -661,6 +693,18 @@ export interface ResourceDiagnostics {
661
693
  * Occurrences of ${...} placeholders outside an Fn::Sub that will not be substituted; each pairs the property path with the placeholder text.
662
694
  */
663
695
  unsubstitutedVariables: PathValuePair[];
696
+ /**
697
+ * Fn::Sub map keys not referenced in the template string; each pairs the property path with the unused key name.
698
+ */
699
+ unusedSubKeys: PathValuePair[];
700
+ /**
701
+ * Property values that are a raw pseudo-parameter string (e.g. \"AWS::Region\") instead of using Ref.
702
+ */
703
+ rawPseudoParams: PathValuePair[];
704
+ /**
705
+ * Property paths containing a {{resolve:secretsmanager:...}} dynamic reference.
706
+ */
707
+ secretsmanagerRefPaths: string[];
664
708
  /**
665
709
  * References whose target is not a defined resource, parameter, or pseudo parameter; each pairs the property path with the missing target name.
666
710
  */
@@ -705,6 +749,19 @@ export interface ResourceIdFilter {
705
749
  resourceId: string;
706
750
  }
707
751
 
752
+ /**
753
+ * Suppress a rule for a specific named template entity — a resource,
754
+ * parameter, output, mapping, condition, or template rule — identified by its
755
+ * logical ID. An absent `rule_id` scopes the filter to every rule on that
756
+ * entity; an absent `entity_type` scopes it to entities of every type with
757
+ * that logical ID.
758
+ */
759
+ export interface LogicalIdFilter {
760
+ ruleId?: string;
761
+ logicalId: string;
762
+ entityType?: EntityType;
763
+ }
764
+
708
765
  /**
709
766
  * Suppress a rule for a specific resource type. An absent `rule_id` scopes the
710
767
  * filter to every rule on that type.
@@ -729,6 +786,43 @@ export interface ServiceFilter {
729
786
  service: string;
730
787
  }
731
788
 
789
+ /**
790
+ * The kind of template entity a diagnostic targets — the singular form of the
791
+ * top-level section the entity is declared in. Every documented section has a
792
+ * variant; the ones whose children are addressable by logical ID (resources,
793
+ * parameters, outputs, mappings, conditions, rules, and metadata keys) are
794
+ * the ones diagnostics attribute findings to today.
795
+ */
796
+ export type EntityType =
797
+ | 'Resource'
798
+ | 'Parameter'
799
+ | 'Output'
800
+ | 'Mapping'
801
+ | 'Metadata'
802
+ | 'Rule'
803
+ | 'Condition'
804
+ | 'Transform'
805
+ | 'FormatVersion'
806
+ | 'Description';
807
+
808
+ /**
809
+ * The named template entity a diagnostic is attributed to, when it targets
810
+ * one. The entity type is the singular form of the top-level template
811
+ * section the entity is declared in.
812
+ */
813
+ export interface Entity {
814
+ /**
815
+ * Logical ID of the entity as declared in the template.
816
+ */
817
+ logicalId: string;
818
+ entityType: EntityType;
819
+ /**
820
+ * CloudFormation resource type, when the entity is a resource whose type
821
+ * is known.
822
+ */
823
+ resourceType?: string;
824
+ }
825
+
732
826
  /**
733
827
  * The template resource a diagnostic is attributed to, when it targets one.
734
828
  */
@@ -810,7 +904,7 @@ export interface PseudoParameterOverrides {
810
904
  export type RuleOrigin = 'SCHEMA' | 'CFN_LINT' | 'ENGINE' | 'CUSTOM' | 'GUARD';
811
905
 
812
906
  /**
813
- *r" A single validation finding with its resource and source location flattened into individual fields.
907
+ *r" A single validation finding with its source location flattened into individual fields.
814
908
  */
815
909
  export interface StandardDiagnostic {
816
910
  /**
@@ -824,10 +918,9 @@ export interface StandardDiagnostic {
824
918
  */
825
919
  source: RuleOrigin;
826
920
  /**
827
- * Logical ID of the resource this finding targets, if any.
921
+ * The named template entity this finding targets — a resource, parameter, output, mapping, condition, or template rule — if any.
828
922
  */
829
- resourceId?: string;
830
- resourceType?: string;
923
+ entity?: Entity;
831
924
  /**
832
925
  * Path to the offending property within the resource, such as \'Properties.Name\'.
833
926
  */
@@ -863,10 +956,9 @@ export interface DetailedDiagnostic {
863
956
  */
864
957
  source: RuleOrigin;
865
958
  /**
866
- * Logical ID of the resource this finding targets, if any.
959
+ * The named template entity this finding targets — a resource, parameter, output, mapping, condition, or template rule — if any.
867
960
  */
868
- resourceId?: string;
869
- resourceType?: string;
961
+ entity?: Entity;
870
962
  /**
871
963
  * Path to the offending property within the resource, such as \'Properties.Name\'.
872
964
  */
@@ -888,10 +980,6 @@ export interface DetailedDiagnostic {
888
980
  documentationUrl?: string;
889
981
  ruleDescription?: string;
890
982
  phase?: Phase;
891
- /**
892
- *r" Top-level template section the finding falls under, such as 'Resources' or 'Parameters'.
893
- */
894
- section?: string;
895
983
  context?: ViolationContext;
896
984
  }
897
985
 
Binary file
package/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  DetailedReport,
3
3
  DiagnosticModel,
4
- EngineConfig,
4
+ ExternalRuleSource,
5
5
  ParameterInfo,
6
6
  ResolvedOutput,
7
7
  ResolvedResource,
@@ -17,11 +17,14 @@ export type {
17
17
  RuleOrigin,
18
18
  IdRange,
19
19
  ResourceIdFilter,
20
+ LogicalIdFilter,
20
21
  ResourceTypeFilter,
21
22
  ServiceFilter,
22
23
  RuleFilterConfig,
23
24
  RuleInfo,
24
25
  SourceSpan,
26
+ Entity,
27
+ EntityType,
25
28
  ResourceRef,
26
29
  RelatedResource,
27
30
  ViolationContext,
@@ -34,7 +37,6 @@ export type {
34
37
  StandardReport,
35
38
  DetailedReport,
36
39
  PseudoParameterOverrides,
37
- EngineConfig,
38
40
  ValidateConfig,
39
41
  ExternalRuleSource,
40
42
  ResolvedValue,
@@ -87,6 +89,18 @@ export declare class TemplateFile {
87
89
  constructor(path: string);
88
90
  readBytes(): Uint8Array;
89
91
  }
92
+ export declare class RuleFile {
93
+ readonly path: string;
94
+ constructor(path: string);
95
+ readContent(): string;
96
+ }
97
+ export type RuleSource = ExternalRuleSource | RuleFile;
98
+ export interface EngineConfig {
99
+ /** Engine-native rules (Rego for RegoEngine, CEL for CelEngine). */
100
+ customRules?: RuleSource[];
101
+ /** CloudFormation Guard DSL rules, usable with either engine. */
102
+ guardRules?: RuleSource[];
103
+ }
90
104
  export declare class TemplateModel {
91
105
  private readonly inner;
92
106
  constructor(template: TemplateFile);
package/index.js CHANGED
@@ -4,6 +4,7 @@ exports.CelEngine =
4
4
  exports.RegoEngine =
5
5
  exports.SchemaValidator =
6
6
  exports.TemplateModel =
7
+ exports.RuleFile =
7
8
  exports.TemplateFile =
8
9
  void 0;
9
10
  exports.version = version;
@@ -18,6 +19,26 @@ class TemplateFile {
18
19
  }
19
20
  }
20
21
  exports.TemplateFile = TemplateFile;
22
+ class RuleFile {
23
+ constructor(path) {
24
+ this.path = path;
25
+ }
26
+ readContent() {
27
+ return (0, fs_1.readFileSync)(this.path, 'utf8');
28
+ }
29
+ }
30
+ exports.RuleFile = RuleFile;
31
+ function toExternalRuleSources(sources) {
32
+ return (sources ?? []).map((source) =>
33
+ source instanceof RuleFile ? { name: source.path, content: source.readContent() } : source,
34
+ );
35
+ }
36
+ function toWasmEngineConfig(config) {
37
+ return {
38
+ customRules: toExternalRuleSources(config?.customRules),
39
+ guardRules: toExternalRuleSources(config?.guardRules),
40
+ };
41
+ }
21
42
  class TemplateModel {
22
43
  constructor(template) {
23
44
  this.inner = bridge.WasmSemanticModel.parse(template.readBytes());
@@ -80,7 +101,7 @@ exports.SchemaValidator = SchemaValidator;
80
101
  function createEngineClass(WasmClass) {
81
102
  return class {
82
103
  constructor(config) {
83
- this.inner = new WasmClass(config ?? {});
104
+ this.inner = new WasmClass(toWasmEngineConfig(config));
84
105
  }
85
106
  validateStandard(template, config) {
86
107
  return this.inner.validateStandard(template.readBytes(), config ?? {}, template.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws/cloudformation-validate",
3
- "version": "1.5.0-beta",
3
+ "version": "1.6.0-beta",
4
4
  "description": "AWS CloudFormation Validate",
5
5
  "keywords": [
6
6
  "aws",
@@ -19,8 +19,7 @@
19
19
  },
20
20
  "license": "Apache-2.0",
21
21
  "engines": {
22
- "node": "^22.15.0",
23
- "npm": ">=10.5.0"
22
+ "node": ">=20.0.0"
24
23
  },
25
24
  "repository": {
26
25
  "type": "git",