@mitre/inspec-objects 2.0.0 → 2.0.2

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.
@@ -4,17 +4,14 @@ on:
4
4
  types: [labeled]
5
5
  permissions:
6
6
  pull-requests: write
7
- contents: write
8
7
 
9
8
  jobs:
10
9
  approve:
11
10
  name: Auto-approve dependabot PRs
12
11
  if: github.event.pull_request.user.login == 'dependabot[bot]' && contains(github.event.pull_request.labels.*.name, 'dependencies')
13
- runs-on: ubuntu-latest
12
+ runs-on: ubuntu-24.04
14
13
  steps:
15
- - uses: hmarr/auto-approve-action@v3
16
- with:
17
- github-token: "${{ secrets.GITHUB_TOKEN }}"
14
+ - uses: hmarr/auto-approve-action@v4
18
15
  - name: Enable auto-merge for Dependabot PRs
19
16
  run: gh pr merge --auto --merge "$PR_URL"
20
17
  env:
@@ -8,9 +8,9 @@ on:
8
8
 
9
9
  jobs:
10
10
  update_draft_release:
11
- runs-on: ubuntu-latest
11
+ runs-on: ubuntu-24.04
12
12
  steps:
13
13
  # Drafts your next Release notes as Pull Requests are merged into "master"
14
- - uses: toolmantim/release-drafter@v5.2.0
14
+ - uses: release-drafter/release-drafter@v6
15
15
  env:
16
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -7,15 +7,15 @@ on:
7
7
  jobs:
8
8
  build:
9
9
  name: Run TS-InSpec-Objects E2E Tests
10
- runs-on: ubuntu-20.04
10
+ runs-on: ubuntu-24.04
11
11
 
12
12
  steps:
13
- - uses: actions/checkout@v3
13
+ - uses: actions/checkout@v4
14
14
 
15
15
  - name: Setup Node.js
16
- uses: actions/setup-node@v3
16
+ uses: actions/setup-node@v4
17
17
  with:
18
- node-version: 18
18
+ node-version: 22
19
19
  cache: 'npm'
20
20
 
21
21
  - name: Install dependencies
@@ -7,16 +7,16 @@ on:
7
7
  jobs:
8
8
  build:
9
9
  name: Lint TS-InSpec-Objects
10
- runs-on: ubuntu-20.04
10
+ runs-on: ubuntu-24.04
11
11
 
12
12
  steps:
13
13
  - name: Checkout code
14
- uses: actions/checkout@v3
14
+ uses: actions/checkout@v4
15
15
 
16
16
  - name: Setup Node.js
17
- uses: actions/setup-node@v3
17
+ uses: actions/setup-node@v4
18
18
  with:
19
- node-version: 18
19
+ node-version: 22
20
20
  cache: 'npm'
21
21
 
22
22
  - name: Install project dependencies
@@ -6,31 +6,28 @@ on:
6
6
 
7
7
  jobs:
8
8
  build-deploy:
9
- runs-on: ubuntu-20.04
9
+ runs-on: ubuntu-24.04
10
10
  steps:
11
- - uses: actions/checkout@v3
11
+ - uses: actions/checkout@v4
12
12
 
13
13
  - name: Setup node
14
- uses: actions/setup-node@v3
14
+ uses: actions/setup-node@v4
15
15
  with:
16
- node-version: 18
17
- registry-url: https://npm.pkg.github.com/
16
+ node-version: 22
17
+ registry-url: 'https://npm.pkg.github.com'
18
18
  scope: '@mitre'
19
19
 
20
20
  - name: Build the NPM Package
21
21
  run: |
22
22
  npm install
23
+ rm -rf test
23
24
  npm run build
25
+
24
26
  - name: Pack all items that are published
25
27
  run: npm pack
26
- # Setup .npmrc file to publish to GitHub Package Registry
27
- - uses: actions/setup-node@v1
28
- with:
29
- registry-url: 'https://npm.pkg.github.com'
30
- scope: '@mitre'
31
28
 
32
29
  # Publish inspec-objects to GitHub Package Registry
33
30
  - name: Publish inspec-objects to GPR
34
31
  run: npm publish mitre-inspec-objects-*.tgz
35
32
  env:
36
- NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -6,14 +6,14 @@ on:
6
6
 
7
7
  jobs:
8
8
  build-deploy:
9
- runs-on: ubuntu-20.04
9
+ runs-on: ubuntu-24.04
10
10
  steps:
11
- - uses: actions/checkout@v3
11
+ - uses: actions/checkout@v4
12
12
 
13
13
  - name: setup node
14
- uses: actions/setup-node@v3
14
+ uses: actions/setup-node@v4
15
15
  with:
16
- node-version: 18
16
+ node-version: 22
17
17
  registry-url: 'https://registry.npmjs.org'
18
18
 
19
19
  - name: Install project dependencies
package/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # ts-inspec-objects
2
- Typescript objects for InSpec profiles
2
+ Typescript objects for InSpec Profiles
3
3
 
4
- This repository contains the source code that facilitates the writing of InSpec profiles (for use in things like stub generation and delta comparisons) more consistent with `Chef Cookstyle` formatting for ease of use when comparing with new changes from delta and when generating InSpec stubs that match a standard format.
4
+ This repository contains the source code that facilitates the writing of InSpec Profiles (for use in things like stub generation and delta comparisons) more consistent with `Chef Cookstyle` formatting for ease of use when comparing with new changes from delta and when generating InSpec stubs that match a standard format.
5
5
 
6
6
  For more information about Chef Cookstyle see:
7
7
  - [chef/cookstyle on GitHub](https://github.com/chef/cookstyle)
8
8
  - [Chef Cookstyle on Chef documents page](https://docs.chef.io/workstation/cookstyle/)
9
9
 
10
+ The `ts-inspec-objects` provides the capability of updating InSpect Profiles and creating stub Profiles based on XCCDF Benchmarks. This is accomplished by providing `Profile` and `Control` classes and supporting methods (functions).
11
+
10
12
  ## How to Use
11
13
  The process code maintained in this repository generates a `npm` executable that is published to the `npm registry` as [mitre-inspec-objects](https://www.npmjs.com/package/@mitre/inspec-objects).
12
14
 
@@ -18,7 +20,7 @@ The package is a CommonJS-based npm written in TypeScript
18
20
 
19
21
  ## Parsing Process
20
22
 
21
- When using this library to parse `InSpec profiles` or `xccdf files` for the purposes of generating InSpec profiles, the general workflow is as follows:
23
+ When using this library to parse `InSpec Profiles` or `xccdf files` for the purposes of generating InSpec profiles, the general workflow is as follows:
22
24
  ```
23
25
  - The input is processed, read into a typescript object
24
26
  - Operated on with any required action / logic
@@ -39,16 +41,46 @@ Here are some formatting choices that are being made.
39
41
  2. Tag keywords are not quoted (ex: tag severity: 'medium')
40
42
  3. Each control file ends with a newline
41
43
 
42
- ### Workflow graphical representation
44
+ ---
45
+ ### XCCDF Workflow Process
46
+ Processes an XCCDF (Extensible Configuration Checklist Description Format) XML string based on the `Rule Identifier` provided and converts it into a Profile object.
47
+ If and OVAL definition is provided it retrives the oval objects and associated states and the Controls description check text is update with the content.
48
+ <div align="center">
49
+ <img src="images/ts-inspec-objects-xccdf-workflow-process.png" alt="Typescript Objects XCCDF Conversion Workflow Process" title="Typescript Objects XCCDF Conversion Workflow Process">
50
+ </div>
51
+
52
+ ### OVAL Workflow Process
53
+ Processes an OVAL (Open Vulnerability and Assessment Language) XML string and converts it into a JSON object.
54
+ The process extracts definitions and their associated criteria references and resolved values.
55
+ The process executes the following steps:
56
+ 1. Converts the OVAL XML string into a JSON object.
57
+ 2. Iterates through the OVAL definitions and extracts each definition.
58
+ 3. For each definition, extracts criteria references and resolves the associated objects and states.
59
+ 4. Logs warnings if any objects or states cannot be found.
60
+ <div align="center">
61
+ <img src="images/ts-inspec-objects-oval-workflow-process.png" alt="Typescript Objects Oval Conversion Workflow Process" title="Typescript Objects Oval Conversion Workflow Process">
62
+ </div>
63
+
64
+ ### InSpec Profile Workflow Process
65
+ Process a JSON string representing an InSpec profile, converts it, and processes it to return a `Profile` object.
66
+ It handles different versions of the InSpec JSON format and sorts the controls by their ID.
67
+ <div align="center">
68
+ <img src="images/ts-inspec-objects-inspec-profile-workflow-process.png" alt="Typescript Objects InSpec Profile Workflow Process" title="Typescript Objects InSpec Profile Workflow Process">
69
+ </div>
70
+
71
+ ### Update Profile Using XCCDF Workflow Process
72
+ Updates a Profile with new metadata from and XCCDF, based on the `Rule Indetefier` and logs the process.
43
73
  <div align="center">
44
- <img src="images/ts-inspec-objects.jpg" alt="Typescript Objects Generation Process" title="Typescript Objects Generation Process">
74
+ <img src="images/ts-inspec-objects-updateProfileUsingXccdf-workflow.png" alt="Typescript Objects Update Profile Using XCCDF Workflow Process" title="Typescript Objects Update Profile Using XCCDF Workflow Process">
45
75
  </div>
46
76
 
47
- ### Delta and Stub Process
77
+ ### Update Control Workflow Process
78
+ Updates a given control object with the provided partial control and logs the process.
48
79
  <div align="center">
49
- <img src="images/Delta_Process.jpg" alt="Delta and Stub Generation Process" title="Delta and Stub Generation Process">
80
+ <img src="images/ts-inspec-objects-process-updateControl-workflow.png" alt="Typescript Objects Update Control Workflow Process" title="Typescript Objects Update Control Workflow Process">
50
81
  </div>
51
82
 
83
+ ---
52
84
  ## Development Environment Configuration
53
85
  ### Installation
54
86
  To install the project, clone the repository and install the dependencies:
@@ -99,7 +99,7 @@ function processOVAL(oval) {
99
99
  if (!oval) {
100
100
  return undefined;
101
101
  }
102
- const parsed = (0, xccdf_1.convertEncodedXmlIntoJson)(oval);
102
+ const parsed = (0, xccdf_1.convertEncodedXmlIntoJson)(oval, 'withArrayNoEntitiesOption');
103
103
  const extractedDefinitions = {};
104
104
  for (const ovalDefinitions of parsed.oval_definitions) {
105
105
  for (const definitionList of ovalDefinitions.definitions) {
@@ -27,8 +27,13 @@ export type InputTextLang = {
27
27
  '@_lang': string;
28
28
  };
29
29
  /**
30
- * Processes an XCCDF XML string and converts it into a Profile object.
31
- * Note: Moved the newline removal to diff library rather than here.
30
+ * Processes an XCCDF (Extensible Configuration Checklist Description Format) XML
31
+ * string and converts it into a Profile object.
32
+ * NOTE: We are using the fast xml parser (FXP) V4 which requires to specify
33
+ * which Whether a single tag should be parsed as an array or an object,
34
+ * it can't be decided by FXP. We process every tag as an array, this is
35
+ * the reason there are numerous tag test, were array index zero [0] is
36
+ * tested.
32
37
  *
33
38
  * @param xml - The XCCDF XML string to process.
34
39
  * @param removeNewlines - A flag indicating whether to remove newlines from the processed data.
@@ -55,21 +55,33 @@ function extractAllComplexChecks(complexCheck) {
55
55
  return complexChecks;
56
56
  }
57
57
  /**
58
- * Ensures that the input is decoded as an XML string value.
58
+ * Ensures that the input is decoded to a string value.
59
59
  *
60
- * @param input - The input value which can be either a string or an array of
61
- * InputTextLang objects.
62
- * @param defaultValue - The default string value to return if the input is
63
- * not a string.
64
- * @returns The decoded XML string value if the input is a string, otherwise the
65
- * value from the first element of the input array or the default value.
60
+ * This function takes an input which can be either a string or an array of `InputTextLang` objects.
61
+ * If the input is a string, it returns the input as is.
62
+ * If the input is an array, it attempts to retrieve the `#text` property from the first element of the array.
63
+ * If the input is neither a string nor an array, it attempts to retrieve the `#text` property from the input.
64
+ * If the `#text` property is not found, it returns the provided default value.
65
+ *
66
+ * @param input - The input value which can be a string or an array of `InputTextLang` objects.
67
+ * @param defaultValue - The default value to return if the `#text` property is not found.
68
+ * @returns The decoded string value or the default value.
66
69
  */
67
70
  function ensureDecodedXMLStringValue(input, defaultValue) {
68
- return lodash_1.default.isString(input) ? input : lodash_1.default.get(input, '[0].#text', defaultValue);
71
+ return lodash_1.default.isString(input)
72
+ ? input
73
+ : lodash_1.default.isArray(input)
74
+ ? lodash_1.default.get(input, '[0].#text', defaultValue)
75
+ : lodash_1.default.get(input, '#text', defaultValue);
69
76
  }
70
77
  /**
71
- * Processes an XCCDF XML string and converts it into a Profile object.
72
- * Note: Moved the newline removal to diff library rather than here.
78
+ * Processes an XCCDF (Extensible Configuration Checklist Description Format) XML
79
+ * string and converts it into a Profile object.
80
+ * NOTE: We are using the fast xml parser (FXP) V4 which requires to specify
81
+ * which Whether a single tag should be parsed as an array or an object,
82
+ * it can't be decided by FXP. We process every tag as an array, this is
83
+ * the reason there are numerous tag test, were array index zero [0] is
84
+ * tested.
73
85
  *
74
86
  * @param xml - The XCCDF XML string to process.
75
87
  * @param removeNewlines - A flag indicating whether to remove newlines from the processed data.
@@ -84,14 +96,40 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
84
96
  if (parsedXML.Benchmark === undefined) {
85
97
  throw new Error('Could not process the XCCDF file, check the input to make sure this is a properly formatted XCCDF file.');
86
98
  }
99
+ // Extracts all rules from the given benchmark groups.
87
100
  const rules = extractAllRules(parsedXML.Benchmark[0].Group);
101
+ // Variable used to store the profile data.
102
+ // The name is the benchmark Id, title and summary are from benchmark.
88
103
  const profile = new profile_1.default({
89
- name: parsedXML.Benchmark[0]['@_id'],
90
- title: parsedXML.Benchmark[0].title[0]['#text'],
91
- summary: parsedXML.Benchmark[0].description[0]['#text']
104
+ //name: parsedXML.Benchmark[0]['@_id'],
105
+ // title: (parsedXML.Benchmark[0].title[0] as FrontMatter)['#text'],
106
+ // summary: (parsedXML.Benchmark[0].description[0] as RationaleElement)['#text']
107
+ name: Array.isArray(parsedXML.Benchmark[0]['@_id'])
108
+ ? parsedXML.Benchmark[0]['@_id'].map(n => n['@_id']).join(' ') === ''
109
+ ? parsedXML.Benchmark[0]['@_id'].map(n => n).join(' ')
110
+ : parsedXML.Benchmark[0]['@_id'].join(' ')
111
+ : parsedXML.Benchmark[0]['@_id'],
112
+ title: Array.isArray(parsedXML.Benchmark[0].title)
113
+ ? parsedXML.Benchmark[0].title.map(t => t['#text']).join(' ') === ''
114
+ ? parsedXML.Benchmark[0].title.map(t => t).join(' ')
115
+ : parsedXML.Benchmark[0].title.map(t => t['#text']).join(' ')
116
+ : parsedXML.Benchmark[0].title,
117
+ summary: Array.isArray(parsedXML.Benchmark[0].description)
118
+ ? parsedXML.Benchmark[0].description.map(d => d['#text']).join(' ') === ''
119
+ ? parsedXML.Benchmark[0].description.map(d => d['p'] || '').join(' ') === ''
120
+ ? parsedXML.Benchmark[0].description.map(d => d).join(' ')
121
+ : parsedXML.Benchmark[0].description.map(d => d['p'] || '').join(' ')
122
+ : parsedXML.Benchmark[0].description.map(d => d['#text']).join(' ')
123
+ : parsedXML.Benchmark[0].description
92
124
  });
125
+ // Process each rule, extracting the necessary
126
+ // data and save it to the profile variable.
93
127
  rules.forEach(rule => {
94
128
  var _a, _b, _c;
129
+ // The description tag contains the following tags:
130
+ // "FalsePositives", "FalseNegatives", "Documentable", "Mitigations",
131
+ // "SeverityOverrideGuidance", "PotentialImpacts", "ThirdPartyTools",
132
+ // "MitigationControl", "Responsibility", "IAControls"
95
133
  let extractedDescription;
96
134
  if (typeof rule.description === 'object') {
97
135
  if (Array.isArray(rule.description) && lodash_1.default.get(rule, "description[0]['#text']")) {
@@ -105,6 +143,10 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
105
143
  if (Array.isArray(lodash_1.default.get(rule.description, '[0].p'))) {
106
144
  const joinedDescriptions = lodash_1.default.get(rule.description, '[0].p');
107
145
  extractedDescription = (0, pretty_1.default)(joinedDescriptions.join('\n\n'));
146
+ extractedDescription = (0, xccdf_1.removeHtmlTags)(extractedDescription).replace('\n', ' ');
147
+ }
148
+ else if (Array.isArray(rule.description)) {
149
+ extractedDescription = (0, xccdf_1.convertEncodedHTMLIntoJson)(rule.description[0]);
108
150
  }
109
151
  else {
110
152
  extractedDescription = JSON.stringify(rule.description);
@@ -115,10 +157,12 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
115
157
  else {
116
158
  extractedDescription = (0, xccdf_1.convertEncodedHTMLIntoJson)(rule.description);
117
159
  }
160
+ // Create a new control object and populate it with the necessary data.
118
161
  const control = new control_1.default();
162
+ // Update the control Id with the appropriate value based on the rule id.
119
163
  switch (useRuleId) {
120
164
  case 'group':
121
- control.id = rule.group['@_id'];
165
+ control.id = rule.group['@_id'].toString();
122
166
  break;
123
167
  case 'rule':
124
168
  if (rule['@_id'][0].toLowerCase().startsWith('sv')) {
@@ -129,27 +173,51 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
129
173
  }
130
174
  break;
131
175
  case 'version':
132
- control.id = rule.version;
176
+ if (rule.version !== undefined) {
177
+ (lodash_1.default.isArray(rule.version))
178
+ ? control.id = rule.version[0]
179
+ : control.id = rule.version;
180
+ }
181
+ else {
182
+ throw new Error('The rule type "version" did not provide an identification (Id) value');
183
+ }
133
184
  break;
134
- case 'cis':
135
- // eslint-disable-next-line no-case-declarations
185
+ case 'cis': {
186
+ // Regex explained
187
+ // \d:
188
+ // matches a single digit (0-9), the required starting point of the match.
189
+ // (\d?):
190
+ // matches an optional digit, there are three of these in sequence
191
+ // (.\d(\d?)(\d?)(\d?))?:
192
+ // matches an optional group that starts with a period (.) followed
193
+ // by one digit and up to three additional optional digits
194
+ // The pattern is repeated four times to match between zero and four
195
+ // groups of a period followed by one required digit and up to three
196
+ // additional optional digits. The pattern matches:
197
+ // 1, 123, 1.2, 1.234, 1.2.3.4.5, or 1.23.456.7.89
136
198
  const controlIdRegex = /\d(\d?)(\d?)(\d?)(.\d(\d?)(\d?)(\d?))?(.\d(\d?)(\d?)(\d?))?(.\d(\d?)(\d?)(\d?))?(.\d(\d?)(\d?)(\d?))?/g;
137
- // eslint-disable-next-line no-case-declarations
138
199
  const controlIdMatch = controlIdRegex.exec(rule['@_id']);
139
200
  if (controlIdMatch) {
140
201
  control.id = controlIdMatch[0];
141
202
  }
142
203
  else {
143
- throw new Error(`Could not parse control ID from rule ID: ${rule['@_id']}. Expecting something in this example format: 'xccdf_org.cisecurity.benchmarks_rule_1.1.11_Rule_title_summary`);
204
+ throw new Error(`Could not parse control ID from rule ID: ${rule['@_id']}. Expecting something in this example format: xccdf_org.cisecurity.benchmarks_rule_1.1.11_Rule_title_summary`);
144
205
  }
145
206
  break;
207
+ }
146
208
  default:
147
- throw new Error('useRuleId must be one of "group", "rule", or "version"');
209
+ throw new Error('useRuleId must be one of "group", "rule", "version" for STIG benchmarks, or "cis" for CIS benchmarks');
148
210
  }
149
211
  if (!(lodash_1.default.isArray(rule.title) && rule.title.length === 1)) {
150
212
  throw new Error('Rule title is not an array of length 1. Investigate if the file is in the proper format.');
151
213
  }
152
- control.title = (0, xccdf_1.removeXMLSpecialCharacters)(rule['@_severity'] ? ensureDecodedXMLStringValue(rule.title[0], 'undefined title') : `[[[MISSING SEVERITY FROM BENCHMARK]]] ${ensureDecodedXMLStringValue(rule.title[0], 'undefined title')}`);
214
+ // Update the control title with the rule.tile content if a rule severity
215
+ // exists after removing any special characters, otherwise set the control
216
+ // title to [[[MISSING SEVERITY FROM BENCHMARK]]], undefined title.
217
+ control.title = (0, xccdf_1.removeXMLSpecialCharacters)(rule['@_severity'] || rule['@_weight']
218
+ ? ensureDecodedXMLStringValue(rule.title[0], 'undefined title')
219
+ : `[[[MISSING SEVERITY or WEIGHT FROM BENCHMARK]]] ${ensureDecodedXMLStringValue(rule.title[0], 'undefined title')}`);
220
+ // Update the control description (desc) with the extracted description content
153
221
  if (typeof extractedDescription === 'object' && !Array.isArray(extractedDescription)) {
154
222
  control.desc = ((_a = extractedDescription.VulnDiscussion) === null || _a === void 0 ? void 0 : _a.split('Satisfies: ')[0]) || '';
155
223
  }
@@ -162,10 +230,13 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
162
230
  else {
163
231
  logger.warn(`Invalid value for extracted description: ${extractedDescription}`);
164
232
  }
233
+ // Update the control impact with the severity value from the rule,
234
+ // default to medium (0.5) if not found.
165
235
  control.impact = (0, xccdf_1.severityStringToImpact)(rule['@_severity'] || 'medium');
166
236
  if (!control.descs || Array.isArray(control.descs)) {
167
237
  control.descs = {};
168
238
  }
239
+ // Update the control descriptions (descs) check with the check text from the rule,
169
240
  if (rule.check) {
170
241
  if (rule.check.some((ruleValue) => 'check-content' in ruleValue)) {
171
242
  control.descs.check = (0, xccdf_1.removeXMLSpecialCharacters)(rule.check ? rule.check[0]['check-content'][0] : 'Missing description');
@@ -240,20 +311,22 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
240
311
  control.descs.check = checkTexts.join('\n');
241
312
  }
242
313
  }
314
+ // Update the control descriptions (descs) fix with content from the rule
315
+ // fixtest, if not found, defaults to "Missing fix text"
243
316
  if (lodash_1.default.get(rule.fixtext, '[0]["#text"]')) {
244
317
  control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)(rule.fixtext[0]['#text']);
245
318
  }
246
319
  else if (typeof rule.fixtext === 'undefined') {
247
320
  if (rule.fix && rule.fix[0]) {
248
- control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)(rule.fix[0]['#text'] || 'Missing fix text');
321
+ control.descs.fix = (0, xccdf_1.removeHtmlTags)(rule.fix[0]['#text'] || 'Missing fix text');
249
322
  }
250
323
  }
251
324
  else if (typeof rule.fixtext[0] === 'string') {
252
- control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)(rule.fixtext[0]);
325
+ control.descs.fix = (0, xccdf_1.removeHtmlTags)(rule.fixtext[0]);
253
326
  }
254
327
  else if (typeof rule.fixtext[0] === 'object') {
255
328
  if (Array.isArray(rule.fixtext[0])) {
256
- control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)((0, pretty_1.default)((0, xccdf_1.convertJsonIntoXML)(rule.fixtext[0].map((fixtext) => {
329
+ control.descs.fix = (0, xccdf_1.removeHtmlTags)((0, pretty_1.default)((0, xccdf_1.convertJsonIntoXML)(rule.fixtext[0].map((fixtext) => {
257
330
  if (fixtext.div) {
258
331
  return fixtext.div;
259
332
  }
@@ -263,21 +336,25 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
263
336
  }))));
264
337
  }
265
338
  else {
266
- control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)((0, pretty_1.default)((0, xccdf_1.convertJsonIntoXML)(rule.fixtext)));
339
+ control.descs.fix = (0, xccdf_1.removeHtmlTags)((0, xccdf_1.removeXMLSpecialCharacters)((0, pretty_1.default)((0, xccdf_1.convertJsonIntoXML)(rule.fixtext)))).replace('\n', ' ').trim();
267
340
  }
268
341
  }
269
342
  else {
270
343
  control.descs.fix = 'Missing fix text';
271
344
  }
345
+ // Update the control tags base on corresponding rule tags.
272
346
  control.tags.severity = (0, xccdf_1.impactNumberToSeverityString)((0, xccdf_1.severityStringToImpact)(rule['@_severity'] || 'medium'));
273
347
  control.tags.gid = rule.group['@_id'],
274
348
  control.tags.rid = rule['@_id'];
275
349
  control.tags.stig_id = rule['version'];
276
- if (typeof rule.group.title[0] === 'string') {
277
- control.tags.gtitle = (0, xccdf_1.removeXMLSpecialCharacters)(rule.group.title[0]);
350
+ if (typeof rule.group.title === 'string') {
351
+ control.tags.gtitle = (0, xccdf_1.removeXMLSpecialCharacters)(rule.group.title);
278
352
  }
279
353
  else {
280
- control.tags.gtitle = (0, xccdf_1.removeXMLSpecialCharacters)(lodash_1.default.get(rule.group, 'title[0].#text', 'undefined title'));
354
+ const gtitle = lodash_1.default.get(rule.group, 'title[0].#text', 'undefined title') === 'undefined title'
355
+ ? lodash_1.default.get(rule.group, 'title[0]', 'undefined title')
356
+ : lodash_1.default.get(rule.group, 'title[0].#text', 'undefined title');
357
+ control.tags.gtitle = typeof gtitle === 'string' ? gtitle : gtitle['#text'] || 'undefined title';
281
358
  }
282
359
  if (rule['fix'] && rule['fix'].length > 0) {
283
360
  control.tags.fix_id = rule['fix'][0]['@_id'];
@@ -285,11 +362,20 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
285
362
  if (rule['rationale']) {
286
363
  control.tags.rationale = rule['rationale'][0]['#text'];
287
364
  }
365
+ // The description tag contains the following tags as well:
366
+ // "FalsePositives", "FalseNegatives", "Documentable", "Mitigations",
367
+ // "SeverityOverrideGuidance", "PotentialImpacts", "ThirdPartyTools",
368
+ // "MitigationControl", "Responsibility", "IAControls"
288
369
  if (typeof extractedDescription === 'object') {
289
- control.tags.satisfies = ((_b = extractedDescription.VulnDiscussion) === null || _b === void 0 ? void 0 : _b.includes('Satisfies: ')) && extractedDescription.VulnDiscussion.split('Satisfies: ').length >= 1 ? extractedDescription.VulnDiscussion.split('Satisfies: ')[1].split(',').map(satisfaction => satisfaction.trim()) : undefined;
370
+ control.tags.satisfies =
371
+ ((_b = extractedDescription.VulnDiscussion) === null || _b === void 0 ? void 0 : _b.includes('Satisfies: ')) && extractedDescription.VulnDiscussion.split('Satisfies: ').length >= 1
372
+ ? extractedDescription.VulnDiscussion.split('Satisfies: ')[1].split(',').map(satisfaction => satisfaction.trim())
373
+ : undefined;
290
374
  control.tags.false_negatives = extractedDescription.FalseNegatives || undefined;
291
375
  control.tags.false_positives = extractedDescription.FalsePositives || undefined;
292
- control.tags.documentable = typeof extractedDescription.Documentable === 'boolean' ? extractedDescription.Documentable : undefined;
376
+ control.tags.documentable = typeof extractedDescription.Documentable === 'boolean'
377
+ ? extractedDescription.Documentable
378
+ : undefined;
293
379
  control.tags.mitigations = extractedDescription.Mitigations || undefined;
294
380
  control.tags.severity_override_guidance = extractedDescription.SeverityOverrideGuidance || undefined;
295
381
  control.tags.potential_impacts = extractedDescription.PotentialImpacts || undefined;
@@ -299,11 +385,15 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
299
385
  control.tags.responsibility = extractedDescription.Responsibility || undefined;
300
386
  control.tags.ia_controls = extractedDescription.IAControls || undefined;
301
387
  }
388
+ // Ensure that tags inside the tags array are not an array
302
389
  control.tags = lodash_1.default.mapValues(lodash_1.default.omitBy(control.tags, (value) => value === undefined), (value) => {
303
390
  if (value && Array.isArray(value)) {
304
391
  if (Array.isArray(value[0])) {
305
392
  return (0, xccdf_1.removeXMLSpecialCharacters)(value[0][0]);
306
393
  }
394
+ else if (value.length > 1) {
395
+ return value;
396
+ }
307
397
  else {
308
398
  return (0, xccdf_1.removeXMLSpecialCharacters)(value[0]);
309
399
  }
@@ -315,7 +405,7 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
315
405
  return value;
316
406
  }
317
407
  });
318
- // Get all identifiers from the rule
408
+ // Get all identifiers from the rule; cci, nist, and legacy
319
409
  if (rule.ident) {
320
410
  rule.ident.forEach((identifier) => {
321
411
  var _a, _b, _c;
@@ -342,8 +432,9 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
342
432
  }
343
433
  });
344
434
  }
435
+ // Update control references with content from the benchmark rule object
345
436
  (_c = rule.reference) === null || _c === void 0 ? void 0 : _c.forEach((reference) => {
346
- var _a, _b, _c, _d;
437
+ var _a, _b, _c, _d, _e;
347
438
  if (lodash_1.default.get(reference, '@_href') === '') {
348
439
  (_a = control.refs) === null || _a === void 0 ? void 0 : _a.push(lodash_1.default.get(reference, '#text', 'undefined href'));
349
440
  }
@@ -368,7 +459,13 @@ function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
368
459
  }
369
460
  else {
370
461
  if ('title' in reference) {
371
- (_d = control.refs) === null || _d === void 0 ? void 0 : _d.push(lodash_1.default.get(reference, 'title'));
462
+ const title = lodash_1.default.get(reference, 'title');
463
+ if (Array.isArray(title)) {
464
+ (_d = control.refs) === null || _d === void 0 ? void 0 : _d.push(title[0]);
465
+ }
466
+ else {
467
+ (_e = control.refs) === null || _e === void 0 ? void 0 : _e.push(lodash_1.default.get(reference, 'title'));
468
+ }
372
469
  }
373
470
  }
374
471
  // Add the reference to the control tags when separated by §
@@ -151,7 +151,7 @@ function diffProfile(fromProfile, toProfile, logger) {
151
151
  logger.error(`Unable to find existing control ${diffValue[1]}`);
152
152
  }
153
153
  }
154
- else if (diffValue[0] === '+' && !changedControlIds.includes(diffValue[1].toString().toLowerCase()) && diffValue[1]) {
154
+ else if (diffValue[0] === '+' && !changedControlIds.includes(diffValue[1].toLowerCase()) && diffValue[1]) {
155
155
  logger.info(JSON.stringify(diffValue));
156
156
  logger.info(JSON.stringify(changedControlIds));
157
157
  profileDiff.addedControlIDs.push(diffValue[1]);
@@ -74,4 +74,14 @@ export declare function updateControlDescribeBlock(from: Control, update: Partia
74
74
  * @throws Will throw an error if a new control is added but the control data is not available.
75
75
  */
76
76
  export declare function updateProfile(from: Profile, using: Profile, logger: winston.Logger): Omit<UpdatedProfileReturn, 'markdown'>;
77
+ /**
78
+ * Update a Profile with with new metadata from a XCCDF benchmark
79
+ *
80
+ * @param from - A Profile object
81
+ * @param using - An XCCDF in string format (XML)
82
+ * @param id - Specifies the rule ID format to use ('group', 'rule', 'version', or 'cis').
83
+ * @param logger - A winston logger instance for logging debug information.
84
+ * @param ovalDefinitions - Optional OVAL definitions to use for resolving values.
85
+ * @returns The Updated Profile (profile, the diff between from and using, and the markdown)
86
+ */
77
87
  export declare function updateProfileUsingXCCDF(from: Profile, using: string, id: 'group' | 'rule' | 'version' | 'cis', logger: winston.Logger, ovalDefinitions?: Record<string, OvalDefinitionValue>): UpdatedProfileReturn;
@@ -285,7 +285,7 @@ function getExistingDescribeFromControl(control) {
285
285
  function findUpdatedControlByAllIdentifiers(existingControl, updatedControls) {
286
286
  // Try to match based on IDs
287
287
  let updatedControl = updatedControls.find((updatedControl) => {
288
- return updatedControl.id[0].toLowerCase() === existingControl.id[0].toLowerCase();
288
+ return updatedControl.id.toLowerCase() === existingControl.id.toLowerCase();
289
289
  });
290
290
  if (updatedControl) {
291
291
  return updatedControl;
@@ -377,9 +377,19 @@ function updateProfile(from, using, logger) {
377
377
  diff,
378
378
  };
379
379
  }
380
+ /**
381
+ * Update a Profile with with new metadata from a XCCDF benchmark
382
+ *
383
+ * @param from - A Profile object
384
+ * @param using - An XCCDF in string format (XML)
385
+ * @param id - Specifies the rule ID format to use ('group', 'rule', 'version', or 'cis').
386
+ * @param logger - A winston logger instance for logging debug information.
387
+ * @param ovalDefinitions - Optional OVAL definitions to use for resolving values.
388
+ * @returns The Updated Profile (profile, the diff between from and using, and the markdown)
389
+ */
380
390
  function updateProfileUsingXCCDF(from, using, id, logger, ovalDefinitions) {
381
391
  logger.info(`Updating profile ${from.name} with control IDs type: ${id}`);
382
- // Parse the XCCDF benchmark and convert it into a Profile
392
+ // Parse the XCCDF benchmark and convert it into a Profile object
383
393
  logger.debug('Loading XCCDF File');
384
394
  const xccdfProfile = (0, xccdf_1.processXCCDF)(using, false, id, ovalDefinitions);
385
395
  logger.debug('Loaded XCCDF File');
@@ -1,23 +1,55 @@
1
1
  import { DecodedDescription } from '../types/xccdf';
2
2
  /**
3
- * Converts an encoded XML string into a JSON object.
3
+ * Converts an encoded XML string into a JSON object using specified
4
+ * parsing options.
4
5
  *
5
- * @param encodedXml - The encoded XML string to be converted.
6
+ * @param encodedXml - The encoded XML string to be converted.
7
+ * @param xmlParserOption - The parsing option to be used. Defaults to
8
+ * 'withArrayOption'.
9
+ * Possible values are:
10
+ * - 'withArrayOption': Parses XML with array option enabled.
11
+ * - 'withArrayNoEntitiesOption': Parses XML with array option
12
+ * enabled and processes entities.
13
+ * - Any other value: Parses XML without array option.
6
14
  * @returns The JSON representation of the XML string.
7
15
  *
8
16
  * @remarks
9
17
  * This function uses the `fast-xml-parser` library to parse the XML string.
10
18
  * The parser options are configured to:
11
- * - Not ignore attributes.
19
+ * - Prevent the parser from converting XML entities (converting &lt into <)
20
+ * - Ignore attributes, allow or disallows attributes to be parsed
12
21
  * - Remove namespace prefixes.
13
22
  * - Prefix attribute names with '@_'.
14
- * - Stop parsing at 'div' and 'p' nodes.
15
- * - Treat all nodes as arrays.
23
+ * - Stop parsing 'div' and 'p' tags.
24
+ * - Treat all nodes as arrays or not
25
+ *
26
+ * Options being used for the XML parser (V4) are:
27
+ * - processEntities: true or false (based on xmlParserOption)
28
+ * - ignoreAttributes: false (allow attributes to be parsed)
29
+ * - removeNSPrefix: true (remove namespace prefixes)
30
+ * - attributeNamePrefix: '@_' (prefix all attribute names with @_)
31
+ * - stopNodes: ["*.pre", "*.p"]
32
+ * - isArray(): true or false (based on xmlParserOption)
33
+ *
34
+ * NOTE: The isArray can specify what tags to always convert into an array, we
35
+ * do not specify specific fields as it could break parsing if future
36
+ * fields are added, we parse all fields as an array.
16
37
  *
17
38
  * For more details on the parser options, see the documentation for the v4 or v5 version of the library:
18
39
  * {@link https://github.com/NaturalIntelligence/fast-xml-parser/tree/master/docs/v4}
19
40
  */
20
- export declare function convertEncodedXmlIntoJson(encodedXml: string): any;
41
+ /**
42
+ * Converts an encoded XML string into a JSON object using specified parsing options.
43
+ *
44
+ * @param encodedXml - The encoded XML string to be converted.
45
+ * @param xmlParserOption - The parsing option to be used. Defaults to 'withArrayOption'.
46
+ * Possible values are:
47
+ * - 'withArrayOption': Parses XML with array option enabled.
48
+ * - 'withArrayNoEntitiesOption': Parses XML with array option enabled and processes entities.
49
+ * - Any other value: Parses XML without array option.
50
+ * @returns The JSON object resulting from the XML parsing.
51
+ */
52
+ export declare function convertEncodedXmlIntoJson(encodedXml: string, xmlParserOption?: string): any;
21
53
  /**
22
54
  * Converts a JSON object into an XML string.
23
55
  *
@@ -35,6 +67,20 @@ export declare function convertJsonIntoXML(data: any): string;
35
67
  * @returns The decoded string with XML special characters removed.
36
68
  */
37
69
  export declare function removeXMLSpecialCharacters(str: string): string;
70
+ /**
71
+ * Removes all of the HTML tags and leaves only the text content.
72
+ *
73
+ * @param input - The string from which HTML tags should be removed.
74
+ * @returns A new string with all HTML tags removed.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const str = '<div>Hello <b>World</b>!</div>';
79
+ * const stripped = removeHtmlTags(str);
80
+ * console.log(stripped); // Output: "Hello World!"
81
+ * ```
82
+ */
83
+ export declare function removeHtmlTags(input: string): string;
38
84
  /**
39
85
  * Converts a severity string to a numerical impact value.
40
86
  *
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.convertEncodedXmlIntoJson = convertEncodedXmlIntoJson;
4
4
  exports.convertJsonIntoXML = convertJsonIntoXML;
5
5
  exports.removeXMLSpecialCharacters = removeXMLSpecialCharacters;
6
+ exports.removeHtmlTags = removeHtmlTags;
6
7
  exports.severityStringToImpact = severityStringToImpact;
7
8
  exports.impactNumberToSeverityString = impactNumberToSeverityString;
8
9
  exports.convertEncodedHTMLIntoJson = convertEncodedHTMLIntoJson;
@@ -13,33 +14,85 @@ const htmlparser = tslib_1.__importStar(require("htmlparser2"));
13
14
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
14
15
  const he_1 = tslib_1.__importDefault(require("he"));
15
16
  /**
16
- * Converts an encoded XML string into a JSON object.
17
+ * Converts an encoded XML string into a JSON object using specified
18
+ * parsing options.
17
19
  *
18
- * @param encodedXml - The encoded XML string to be converted.
20
+ * @param encodedXml - The encoded XML string to be converted.
21
+ * @param xmlParserOption - The parsing option to be used. Defaults to
22
+ * 'withArrayOption'.
23
+ * Possible values are:
24
+ * - 'withArrayOption': Parses XML with array option enabled.
25
+ * - 'withArrayNoEntitiesOption': Parses XML with array option
26
+ * enabled and processes entities.
27
+ * - Any other value: Parses XML without array option.
19
28
  * @returns The JSON representation of the XML string.
20
29
  *
21
30
  * @remarks
22
31
  * This function uses the `fast-xml-parser` library to parse the XML string.
23
32
  * The parser options are configured to:
24
- * - Not ignore attributes.
33
+ * - Prevent the parser from converting XML entities (converting &lt into <)
34
+ * - Ignore attributes, allow or disallows attributes to be parsed
25
35
  * - Remove namespace prefixes.
26
36
  * - Prefix attribute names with '@_'.
27
- * - Stop parsing at 'div' and 'p' nodes.
28
- * - Treat all nodes as arrays.
37
+ * - Stop parsing 'div' and 'p' tags.
38
+ * - Treat all nodes as arrays or not
39
+ *
40
+ * Options being used for the XML parser (V4) are:
41
+ * - processEntities: true or false (based on xmlParserOption)
42
+ * - ignoreAttributes: false (allow attributes to be parsed)
43
+ * - removeNSPrefix: true (remove namespace prefixes)
44
+ * - attributeNamePrefix: '@_' (prefix all attribute names with @_)
45
+ * - stopNodes: ["*.pre", "*.p"]
46
+ * - isArray(): true or false (based on xmlParserOption)
47
+ *
48
+ * NOTE: The isArray can specify what tags to always convert into an array, we
49
+ * do not specify specific fields as it could break parsing if future
50
+ * fields are added, we parse all fields as an array.
29
51
  *
30
52
  * For more details on the parser options, see the documentation for the v4 or v5 version of the library:
31
53
  * {@link https://github.com/NaturalIntelligence/fast-xml-parser/tree/master/docs/v4}
32
54
  */
33
- function convertEncodedXmlIntoJson(encodedXml) {
34
- const options = {
55
+ /**
56
+ * Converts an encoded XML string into a JSON object using specified parsing options.
57
+ *
58
+ * @param encodedXml - The encoded XML string to be converted.
59
+ * @param xmlParserOption - The parsing option to be used. Defaults to 'withArrayOption'.
60
+ * Possible values are:
61
+ * - 'withArrayOption': Parses XML with array option enabled.
62
+ * - 'withArrayNoEntitiesOption': Parses XML with array option enabled and processes entities.
63
+ * - Any other value: Parses XML without array option.
64
+ * @returns The JSON object resulting from the XML parsing.
65
+ */
66
+ function convertEncodedXmlIntoJson(encodedXml, xmlParserOption = 'withArrayOption') {
67
+ const withArrayOption = {
68
+ processEntities: false,
35
69
  ignoreAttributes: false,
36
70
  removeNSPrefix: true,
37
71
  attributeNamePrefix: '@_',
38
- stopNodes: ['div', 'p'],
39
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
40
- isArray: (_name, _jpath, _isLeafNode, _isAttribute) => true,
72
+ stopNodes: ['*.div', '*.p'],
73
+ isArray: () => true,
41
74
  };
42
- const parser = new fast_xml_parser_1.XMLParser(options);
75
+ const withArrayNoEntitiesOption = {
76
+ processEntities: true,
77
+ ignoreAttributes: false,
78
+ removeNSPrefix: true,
79
+ attributeNamePrefix: '@_',
80
+ stopNodes: ['*.div', '*.p'],
81
+ isArray: () => true,
82
+ };
83
+ const noArrayOption = {
84
+ processEntities: false,
85
+ ignoreAttributes: false,
86
+ removeNSPrefix: true,
87
+ attributeNamePrefix: '@_',
88
+ stopNodes: ['*.div', '*.p'],
89
+ isArray: () => false,
90
+ };
91
+ const parser = new fast_xml_parser_1.XMLParser(xmlParserOption === 'withArrayOption'
92
+ ? withArrayOption
93
+ : xmlParserOption === 'withArrayNoEntitiesOption'
94
+ ? withArrayNoEntitiesOption
95
+ : noArrayOption);
43
96
  return parser.parse(encodedXml);
44
97
  }
45
98
  /**
@@ -61,11 +114,35 @@ function convertJsonIntoXML(data) {
61
114
  * @returns The decoded string with XML special characters removed.
62
115
  */
63
116
  function removeXMLSpecialCharacters(str) {
64
- //console.log('Remove special characters: ', JSON.stringify(str, null, 2));
65
117
  const result = he_1.default.decode(str);
66
- //console.log('Result of he.decode: ', JSON.stringify(result));
67
118
  return result;
68
119
  }
120
+ /**
121
+ * Removes all of the HTML tags and leaves only the text content.
122
+ *
123
+ * @param input - The string from which HTML tags should be removed.
124
+ * @returns A new string with all HTML tags removed.
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const str = '<div>Hello <b>World</b>!</div>';
129
+ * const stripped = removeHtmlTags(str);
130
+ * console.log(stripped); // Output: "Hello World!"
131
+ * ```
132
+ */
133
+ function removeHtmlTags(input) {
134
+ // Regex explained
135
+ // <: Matches the opening angle bracket of an HTML tag
136
+ // /?: Matches zero or one forward slash /, to include closing tags
137
+ // [^>]: Matches any character except the > symbol
138
+ // +: Ensures preceding pattern ([^>]) matches one or more characters
139
+ // (>|$):
140
+ // > matches the closing angle bracket of an HTML tag.
141
+ // $ matches the end of the string. This ensures the regex can handle
142
+ // cases where the tag is incomplete or unclosed (e.g., <div)
143
+ // g: Global flag to find all matches in the input string
144
+ return input.replace(/<\/?[^>]+(>|$)/g, '');
145
+ }
69
146
  /**
70
147
  * Converts a severity string to a numerical impact value.
71
148
  *
@@ -166,7 +243,7 @@ function convertEncodedHTMLIntoJson(encodedHTML) {
166
243
  });
167
244
  htmlParser.write(patchedHTML);
168
245
  htmlParser.end();
169
- const converted = convertEncodedXmlIntoJson(xmlChunks.join(''));
246
+ const converted = convertEncodedXmlIntoJson(xmlChunks.join(''), 'noArrayOption');
170
247
  let cleaned = {};
171
248
  // Some STIGs have xml tags inside of the actual text which breaks processing,
172
249
  // e.g U_ASD_STIG_V5R1_Manual-xccdf.xml and all Oracle Database STIGs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mitre/inspec-objects",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Typescript objects for normalizing between InSpec profiles and XCCDF benchmarks",
5
5
  "main": "lib/index.js",
6
6
  "publishConfig": {
@@ -31,13 +31,13 @@
31
31
  "@types/lodash": "^4.14.178",
32
32
  "@types/mustache": "^4.2.0",
33
33
  "@types/pretty": "^2.0.1",
34
- "fast-xml-parser": "^4.5.1",
34
+ "fast-xml-parser": "^5.0.7",
35
35
  "flat": "5.0.2",
36
36
  "he": "^1.2.0",
37
37
  "htmlparser2": "^10.0.0",
38
38
  "inspecjs": "^2.6.6",
39
39
  "json-diff": "^1.0.6",
40
- "jstoxml": "^5.0.2",
40
+ "jstoxml": "^6.0.1",
41
41
  "lodash": "^4.17.21",
42
42
  "mustache": "^4.2.0",
43
43
  "pretty": "^2.0.0",
@@ -45,12 +45,12 @@
45
45
  "yaml": "^2.3.1"
46
46
  },
47
47
  "devDependencies": {
48
- "@types/jest": "^29.5.12",
49
- "@types/node": "^22.5.2",
48
+ "@types/jest": "^30.0.0",
49
+ "@types/node": "^24.0.0",
50
50
  "@typescript-eslint/eslint-plugin": "^6.4.1",
51
51
  "@typescript-eslint/parser": "^6.0.0",
52
52
  "eslint": "^8.30.0",
53
- "jest": "^29.7.0",
53
+ "jest": "^30.0.0",
54
54
  "ts-jest": "^29.1.1",
55
55
  "tslib": "^2.4.0",
56
56
  "typescript": "^5.2.2"