@mitre/inspec-objects 1.0.0 → 2.0.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.
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/auto-approve-and-merge.yml +22 -0
- package/.github/workflows/e2e-test.yml +1 -1
- package/.github/workflows/linter.yml +1 -2
- package/README.md +45 -4
- package/lib/index.d.ts +4 -2
- package/lib/index.js +5 -2
- package/lib/objects/control.d.ts +78 -0
- package/lib/objects/control.js +111 -19
- package/lib/objects/profile.d.ts +58 -0
- package/lib/objects/profile.js +60 -1
- package/lib/parsers/json.d.ts +32 -0
- package/lib/parsers/json.js +36 -5
- package/lib/parsers/oval.d.ts +28 -0
- package/lib/parsers/oval.js +45 -5
- package/lib/parsers/xccdf.d.ts +32 -1
- package/lib/parsers/xccdf.js +84 -38
- package/lib/utilities/diff.d.ts +42 -0
- package/lib/utilities/diff.js +51 -12
- package/lib/utilities/diffMarkdown.d.ts +13 -0
- package/lib/utilities/diffMarkdown.js +24 -12
- package/lib/utilities/global.d.ts +53 -0
- package/lib/utilities/global.js +92 -13
- package/lib/utilities/logging.d.ts +1 -1
- package/lib/utilities/logging.js +8 -5
- package/lib/utilities/update.d.ts +60 -1
- package/lib/utilities/update.js +132 -42
- package/lib/utilities/xccdf.d.ts +82 -1
- package/lib/utilities/xccdf.js +113 -22
- package/package.json +17 -17
package/lib/utilities/update.js
CHANGED
|
@@ -2,13 +2,39 @@
|
|
|
2
2
|
// Utilities to update a profile or control with new metadata
|
|
3
3
|
// The ultimate goal is to preserve all the metadata that is already there and only add what is new
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.
|
|
5
|
+
exports.getExistingDescribeFromControl = getExistingDescribeFromControl;
|
|
6
|
+
exports.findUpdatedControlByAllIdentifiers = findUpdatedControlByAllIdentifiers;
|
|
7
|
+
exports.updateControl = updateControl;
|
|
8
|
+
exports.updateControlDescribeBlock = updateControlDescribeBlock;
|
|
9
|
+
exports.updateProfile = updateProfile;
|
|
10
|
+
exports.updateProfileUsingXCCDF = updateProfileUsingXCCDF;
|
|
6
11
|
const tslib_1 = require("tslib");
|
|
7
12
|
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
8
13
|
const diff_1 = require("./diff");
|
|
14
|
+
const control_1 = tslib_1.__importDefault(require("../objects/control"));
|
|
9
15
|
const profile_1 = tslib_1.__importDefault(require("../objects/profile"));
|
|
10
16
|
const xccdf_1 = require("../parsers/xccdf");
|
|
11
17
|
const diffMarkdown_1 = require("./diffMarkdown");
|
|
18
|
+
/**
|
|
19
|
+
* Projects values from the source object onto the destination object,
|
|
20
|
+
* updating the destination object in place.
|
|
21
|
+
*
|
|
22
|
+
* @param dst - The destination object to be updated.
|
|
23
|
+
* @param src - The source object containing new values.
|
|
24
|
+
* @param currentPath - The current path being processed (used for nested objects).
|
|
25
|
+
* @returns The updated destination object.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* - If a value in the source object is an object and the corresponding value
|
|
29
|
+
* in the destination object is also an object, the function will recursively
|
|
30
|
+
* update the nested object.
|
|
31
|
+
* - If a value in the source object is a string, it will be trimmed before
|
|
32
|
+
* being set in the destination object.
|
|
33
|
+
* - If a value in the source object is a number, it will be directly set in
|
|
34
|
+
* the destination object.
|
|
35
|
+
* - If a value in the source object is an array, the function will merge it with
|
|
36
|
+
* the corresponding array in the destination object, ensuring unique values.
|
|
37
|
+
*/
|
|
12
38
|
function projectValuesOntoExistingObj(dst, src, currentPath = '') {
|
|
13
39
|
for (const updatedValue in src) {
|
|
14
40
|
const existingValue = lodash_1.default.get(dst, updatedValue);
|
|
@@ -31,28 +57,32 @@ function projectValuesOntoExistingObj(dst, src, currentPath = '') {
|
|
|
31
57
|
}
|
|
32
58
|
return dst;
|
|
33
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Returns an array containing two numerical indices (i.e., start and stop
|
|
62
|
+
* line numbers) for each string or multi-line comment, given raw text as
|
|
63
|
+
* an input parameter. The raw text is a string containing the entirety of an
|
|
64
|
+
* InSpec control.
|
|
65
|
+
*
|
|
66
|
+
* The function utilizes a pair of stacks (i.e., `stack`, `rangeStack`) to keep
|
|
67
|
+
* track of string delimiters and their associated line numbers, respectively.
|
|
68
|
+
*
|
|
69
|
+
* Combinations Handled:
|
|
70
|
+
* - Single quotes (')
|
|
71
|
+
* - Double quotes (")
|
|
72
|
+
* - Back ticks (`)
|
|
73
|
+
* - Mixed quotes ("`'")
|
|
74
|
+
* - Percent strings (%; keys: q, Q, r, i, I, w, W, x; delimiters: (), {},
|
|
75
|
+
* [], <>, most non-alphanumeric characters); (e.g., "%q()")
|
|
76
|
+
* - Percent literals (%; delimiters: (), {}, [], <>, most non-
|
|
77
|
+
* alphanumeric characters); (e.g., "%()")
|
|
78
|
+
* - Multi-line comments (e.g., =begin\nSome comment\n=end)
|
|
79
|
+
* - Variable delimiters (i.e., parenthesis: (); array: []; hash: {})
|
|
80
|
+
*
|
|
81
|
+
* @param text - The raw text containing the entirety of an InSpec control.
|
|
82
|
+
* @returns An array of arrays, each containing two numerical indices representing
|
|
83
|
+
* the start and stop line numbers for each string or multi-line comment.
|
|
84
|
+
*/
|
|
34
85
|
function getRangesForLines(text) {
|
|
35
|
-
/*
|
|
36
|
-
Returns an array containing two numerical indices (i.e., start and stop
|
|
37
|
-
line numbers) for each string or multi-line comment, given raw text as
|
|
38
|
-
an input parameter. The raw text is a string containing the entirety of an
|
|
39
|
-
InSpec control.
|
|
40
|
-
|
|
41
|
-
Algorithm utilizes a pair of stacks (i.e., `stack`, `rangeStack`) to keep
|
|
42
|
-
track of string delimiters and their associated line numbers, respectively.
|
|
43
|
-
|
|
44
|
-
Combinations Handled:
|
|
45
|
-
- Single quotes (')
|
|
46
|
-
- Double quotes (")
|
|
47
|
-
- Back ticks (`)
|
|
48
|
-
- Mixed quotes ("`'")
|
|
49
|
-
- Percent strings (%; keys: q, Q, r, i, I, w, W, x; delimiters: (), {},
|
|
50
|
-
[], <>, most non-alphanumeric characters); (e.g., "%q()")
|
|
51
|
-
- Percent literals (%; delimiters: (), {}, [], <>, most non-
|
|
52
|
-
alphanumeric characters); (e.g., "%()")
|
|
53
|
-
- Multi-line comments (e.g., =begin\nSome comment\n=end)
|
|
54
|
-
- Variable delimiters (i.e., parenthesis: (); array: []; hash: {})
|
|
55
|
-
*/
|
|
56
86
|
const stringDelimiters = { '(': ')', '{': '}', '[': ']', '<': '>' };
|
|
57
87
|
const variableDelimiters = { '(': ')', '{': '}', '[': ']' };
|
|
58
88
|
const quotes = '\'"`';
|
|
@@ -145,11 +175,17 @@ function getRangesForLines(text) {
|
|
|
145
175
|
}
|
|
146
176
|
return ranges;
|
|
147
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Joins lines of text from specified ranges and returns an array of strings.
|
|
180
|
+
* Each range is specified by a start and stop index, and the lines within
|
|
181
|
+
* those ranges are joined together.
|
|
182
|
+
*
|
|
183
|
+
* @param text - The raw text input to be processed.
|
|
184
|
+
* @param ranges - An array of ranges, where each range is a tuple containing
|
|
185
|
+
* the start and stop indices (inclusive) of the lines to be joined.
|
|
186
|
+
* @returns An array of strings, where lines within the specified ranges are joined.
|
|
187
|
+
*/
|
|
148
188
|
function joinMultiLineStringsFromRanges(text, ranges) {
|
|
149
|
-
/*
|
|
150
|
-
Returns an array of strings and joined strings at specified ranges, given
|
|
151
|
-
raw text as an input parameter.
|
|
152
|
-
*/
|
|
153
189
|
const originalLines = text.split('\n');
|
|
154
190
|
const joinedLines = [];
|
|
155
191
|
let i = 0;
|
|
@@ -170,11 +206,16 @@ function joinMultiLineStringsFromRanges(text, ranges) {
|
|
|
170
206
|
}
|
|
171
207
|
return joinedLines;
|
|
172
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Filters out ranges that span only a single line. There is,
|
|
211
|
+
* drops ranges with the same start and stop line numbers (i.e., strings
|
|
212
|
+
* that populate a single line)
|
|
213
|
+
*
|
|
214
|
+
* @param ranges - An array of ranges, where each range is represented by a
|
|
215
|
+
* tuple of start and stop line numbers.
|
|
216
|
+
* @returns An array of ranges that span multiple lines.
|
|
217
|
+
*/
|
|
173
218
|
function getMultiLineRanges(ranges) {
|
|
174
|
-
/*
|
|
175
|
-
Drops ranges with the same start and stop line numbers (i.e., strings
|
|
176
|
-
that populate a single line)
|
|
177
|
-
*/
|
|
178
219
|
const multiLineRanges = [];
|
|
179
220
|
for (const [start, stop] of ranges) {
|
|
180
221
|
if (start !== stop) {
|
|
@@ -183,10 +224,15 @@ function getMultiLineRanges(ranges) {
|
|
|
183
224
|
}
|
|
184
225
|
return multiLineRanges;
|
|
185
226
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
227
|
+
/**
|
|
228
|
+
* This is the most likely thing to break if you are getting code formatting issues.
|
|
229
|
+
*
|
|
230
|
+
* Extracts the `describe` block (what is actually run by inspec for validation)
|
|
231
|
+
* from an InSpec control object, collapsing multi-line strings.
|
|
232
|
+
*
|
|
233
|
+
* @param control - The InSpec control object containing the code to extract the `describe` block from.
|
|
234
|
+
* @returns The extracted `describe` block as a string, or an empty string if the control has no code.
|
|
235
|
+
*/
|
|
190
236
|
function getExistingDescribeFromControl(control) {
|
|
191
237
|
if (control.code) {
|
|
192
238
|
// Join multi-line strings in InSpec control.
|
|
@@ -214,17 +260,32 @@ function getExistingDescribeFromControl(control) {
|
|
|
214
260
|
}
|
|
215
261
|
}
|
|
216
262
|
// Return synthesized logic as describe block
|
|
217
|
-
|
|
263
|
+
const lastIndex = (describeBlock.lastIndexOf('end') === -1)
|
|
264
|
+
? describeBlock.lastIndexOf('end\r')
|
|
265
|
+
: describeBlock.lastIndexOf('end');
|
|
266
|
+
// Drop trailing ['end', '\n'] from Control block.
|
|
267
|
+
return describeBlock.slice(0, lastIndex).join('\n');
|
|
218
268
|
}
|
|
219
269
|
else {
|
|
220
270
|
return '';
|
|
221
271
|
}
|
|
222
272
|
}
|
|
223
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Finds an updated control from a list of updated controls by matching all possible identifiers.
|
|
275
|
+
*
|
|
276
|
+
* This function attempts to find a matching control in the `updatedControls` array by comparing
|
|
277
|
+
* the `id` of the `existingControl` with the `id` of each control in the `updatedControls` array.
|
|
278
|
+
* If no match is found based on `id`, it then tries to match based on legacy identifiers found
|
|
279
|
+
* in the `tags.legacy` property of each control in the `updatedControls` array.
|
|
280
|
+
*
|
|
281
|
+
* @param existingControl - The control to find a match for in the updated controls.
|
|
282
|
+
* @param updatedControls - An array of updated controls to search through.
|
|
283
|
+
* @returns The matching updated control if found, otherwise `undefined`.
|
|
284
|
+
*/
|
|
224
285
|
function findUpdatedControlByAllIdentifiers(existingControl, updatedControls) {
|
|
225
286
|
// Try to match based on IDs
|
|
226
287
|
let updatedControl = updatedControls.find((updatedControl) => {
|
|
227
|
-
return updatedControl.id.toLowerCase() === existingControl.id.toLowerCase();
|
|
288
|
+
return updatedControl.id[0].toLowerCase() === existingControl.id[0].toLowerCase();
|
|
228
289
|
});
|
|
229
290
|
if (updatedControl) {
|
|
230
291
|
return updatedControl;
|
|
@@ -242,7 +303,14 @@ function findUpdatedControlByAllIdentifiers(existingControl, updatedControls) {
|
|
|
242
303
|
}
|
|
243
304
|
return undefined;
|
|
244
305
|
}
|
|
245
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Updates a given control object with the provided partial update and logs the process.
|
|
308
|
+
*
|
|
309
|
+
* @param {Control} from - The original control object to be updated.
|
|
310
|
+
* @param {Partial<Control>} update - An object containing the properties to update in the original control.
|
|
311
|
+
* @param {winston.Logger} logger - A logger instance to log debug information.
|
|
312
|
+
* @returns {Control} - The updated control object.
|
|
313
|
+
*/
|
|
246
314
|
function updateControl(from, update, logger) {
|
|
247
315
|
const existingDescribeBlock = getExistingDescribeFromControl(from);
|
|
248
316
|
logger.debug(`Existing describe block for control ${from.id}: ${JSON.stringify(existingDescribeBlock)}`);
|
|
@@ -250,7 +318,31 @@ function updateControl(from, update, logger) {
|
|
|
250
318
|
projectedControl.describe = existingDescribeBlock;
|
|
251
319
|
return projectedControl;
|
|
252
320
|
}
|
|
253
|
-
|
|
321
|
+
/**
|
|
322
|
+
* Updates the describe block of a control with the describe block from another control.
|
|
323
|
+
*
|
|
324
|
+
* @param from - The control from which to get the existing describe block.
|
|
325
|
+
* @param update - The partial control data to update.
|
|
326
|
+
* @param logger - The logger instance to use for logging debug information.
|
|
327
|
+
* @returns The updated control with the describe block from the `from` control.
|
|
328
|
+
*/
|
|
329
|
+
function updateControlDescribeBlock(from, update, logger) {
|
|
330
|
+
const existingDescribeBlock = getExistingDescribeFromControl(from);
|
|
331
|
+
logger.debug(`Updating control ${update.id} with this describe block: ${JSON.stringify(existingDescribeBlock)}`);
|
|
332
|
+
const projectedControl = new control_1.default(update);
|
|
333
|
+
projectedControl.describe = existingDescribeBlock;
|
|
334
|
+
return projectedControl;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Updates a given profile with new metadata and controls from another profile.
|
|
338
|
+
*
|
|
339
|
+
* @param from - The original profile to be updated.
|
|
340
|
+
* @param using - The profile containing the new metadata and controls.
|
|
341
|
+
* @param logger - A winston logger instance for logging debug information.
|
|
342
|
+
* @returns An object containing the updated profile and the diff between the original and updated profiles, excluding markdown.
|
|
343
|
+
*
|
|
344
|
+
* @throws Will throw an error if a new control is added but the control data is not available.
|
|
345
|
+
*/
|
|
254
346
|
function updateProfile(from, using, logger) {
|
|
255
347
|
// Update the profile with the new metadata
|
|
256
348
|
const to = new profile_1.default(lodash_1.default.omit(from, 'controls'));
|
|
@@ -285,9 +377,8 @@ function updateProfile(from, using, logger) {
|
|
|
285
377
|
diff,
|
|
286
378
|
};
|
|
287
379
|
}
|
|
288
|
-
exports.updateProfile = updateProfile;
|
|
289
380
|
function updateProfileUsingXCCDF(from, using, id, logger, ovalDefinitions) {
|
|
290
|
-
logger.
|
|
381
|
+
logger.info(`Updating profile ${from.name} with control IDs type: ${id}`);
|
|
291
382
|
// Parse the XCCDF benchmark and convert it into a Profile
|
|
292
383
|
logger.debug('Loading XCCDF File');
|
|
293
384
|
const xccdfProfile = (0, xccdf_1.processXCCDF)(using, false, id, ovalDefinitions);
|
|
@@ -305,4 +396,3 @@ function updateProfileUsingXCCDF(from, using, id, logger, ovalDefinitions) {
|
|
|
305
396
|
markdown: markdown
|
|
306
397
|
};
|
|
307
398
|
}
|
|
308
|
-
exports.updateProfileUsingXCCDF = updateProfileUsingXCCDF;
|
package/lib/utilities/xccdf.d.ts
CHANGED
|
@@ -1,7 +1,88 @@
|
|
|
1
1
|
import { DecodedDescription } from '../types/xccdf';
|
|
2
|
+
/**
|
|
3
|
+
* Converts an encoded XML string into a JSON object.
|
|
4
|
+
*
|
|
5
|
+
* @param encodedXml - The encoded XML string to be converted.
|
|
6
|
+
* @returns The JSON representation of the XML string.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* This function uses the `fast-xml-parser` library to parse the XML string.
|
|
10
|
+
* The parser options are configured to:
|
|
11
|
+
* - Not ignore attributes.
|
|
12
|
+
* - Remove namespace prefixes.
|
|
13
|
+
* - Prefix attribute names with '@_'.
|
|
14
|
+
* - Stop parsing at 'div' and 'p' nodes.
|
|
15
|
+
* - Treat all nodes as arrays.
|
|
16
|
+
*
|
|
17
|
+
* For more details on the parser options, see the documentation for the v4 or v5 version of the library:
|
|
18
|
+
* {@link https://github.com/NaturalIntelligence/fast-xml-parser/tree/master/docs/v4}
|
|
19
|
+
*/
|
|
2
20
|
export declare function convertEncodedXmlIntoJson(encodedXml: string): any;
|
|
21
|
+
/**
|
|
22
|
+
* Converts a JSON object into an XML string.
|
|
23
|
+
*
|
|
24
|
+
* @param data - The JSON object to be converted.
|
|
25
|
+
* @returns The XML string representation of the JSON object.
|
|
26
|
+
*/
|
|
3
27
|
export declare function convertJsonIntoXML(data: any): string;
|
|
28
|
+
/**
|
|
29
|
+
* Removes XML special characters from a given string.
|
|
30
|
+
*
|
|
31
|
+
* This function decodes any XML special characters in the input string
|
|
32
|
+
* and returns the decoded result.
|
|
33
|
+
*
|
|
34
|
+
* @param str - The input string containing XML special characters.
|
|
35
|
+
* @returns The decoded string with XML special characters removed.
|
|
36
|
+
*/
|
|
4
37
|
export declare function removeXMLSpecialCharacters(str: string): string;
|
|
5
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Converts a severity string to a numerical impact value.
|
|
40
|
+
*
|
|
41
|
+
* The function matches the input string against various regular expressions
|
|
42
|
+
* to determine the corresponding impact value:
|
|
43
|
+
* - "none", "na", "n/a", "not applicable" (case insensitive) -> 0.0
|
|
44
|
+
* - "low", "category iii", "category 3" (case insensitive) -> 0.3
|
|
45
|
+
* - "medium", "category ii", "category 2" -> 0.5
|
|
46
|
+
* - "high", "category i", "category 1" -> 0.7
|
|
47
|
+
* - "critical", "severe" -> 1.0
|
|
48
|
+
*
|
|
49
|
+
* If no match is found, the default impact value is 0.5.
|
|
50
|
+
*
|
|
51
|
+
* @param string - The severity string to be converted.
|
|
52
|
+
* @returns The numerical impact value corresponding to the severity string.
|
|
53
|
+
*/
|
|
54
|
+
export declare function severityStringToImpact(string: string): number;
|
|
55
|
+
/**
|
|
56
|
+
* Converts an impact number to a severity string.
|
|
57
|
+
*
|
|
58
|
+
* @param impact - A number representing the impact, which must be between 0.0 and 1.0 inclusive.
|
|
59
|
+
* @returns A string representing the severity level:
|
|
60
|
+
* - 'critical' for impact >= 0.9
|
|
61
|
+
* - 'high' for impact >= 0.7
|
|
62
|
+
* - 'medium' for impact >= 0.4
|
|
63
|
+
* - 'low' for impact >= 0.1
|
|
64
|
+
* - 'none' for impact < 0.1
|
|
65
|
+
* @throws {Error} If the impact is less than 0.0 or greater than 1.0.
|
|
66
|
+
*/
|
|
6
67
|
export declare function impactNumberToSeverityString(impact: number): string;
|
|
68
|
+
/**
|
|
69
|
+
* Converts an encoded HTML string into a JSON object, handling specific edge
|
|
70
|
+
* cases related to XSS and XML tags.
|
|
71
|
+
*
|
|
72
|
+
* This function performs the following steps:
|
|
73
|
+
* 1. Replaces occurrences of `"<"` with a placeholder to avoid
|
|
74
|
+
* breaking parsing.
|
|
75
|
+
* 2. Parses the patched HTML to extract text chunks.
|
|
76
|
+
* 3. Converts the extracted text chunks into JSON.
|
|
77
|
+
* 4. Cleans the converted JSON by replacing placeholders with the original
|
|
78
|
+
* characters and handling nested objects.
|
|
79
|
+
*
|
|
80
|
+
* Note: This function specifically addresses issues found in certain
|
|
81
|
+
* STIGs (Security Technical Implementation Guides) where XML tags are
|
|
82
|
+
* embedded within text fields.
|
|
83
|
+
*
|
|
84
|
+
* @param encodedHTML - The encoded HTML string to be converted.
|
|
85
|
+
* If not provided, an empty object is returned.
|
|
86
|
+
* @returns A `DecodedDescription` object containing the converted JSON data.
|
|
87
|
+
*/
|
|
7
88
|
export declare function convertEncodedHTMLIntoJson(encodedHTML?: string): DecodedDescription;
|
package/lib/utilities/xccdf.js
CHANGED
|
@@ -1,51 +1,118 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.convertEncodedXmlIntoJson = convertEncodedXmlIntoJson;
|
|
4
|
+
exports.convertJsonIntoXML = convertJsonIntoXML;
|
|
5
|
+
exports.removeXMLSpecialCharacters = removeXMLSpecialCharacters;
|
|
6
|
+
exports.severityStringToImpact = severityStringToImpact;
|
|
7
|
+
exports.impactNumberToSeverityString = impactNumberToSeverityString;
|
|
8
|
+
exports.convertEncodedHTMLIntoJson = convertEncodedHTMLIntoJson;
|
|
4
9
|
const tslib_1 = require("tslib");
|
|
5
|
-
const fast_xml_parser_1 =
|
|
10
|
+
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
6
11
|
const jstoxml_1 = require("jstoxml");
|
|
7
12
|
const htmlparser = tslib_1.__importStar(require("htmlparser2"));
|
|
8
13
|
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
9
14
|
const he_1 = tslib_1.__importDefault(require("he"));
|
|
15
|
+
/**
|
|
16
|
+
* Converts an encoded XML string into a JSON object.
|
|
17
|
+
*
|
|
18
|
+
* @param encodedXml - The encoded XML string to be converted.
|
|
19
|
+
* @returns The JSON representation of the XML string.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* This function uses the `fast-xml-parser` library to parse the XML string.
|
|
23
|
+
* The parser options are configured to:
|
|
24
|
+
* - Not ignore attributes.
|
|
25
|
+
* - Remove namespace prefixes.
|
|
26
|
+
* - Prefix attribute names with '@_'.
|
|
27
|
+
* - Stop parsing at 'div' and 'p' nodes.
|
|
28
|
+
* - Treat all nodes as arrays.
|
|
29
|
+
*
|
|
30
|
+
* For more details on the parser options, see the documentation for the v4 or v5 version of the library:
|
|
31
|
+
* {@link https://github.com/NaturalIntelligence/fast-xml-parser/tree/master/docs/v4}
|
|
32
|
+
*/
|
|
10
33
|
function convertEncodedXmlIntoJson(encodedXml) {
|
|
11
|
-
|
|
34
|
+
const options = {
|
|
12
35
|
ignoreAttributes: false,
|
|
13
|
-
|
|
36
|
+
removeNSPrefix: true,
|
|
14
37
|
attributeNamePrefix: '@_',
|
|
15
38
|
stopNodes: ['div', 'p'],
|
|
16
|
-
|
|
17
|
-
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
40
|
+
isArray: (_name, _jpath, _isLeafNode, _isAttribute) => true,
|
|
41
|
+
};
|
|
42
|
+
const parser = new fast_xml_parser_1.XMLParser(options);
|
|
43
|
+
return parser.parse(encodedXml);
|
|
18
44
|
}
|
|
19
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Converts a JSON object into an XML string.
|
|
47
|
+
*
|
|
48
|
+
* @param data - The JSON object to be converted.
|
|
49
|
+
* @returns The XML string representation of the JSON object.
|
|
50
|
+
*/
|
|
20
51
|
function convertJsonIntoXML(data) {
|
|
21
52
|
return (0, jstoxml_1.toXML)(data);
|
|
22
53
|
}
|
|
23
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Removes XML special characters from a given string.
|
|
56
|
+
*
|
|
57
|
+
* This function decodes any XML special characters in the input string
|
|
58
|
+
* and returns the decoded result.
|
|
59
|
+
*
|
|
60
|
+
* @param str - The input string containing XML special characters.
|
|
61
|
+
* @returns The decoded string with XML special characters removed.
|
|
62
|
+
*/
|
|
24
63
|
function removeXMLSpecialCharacters(str) {
|
|
25
|
-
|
|
64
|
+
//console.log('Remove special characters: ', JSON.stringify(str, null, 2));
|
|
65
|
+
const result = he_1.default.decode(str);
|
|
66
|
+
//console.log('Result of he.decode: ', JSON.stringify(result));
|
|
67
|
+
return result;
|
|
26
68
|
}
|
|
27
|
-
|
|
28
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Converts a severity string to a numerical impact value.
|
|
71
|
+
*
|
|
72
|
+
* The function matches the input string against various regular expressions
|
|
73
|
+
* to determine the corresponding impact value:
|
|
74
|
+
* - "none", "na", "n/a", "not applicable" (case insensitive) -> 0.0
|
|
75
|
+
* - "low", "category iii", "category 3" (case insensitive) -> 0.3
|
|
76
|
+
* - "medium", "category ii", "category 2" -> 0.5
|
|
77
|
+
* - "high", "category i", "category 1" -> 0.7
|
|
78
|
+
* - "critical", "severe" -> 1.0
|
|
79
|
+
*
|
|
80
|
+
* If no match is found, the default impact value is 0.5.
|
|
81
|
+
*
|
|
82
|
+
* @param string - The severity string to be converted.
|
|
83
|
+
* @returns The numerical impact value corresponding to the severity string.
|
|
84
|
+
*/
|
|
85
|
+
function severityStringToImpact(string) {
|
|
29
86
|
var _a, _b, _c, _d, _e;
|
|
30
|
-
if ((_a =
|
|
87
|
+
if ((_a = RegExp(/none|na|n\/a|not[\s()*_|]?applicable/i).exec(string)) === null || _a === void 0 ? void 0 : _a.length) {
|
|
31
88
|
return 0.0;
|
|
32
89
|
}
|
|
33
|
-
if ((_b =
|
|
90
|
+
if ((_b = RegExp(/low|cat(egory)?\s*(iii|3)/i).exec(string)) === null || _b === void 0 ? void 0 : _b.length) {
|
|
34
91
|
return 0.3;
|
|
35
92
|
}
|
|
36
|
-
if ((_c =
|
|
93
|
+
if ((_c = RegExp(/med(ium)?|cat(egory)?\s*(ii|2)/).exec(string)) === null || _c === void 0 ? void 0 : _c.length) {
|
|
37
94
|
return 0.5;
|
|
38
95
|
}
|
|
39
|
-
if ((_d =
|
|
96
|
+
if ((_d = RegExp(/high|cat(egory)?\s*(i|1)/).exec(string)) === null || _d === void 0 ? void 0 : _d.length) {
|
|
40
97
|
return 0.7;
|
|
41
98
|
}
|
|
42
|
-
if ((_e =
|
|
99
|
+
if ((_e = RegExp(/crit(ical)?|severe/).exec(string)) === null || _e === void 0 ? void 0 : _e.length) {
|
|
43
100
|
return 1.0;
|
|
44
101
|
}
|
|
45
|
-
console.log(`${string} is not a valid severity value. It should be one of the approved keywords. ${id} will be treated as medium severity`);
|
|
46
102
|
return 0.5;
|
|
47
103
|
}
|
|
48
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Converts an impact number to a severity string.
|
|
106
|
+
*
|
|
107
|
+
* @param impact - A number representing the impact, which must be between 0.0 and 1.0 inclusive.
|
|
108
|
+
* @returns A string representing the severity level:
|
|
109
|
+
* - 'critical' for impact >= 0.9
|
|
110
|
+
* - 'high' for impact >= 0.7
|
|
111
|
+
* - 'medium' for impact >= 0.4
|
|
112
|
+
* - 'low' for impact >= 0.1
|
|
113
|
+
* - 'none' for impact < 0.1
|
|
114
|
+
* @throws {Error} If the impact is less than 0.0 or greater than 1.0.
|
|
115
|
+
*/
|
|
49
116
|
function impactNumberToSeverityString(impact) {
|
|
50
117
|
// Impact must be 0.0 - 1.0
|
|
51
118
|
if (impact < 0.0 || impact > 1.0) {
|
|
@@ -67,7 +134,26 @@ function impactNumberToSeverityString(impact) {
|
|
|
67
134
|
return 'none';
|
|
68
135
|
}
|
|
69
136
|
}
|
|
70
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Converts an encoded HTML string into a JSON object, handling specific edge
|
|
139
|
+
* cases related to XSS and XML tags.
|
|
140
|
+
*
|
|
141
|
+
* This function performs the following steps:
|
|
142
|
+
* 1. Replaces occurrences of `"<"` with a placeholder to avoid
|
|
143
|
+
* breaking parsing.
|
|
144
|
+
* 2. Parses the patched HTML to extract text chunks.
|
|
145
|
+
* 3. Converts the extracted text chunks into JSON.
|
|
146
|
+
* 4. Cleans the converted JSON by replacing placeholders with the original
|
|
147
|
+
* characters and handling nested objects.
|
|
148
|
+
*
|
|
149
|
+
* Note: This function specifically addresses issues found in certain
|
|
150
|
+
* STIGs (Security Technical Implementation Guides) where XML tags are
|
|
151
|
+
* embedded within text fields.
|
|
152
|
+
*
|
|
153
|
+
* @param encodedHTML - The encoded HTML string to be converted.
|
|
154
|
+
* If not provided, an empty object is returned.
|
|
155
|
+
* @returns A `DecodedDescription` object containing the converted JSON data.
|
|
156
|
+
*/
|
|
71
157
|
function convertEncodedHTMLIntoJson(encodedHTML) {
|
|
72
158
|
if (encodedHTML) {
|
|
73
159
|
// Some STIGs regarding XSS put the < character inside of the description which breaks parsing
|
|
@@ -82,9 +168,15 @@ function convertEncodedHTMLIntoJson(encodedHTML) {
|
|
|
82
168
|
htmlParser.end();
|
|
83
169
|
const converted = convertEncodedXmlIntoJson(xmlChunks.join(''));
|
|
84
170
|
let cleaned = {};
|
|
85
|
-
|
|
171
|
+
// Some STIGs have xml tags inside of the actual text which breaks processing,
|
|
172
|
+
// e.g U_ASD_STIG_V5R1_Manual-xccdf.xml and all Oracle Database STIGs
|
|
173
|
+
if (typeof converted.VulnDiscussion === 'object') {
|
|
86
174
|
let extractedVulnDescription = '';
|
|
87
|
-
const remainingFields = lodash_1.default.omit(converted.VulnDiscussion, [
|
|
175
|
+
const remainingFields = lodash_1.default.omit(converted.VulnDiscussion, [
|
|
176
|
+
'FalsePositives', 'FalseNegatives', 'Documentable', 'Mitigations',
|
|
177
|
+
'SeverityOverrideGuidance', 'PotentialImpacts', 'ThirdPartyTools',
|
|
178
|
+
'MitigationControl', 'Responsibility', 'IAControls'
|
|
179
|
+
]);
|
|
88
180
|
Object.entries(remainingFields).forEach(([field, value]) => {
|
|
89
181
|
extractedVulnDescription += `<${field}> ${value}`;
|
|
90
182
|
});
|
|
@@ -114,4 +206,3 @@ function convertEncodedHTMLIntoJson(encodedHTML) {
|
|
|
114
206
|
}
|
|
115
207
|
return {};
|
|
116
208
|
}
|
|
117
|
-
exports.convertEncodedHTMLIntoJson = convertEncodedHTMLIntoJson;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mitre/inspec-objects",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Typescript objects for normalizing between InSpec profiles and XCCDF benchmarks",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -24,36 +24,36 @@
|
|
|
24
24
|
},
|
|
25
25
|
"homepage": "https://github.com/mitre/ts-inspec-objects#readme",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@types/flat": "
|
|
27
|
+
"@types/flat": "5.0.2",
|
|
28
28
|
"@types/he": "^1.1.2",
|
|
29
|
-
"@types/json-diff": "^0.
|
|
29
|
+
"@types/json-diff": "^1.0.0",
|
|
30
30
|
"@types/jstoxml": "^2.0.2",
|
|
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": "^
|
|
35
|
-
"flat": "
|
|
34
|
+
"fast-xml-parser": "^4.5.1",
|
|
35
|
+
"flat": "5.0.2",
|
|
36
36
|
"he": "^1.2.0",
|
|
37
|
-
"htmlparser2": "^
|
|
37
|
+
"htmlparser2": "^10.0.0",
|
|
38
38
|
"inspecjs": "^2.6.6",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"jstoxml": "^3.2.3",
|
|
39
|
+
"json-diff": "^1.0.6",
|
|
40
|
+
"jstoxml": "^5.0.2",
|
|
42
41
|
"lodash": "^4.17.21",
|
|
43
42
|
"mustache": "^4.2.0",
|
|
44
43
|
"pretty": "^2.0.0",
|
|
45
|
-
"ts-jest": "^28.0.4",
|
|
46
|
-
"typescript": "^4.5.5",
|
|
47
44
|
"winston": "^3.8.1",
|
|
48
|
-
"yaml": "^
|
|
45
|
+
"yaml": "^2.3.1"
|
|
49
46
|
},
|
|
50
47
|
"devDependencies": {
|
|
51
|
-
"@types/jest": "^
|
|
52
|
-
"@types/node": "^
|
|
53
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
54
|
-
"@typescript-eslint/parser": "^
|
|
48
|
+
"@types/jest": "^29.5.12",
|
|
49
|
+
"@types/node": "^22.5.2",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
|
51
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
55
52
|
"eslint": "^8.30.0",
|
|
56
|
-
"
|
|
53
|
+
"jest": "^29.7.0",
|
|
54
|
+
"ts-jest": "^29.1.1",
|
|
55
|
+
"tslib": "^2.4.0",
|
|
56
|
+
"typescript": "^5.2.2"
|
|
57
57
|
},
|
|
58
58
|
"jest": {
|
|
59
59
|
"rootDir": ".",
|