@mitre/inspec-objects 2.0.4 → 2.1.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/workflows/auto-approve-and-merge.yml +1 -0
- package/.github/workflows/build.yml +6 -2
- package/.github/workflows/draft-release.yml +4 -1
- package/.github/workflows/e2e-test.yml +7 -3
- package/.github/workflows/linter.yml +6 -2
- package/.github/workflows/push-to-gpr.yml +7 -2
- package/.github/workflows/push-to-npm.yml +10 -4
- package/eslint.config.js +41 -0
- package/lib/index.d.ts +2 -4
- package/lib/index.js +4 -4
- package/lib/mappings/{CciNistMappingData.js → cci-nist-mapping-data.js} +1 -1
- package/lib/objects/control.js +41 -81
- package/lib/objects/profile.js +6 -6
- package/lib/parsers/json.js +5 -7
- package/lib/parsers/oval.js +23 -24
- package/lib/parsers/xccdf.d.ts +1 -1
- package/lib/parsers/xccdf.js +122 -137
- package/lib/utilities/{diffMarkdown.js → diff-markdown.js} +10 -11
- package/lib/utilities/diff.js +46 -48
- package/lib/utilities/global.js +9 -15
- package/lib/utilities/logging.js +1 -1
- package/lib/utilities/update.js +25 -28
- package/lib/utilities/xccdf.js +21 -32
- package/package.json +14 -13
- package/tsconfig.build.json +1 -1
- package/tsconfig.json +2 -2
- package/vitest.config.ts +3 -3
- package/.eslintignore +0 -2
- package/.eslintrc +0 -41
- /package/lib/mappings/{CciNistMappingData.d.ts → cci-nist-mapping-data.d.ts} +0 -0
- /package/lib/utilities/{diffMarkdown.d.ts → diff-markdown.d.ts} +0 -0
|
@@ -29,20 +29,19 @@ function createDiffMarkdown(diff) {
|
|
|
29
29
|
updatedTitles: [],
|
|
30
30
|
updatedDescriptions: [],
|
|
31
31
|
};
|
|
32
|
-
Object.entries(diff.ignoreFormattingDiff.renamedControlIDs)
|
|
32
|
+
for (const [oldId, newId] of Object.entries(diff.ignoreFormattingDiff.renamedControlIDs)) {
|
|
33
33
|
renderableDiffData.hasRenamedControls = true;
|
|
34
34
|
renderableDiffData.renamedControls.push({
|
|
35
35
|
oldId: oldId,
|
|
36
36
|
newId: newId,
|
|
37
37
|
});
|
|
38
|
-
}
|
|
39
|
-
Object.entries(diff.rawDiff.changedControls)
|
|
40
|
-
|
|
41
|
-
if ((_a = controlDiff.descs) === null || _a === void 0 ? void 0 : _a.check) {
|
|
38
|
+
}
|
|
39
|
+
for (const [id, controlDiff] of Object.entries(diff.rawDiff.changedControls)) {
|
|
40
|
+
if (controlDiff.descs?.check) {
|
|
42
41
|
const oldCheck = lodash_1.default.get(controlDiff.descs.check, '__old', 'undefined');
|
|
43
42
|
const newCheck = lodash_1.default.get(controlDiff.descs.check, '__new', 'undefined');
|
|
44
|
-
if (oldCheck.
|
|
45
|
-
newCheck.
|
|
43
|
+
if (oldCheck.replaceAll('\n', '').replaceAll(/\W/g, '')
|
|
44
|
+
!== newCheck.replaceAll('\n', '').replaceAll(/\W/g, '')) {
|
|
46
45
|
renderableDiffData.updatedChecks.push({
|
|
47
46
|
id: id,
|
|
48
47
|
old: oldCheck,
|
|
@@ -50,11 +49,11 @@ function createDiffMarkdown(diff) {
|
|
|
50
49
|
});
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
|
-
if (
|
|
52
|
+
if (controlDiff.descs?.fix) {
|
|
54
53
|
const oldFix = lodash_1.default.get(controlDiff.descs.fix, '__old', 'undefined');
|
|
55
54
|
const newFix = lodash_1.default.get(controlDiff.descs.fix, '__new', 'undefined');
|
|
56
|
-
if (oldFix.
|
|
57
|
-
newFix.
|
|
55
|
+
if (oldFix.replaceAll('\n', '').replaceAll(/\W/g, '')
|
|
56
|
+
!== newFix.replaceAll('\n', '').replaceAll(/\W/g, '')) {
|
|
58
57
|
renderableDiffData.updatedFixes.push({
|
|
59
58
|
id: id,
|
|
60
59
|
old: oldFix,
|
|
@@ -95,7 +94,7 @@ function createDiffMarkdown(diff) {
|
|
|
95
94
|
});
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
|
-
}
|
|
97
|
+
}
|
|
99
98
|
// Render output
|
|
100
99
|
return mustache_1.default.render(automatticUpdateTemplate_json_1.default.data, renderableDiffData);
|
|
101
100
|
}
|
package/lib/utilities/diff.js
CHANGED
|
@@ -25,7 +25,7 @@ function removeNewlines(control) {
|
|
|
25
25
|
}
|
|
26
26
|
return lodash_1.default.mapValues(control, (value) => {
|
|
27
27
|
if (typeof value === 'string') {
|
|
28
|
-
return value.
|
|
28
|
+
return value.replaceAll('\n', '{{{{newlineHERE}}}}').trim();
|
|
29
29
|
}
|
|
30
30
|
else if (typeof value === 'object' && value !== null) {
|
|
31
31
|
return removeNewlines(value);
|
|
@@ -57,10 +57,10 @@ function ignoreFormattingDiff(diffData) {
|
|
|
57
57
|
return lodash_1.default.transform(diffData, (result, diffValue, key) => {
|
|
58
58
|
if (lodash_1.default.has(diffValue, '__new')) {
|
|
59
59
|
// Remove any trailing space
|
|
60
|
-
if (typeof lodash_1.default.get(diffValue, '__new') === 'string'
|
|
61
|
-
typeof lodash_1.default.get(diffValue, '__old') === 'string') {
|
|
62
|
-
if ((0, global_1.removeWhitespace)(lodash_1.default.get(diffValue, '__new', 'undefined'))
|
|
63
|
-
(0, global_1.removeWhitespace)(lodash_1.default.get(diffValue, '__old', 'undefined'))) {
|
|
60
|
+
if (typeof lodash_1.default.get(diffValue, '__new') === 'string'
|
|
61
|
+
&& typeof lodash_1.default.get(diffValue, '__old') === 'string') {
|
|
62
|
+
if ((0, global_1.removeWhitespace)(lodash_1.default.get(diffValue, '__new', 'undefined'))
|
|
63
|
+
!== (0, global_1.removeWhitespace)(lodash_1.default.get(diffValue, '__old', 'undefined'))) {
|
|
64
64
|
lodash_1.default.set(result, key, lodash_1.default.get(diffValue, '__new'));
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -70,14 +70,14 @@ function ignoreFormattingDiff(diffData) {
|
|
|
70
70
|
}
|
|
71
71
|
else if (Array.isArray(diffValue)) {
|
|
72
72
|
result[key] = diffValue
|
|
73
|
-
.map(
|
|
74
|
-
.filter(
|
|
73
|
+
.map(value => value[0] === '+' && value[1])
|
|
74
|
+
.filter(Boolean);
|
|
75
75
|
}
|
|
76
76
|
else if (typeof diffValue === 'object') {
|
|
77
77
|
result[key] = ignoreFormattingDiff(diffValue);
|
|
78
78
|
}
|
|
79
79
|
else if (key.endsWith('__deleted')) {
|
|
80
|
-
return
|
|
80
|
+
return;
|
|
81
81
|
}
|
|
82
82
|
else {
|
|
83
83
|
result[key] = diffValue;
|
|
@@ -96,7 +96,6 @@ function ignoreFormattingDiff(diffData) {
|
|
|
96
96
|
* - `rawDiff`: The raw profile differences.
|
|
97
97
|
*/
|
|
98
98
|
function diffProfile(fromProfile, toProfile, logger) {
|
|
99
|
-
var _a;
|
|
100
99
|
logger.info(`Processing diff between: ${fromProfile.name}(v:${fromProfile.version}) and: ${toProfile.name}(v:${toProfile.version})`);
|
|
101
100
|
const profileDiff = {
|
|
102
101
|
addedControlIDs: [],
|
|
@@ -114,72 +113,71 @@ function diffProfile(fromProfile, toProfile, logger) {
|
|
|
114
113
|
addedControls: {},
|
|
115
114
|
changedControls: {},
|
|
116
115
|
};
|
|
117
|
-
const fromControlIDs = fromProfile.controls.map(
|
|
118
|
-
const toControlIDs = toProfile.controls.map(
|
|
116
|
+
const fromControlIDs = fromProfile.controls.map(control => control.id).toSorted();
|
|
117
|
+
const toControlIDs = toProfile.controls.map(control => control.id).toSorted();
|
|
119
118
|
// Find new controls
|
|
120
|
-
const controlIDDiff = (
|
|
119
|
+
const controlIDDiff = (0, json_diff_1.diff)(fromControlIDs, toControlIDs)?.filter((item) => !(item.length === 1 && item[0] === ' '));
|
|
121
120
|
// Contains the new IDs
|
|
122
121
|
const changedControlIds = [];
|
|
123
122
|
// a diffValue has an entry for both what was subtracted ("-")
|
|
124
123
|
// and what was added ("+") -- need to handle both
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
124
|
+
if (controlIDDiff)
|
|
125
|
+
for (const diffValue of controlIDDiff) {
|
|
126
|
+
if (diffValue[0] === '-') {
|
|
127
|
+
const existingControl = fromProfile.controls.find(control => control.id === diffValue[1]);
|
|
128
|
+
// Check if the control has been given a new ID
|
|
129
|
+
if (existingControl) {
|
|
130
|
+
const newControl = (0, update_1.findUpdatedControlByAllIdentifiers)(existingControl, toProfile.controls);
|
|
131
|
+
if (newControl && newControl.id !== existingControl.id) {
|
|
132
|
+
profileDiff.renamedControlIDs[existingControl.id] = newControl.id;
|
|
133
|
+
originalDiff.renamedControlIDs[existingControl.id] = newControl.id;
|
|
134
|
+
changedControlIds.push(newControl.id.toLowerCase());
|
|
135
|
+
const controlDiff = lodash_1.default.omit((0, json_diff_1.diff)(existingControl, newControl), 'code__deleted');
|
|
136
|
+
// logger.info("CONTROL DIFF:" + JSON.stringify(controlDiff, null, 2))
|
|
137
|
+
const renamedControlIgnoredFormatting = ignoreFormattingDiff(controlDiff);
|
|
138
|
+
profileDiff.changedControls[newControl.id] = renamedControlIgnoredFormatting;
|
|
139
|
+
profileDiff.changedControlIDs.push(newControl.id);
|
|
140
|
+
originalDiff.changedControls[newControl.id] = controlDiff;
|
|
141
|
+
originalDiff.changedControlIDs.push(newControl.id);
|
|
142
|
+
logger.verbose(`Control ${existingControl.id} has been updated to ${newControl.id}`);
|
|
143
|
+
logger.debug(`Updated control content: ${JSON.stringify(renamedControlIgnoredFormatting)}`);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
profileDiff.removedControlIDs.push(diffValue[1]);
|
|
147
|
+
originalDiff.removedControlIDs.push(diffValue[1]);
|
|
148
|
+
}
|
|
144
149
|
}
|
|
145
150
|
else {
|
|
146
|
-
|
|
147
|
-
originalDiff.removedControlIDs.push(diffValue[1]);
|
|
151
|
+
logger.error(`Unable to find existing control ${diffValue[1]}`);
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
|
-
else {
|
|
151
|
-
logger.
|
|
154
|
+
else if (diffValue[0] === '+' && !changedControlIds.includes(diffValue[1].toLowerCase()) && diffValue[1]) {
|
|
155
|
+
logger.info(JSON.stringify(diffValue));
|
|
156
|
+
logger.info(JSON.stringify(changedControlIds));
|
|
157
|
+
profileDiff.addedControlIDs.push(diffValue[1]);
|
|
158
|
+
originalDiff.addedControlIDs.push(diffValue[1]);
|
|
152
159
|
}
|
|
153
160
|
}
|
|
154
|
-
else if (diffValue[0] === '+' && !changedControlIds.includes(diffValue[1].toLowerCase()) && diffValue[1]) {
|
|
155
|
-
logger.info(JSON.stringify(diffValue));
|
|
156
|
-
logger.info(JSON.stringify(changedControlIds));
|
|
157
|
-
profileDiff.addedControlIDs.push(diffValue[1]);
|
|
158
|
-
originalDiff.addedControlIDs.push(diffValue[1]);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
161
|
// take the list of renamed controls out of the list of added controls
|
|
162
162
|
// (a control is not "new" if it was renamed)
|
|
163
163
|
profileDiff.addedControlIDs = profileDiff.addedControlIDs.filter((item) => !Object.values(profileDiff.renamedControlIDs).includes(item));
|
|
164
164
|
originalDiff.addedControlIDs = originalDiff.addedControlIDs.filter((item) => !Object.values(originalDiff.renamedControlIDs).includes(item));
|
|
165
165
|
// Add new controls to addedControls
|
|
166
|
-
profileDiff.addedControlIDs
|
|
167
|
-
const newControl = toProfile.controls.find(
|
|
166
|
+
for (const addedControl of profileDiff.addedControlIDs) {
|
|
167
|
+
const newControl = toProfile.controls.find(control => addedControl === control.id);
|
|
168
168
|
if (newControl && !profileDiff.changedControls[newControl.id]) {
|
|
169
169
|
profileDiff.addedControls[addedControl] = newControl;
|
|
170
170
|
originalDiff.addedControls[addedControl] = newControl;
|
|
171
171
|
}
|
|
172
|
-
}
|
|
172
|
+
}
|
|
173
173
|
// Find changed controls
|
|
174
174
|
for (const fromControl of fromProfile.controls) {
|
|
175
|
-
const toControl = toProfile.controls.find(
|
|
175
|
+
const toControl = toProfile.controls.find(control => control.id === fromControl.id);
|
|
176
176
|
if (toControl) {
|
|
177
177
|
const controlDiff = lodash_1.default.omit((0, json_diff_1.diff)(fromControl, toControl), 'code__deleted');
|
|
178
178
|
if (controlDiff) {
|
|
179
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
180
179
|
profileDiff.changedControls[toControl.id] = ignoreFormattingDiff(controlDiff);
|
|
181
180
|
profileDiff.changedControlIDs.push(toControl.id);
|
|
182
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
183
181
|
originalDiff.changedControls[toControl.id] = controlDiff;
|
|
184
182
|
originalDiff.changedControlIDs.push(toControl.id);
|
|
185
183
|
}
|
package/lib/utilities/global.js
CHANGED
|
@@ -56,7 +56,7 @@ function wrap(s, lineLength = 80) {
|
|
|
56
56
|
* @returns The unformatted string with newline characters and excessive whitespace removed.
|
|
57
57
|
*/
|
|
58
58
|
function unformatText(s) {
|
|
59
|
-
return s.
|
|
59
|
+
return s.replaceAll('\n', ' ').replaceAll(String.raw `\n`, ' ').replaceAll(/( +|\t)/g, ' ');
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
62
|
* Removes all whitespace characters from the given input string.
|
|
@@ -65,7 +65,7 @@ function unformatText(s) {
|
|
|
65
65
|
* @returns A new string with all whitespace characters removed.
|
|
66
66
|
*/
|
|
67
67
|
function removeWhitespace(input) {
|
|
68
|
-
return input.
|
|
68
|
+
return input.replaceAll(/\s/gi, '');
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Escapes backslashes that precede closing parentheses in a given string.
|
|
@@ -79,7 +79,7 @@ function removeWhitespace(input) {
|
|
|
79
79
|
* @returns A new string with the specified backslashes escaped.
|
|
80
80
|
*/
|
|
81
81
|
const escapeSpecialCaseBackslashes = (s) => {
|
|
82
|
-
return s.
|
|
82
|
+
return s.replaceAll(String.raw `\)`, String.raw `\\)`);
|
|
83
83
|
};
|
|
84
84
|
/**
|
|
85
85
|
* Escapes single quotes and backslashes in a given string.
|
|
@@ -91,7 +91,7 @@ const escapeSpecialCaseBackslashes = (s) => {
|
|
|
91
91
|
* @returns The escaped string with single quotes and backslashes properly escaped.
|
|
92
92
|
*/
|
|
93
93
|
const escapeSingleQuotes = (s) => {
|
|
94
|
-
return s.
|
|
94
|
+
return s.replaceAll('\\', '\\\\').replaceAll('\'', String.raw `\'`);
|
|
95
95
|
};
|
|
96
96
|
/**
|
|
97
97
|
* Escapes backslashes and double quotes in a given string.
|
|
@@ -103,7 +103,7 @@ const escapeSingleQuotes = (s) => {
|
|
|
103
103
|
* @returns The escaped string with backslashes and double quotes properly escaped.
|
|
104
104
|
*/
|
|
105
105
|
const escapeDoubleQuotes = (s) => {
|
|
106
|
-
return s.
|
|
106
|
+
return s.replaceAll('\\', '\\\\').replaceAll('"', String.raw `\"`);
|
|
107
107
|
};
|
|
108
108
|
/**
|
|
109
109
|
* Escapes quotes in a given string based on the presence of single and double quotes.
|
|
@@ -133,7 +133,7 @@ function escapeQuotes(s) {
|
|
|
133
133
|
* @returns The modified string with placeholders replaced by newline characters.
|
|
134
134
|
*/
|
|
135
135
|
function removeNewlinePlaceholders(s) {
|
|
136
|
-
return s.
|
|
136
|
+
return s.replaceAll('{{{{newlineHERE}}}}', '\n');
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
139
139
|
* Retrieves the value from the first path in the provided paths array that exists in the given object.
|
|
@@ -144,7 +144,7 @@ function removeNewlinePlaceholders(s) {
|
|
|
144
144
|
* @throws Will throw an error if none of the paths exist in the object.
|
|
145
145
|
*/
|
|
146
146
|
function getFirstPath(object, paths) {
|
|
147
|
-
const index = lodash_1.default.findIndex(paths,
|
|
147
|
+
const index = lodash_1.default.findIndex(paths, p => hasPath(object, p));
|
|
148
148
|
if (index === -1) {
|
|
149
149
|
throw new Error(`Attestation is missing one of these paths: ${paths.join(', ')}`);
|
|
150
150
|
}
|
|
@@ -160,12 +160,6 @@ function getFirstPath(object, paths) {
|
|
|
160
160
|
* @returns `true` if any of the specified paths exist in the object, otherwise `false`.
|
|
161
161
|
*/
|
|
162
162
|
function hasPath(file, path) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
pathArray = [path];
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
pathArray = path;
|
|
169
|
-
}
|
|
170
|
-
return lodash_1.default.some(pathArray, (p) => lodash_1.default.has(file, p));
|
|
163
|
+
const pathArray = lodash_1.default.isString(path) ? [path] : path;
|
|
164
|
+
return lodash_1.default.some(pathArray, p => lodash_1.default.has(file, p));
|
|
171
165
|
}
|
package/lib/utilities/logging.js
CHANGED
|
@@ -13,6 +13,6 @@ function createWinstonLogger(mapperName, level = 'debug') {
|
|
|
13
13
|
// Using the ANSI escape code sequence initiator (\xb[) to change output colors
|
|
14
14
|
// Colors used are: 33m (yellow) and 34m (blue)
|
|
15
15
|
// \x1b[0m : Resets the color settings to the default
|
|
16
|
-
info => `\
|
|
16
|
+
info => `\u001B[33m[${[info.timestamp]} -> ${mapperName}]:\u001B[0m \u001B[34m${info.message}\u001B[0m`)),
|
|
17
17
|
});
|
|
18
18
|
}
|
package/lib/utilities/update.js
CHANGED
|
@@ -14,7 +14,7 @@ const diff_1 = require("./diff");
|
|
|
14
14
|
const control_1 = tslib_1.__importDefault(require("../objects/control"));
|
|
15
15
|
const profile_1 = tslib_1.__importDefault(require("../objects/profile"));
|
|
16
16
|
const xccdf_1 = require("../parsers/xccdf");
|
|
17
|
-
const
|
|
17
|
+
const diff_markdown_1 = require("./diff-markdown");
|
|
18
18
|
/**
|
|
19
19
|
* Projects values from the source object onto the destination object,
|
|
20
20
|
* updating the destination object in place.
|
|
@@ -50,7 +50,7 @@ function projectValuesOntoExistingObj(dst, src, currentPath = '') {
|
|
|
50
50
|
lodash_1.default.set(dst, updatedValue, src[updatedValue]);
|
|
51
51
|
}
|
|
52
52
|
else if (Array.isArray(src[updatedValue])) {
|
|
53
|
-
const uniqueArrayValues = [...
|
|
53
|
+
const uniqueArrayValues = lodash_1.default.uniq([...lodash_1.default.get(dst, updatedValue, []), ...src[updatedValue]]);
|
|
54
54
|
lodash_1.default.set(dst, updatedValue, uniqueArrayValues);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -98,12 +98,11 @@ function getRangesForLines(text) {
|
|
|
98
98
|
const rangeStack = [];
|
|
99
99
|
const ranges = [];
|
|
100
100
|
const lines = text.split('\n');
|
|
101
|
-
for (
|
|
101
|
+
for (const [i, line] of lines.entries()) {
|
|
102
102
|
let j = 0;
|
|
103
|
-
while (j <
|
|
104
|
-
const line = lines[i];
|
|
103
|
+
while (j < line.length) {
|
|
105
104
|
let char = line[j];
|
|
106
|
-
const isEmptyStack = (stack.length
|
|
105
|
+
const isEmptyStack = (stack.length === 0);
|
|
107
106
|
const isNotEmptyStack = (stack.length > 0);
|
|
108
107
|
const isQuoteChar = quotes.includes(char);
|
|
109
108
|
const isNotEscapeChar = ((j == 0) || (j > 0 && line[j - 1] != '\\'));
|
|
@@ -141,20 +140,20 @@ function getRangesForLines(text) {
|
|
|
141
140
|
}
|
|
142
141
|
char = line[j];
|
|
143
142
|
baseCondition = (isNotEmptyStack && isNotEscapeChar);
|
|
144
|
-
const delimiterCondition = (baseCondition && Object.keys(stringDelimiters).includes(stack
|
|
145
|
-
const delimiterPushCondition = (delimiterCondition && (stack
|
|
146
|
-
const delimiterPopCondition = (delimiterCondition && (stringDelimiters[stack
|
|
147
|
-
const basePopCondition = (baseCondition && (stack
|
|
143
|
+
const delimiterCondition = (baseCondition && Object.keys(stringDelimiters).includes(stack.at(-1)));
|
|
144
|
+
const delimiterPushCondition = (delimiterCondition && (stack.at(-1) == char));
|
|
145
|
+
const delimiterPopCondition = (delimiterCondition && (stringDelimiters[stack.at(-1)] == char));
|
|
146
|
+
const basePopCondition = (baseCondition && (stack.at(-1) == char) && !Object.keys(stringDelimiters).includes(char));
|
|
148
147
|
const isCommentEndChar = ((j == 0) && (line.length >= 4) && (line.slice(0, 4) == '=end'));
|
|
149
|
-
const commentEndCondition = (baseCondition && isCommentEndChar && (stack
|
|
148
|
+
const commentEndCondition = (baseCondition && isCommentEndChar && (stack.at(-1) == '=begin'));
|
|
150
149
|
const popCondition = (basePopCondition || delimiterPopCondition || commentEndCondition);
|
|
151
|
-
const pushCondition = (quotePushCondition || variablePushCondition || stringPushCondition
|
|
152
|
-
percentStringPushCondition || delimiterPushCondition || commentBeginCondition || inlineInterpolationCondition);
|
|
150
|
+
const pushCondition = (quotePushCondition || variablePushCondition || stringPushCondition
|
|
151
|
+
|| percentStringPushCondition || delimiterPushCondition || commentBeginCondition || inlineInterpolationCondition);
|
|
153
152
|
if (popCondition) {
|
|
154
153
|
stack.pop();
|
|
155
|
-
rangeStack
|
|
154
|
+
rangeStack.at(-1).push(i);
|
|
156
155
|
const range_ = rangeStack.pop();
|
|
157
|
-
if (rangeStack.length
|
|
156
|
+
if (rangeStack.length === 0) {
|
|
158
157
|
ranges.push(range_);
|
|
159
158
|
}
|
|
160
159
|
}
|
|
@@ -242,8 +241,8 @@ function getExistingDescribeFromControl(control) {
|
|
|
242
241
|
// Array of lines representing the full InSpec control, with multi-line strings collapsed
|
|
243
242
|
const lines = joinMultiLineStringsFromRanges(control.code, multiLineRanges);
|
|
244
243
|
// Define RegExp for lines to skip.
|
|
245
|
-
const skip = [
|
|
246
|
-
const skipRegExp = RegExp(skip.map(x => `(^${x})`).join('|'));
|
|
244
|
+
const skip = [String.raw `control\W`, String.raw ` title\W`, String.raw ` desc\W`, String.raw ` impact\W`, String.raw ` tag\W`, String.raw ` ref\W`];
|
|
245
|
+
const skipRegExp = new RegExp(skip.map(x => `(^${x})`).join('|'));
|
|
247
246
|
// Extract describe block from InSpec control with collapsed multiline strings.
|
|
248
247
|
const describeBlock = [];
|
|
249
248
|
let ignoreNewLine = true;
|
|
@@ -260,9 +259,9 @@ function getExistingDescribeFromControl(control) {
|
|
|
260
259
|
}
|
|
261
260
|
}
|
|
262
261
|
// Return synthesized logic as describe block
|
|
263
|
-
const lastIndex = (describeBlock.
|
|
264
|
-
? describeBlock.lastIndexOf('end
|
|
265
|
-
: describeBlock.lastIndexOf('end');
|
|
262
|
+
const lastIndex = (describeBlock.includes('end'))
|
|
263
|
+
? describeBlock.lastIndexOf('end')
|
|
264
|
+
: describeBlock.lastIndexOf('end\r');
|
|
266
265
|
// Drop trailing ['end', '\n'] from Control block.
|
|
267
266
|
return describeBlock.slice(0, lastIndex).join('\n');
|
|
268
267
|
}
|
|
@@ -292,10 +291,8 @@ function findUpdatedControlByAllIdentifiers(existingControl, updatedControls) {
|
|
|
292
291
|
}
|
|
293
292
|
// Try to match based on legacy identifiers
|
|
294
293
|
updatedControl = updatedControls.find((updatedControl) => {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
var _a;
|
|
298
|
-
return legacyTag.toLowerCase() === ((_a = existingControl.id) === null || _a === void 0 ? void 0 : _a.toLowerCase());
|
|
294
|
+
return updatedControl.tags.legacy?.some((legacyTag) => {
|
|
295
|
+
return legacyTag.toLowerCase() === existingControl.id?.toLowerCase();
|
|
299
296
|
});
|
|
300
297
|
});
|
|
301
298
|
if (updatedControl) {
|
|
@@ -349,7 +346,7 @@ function updateProfile(from, using, logger) {
|
|
|
349
346
|
// Find the diff
|
|
350
347
|
const diff = (0, diff_1.diffProfile)(from, using, logger);
|
|
351
348
|
// Add the new controls
|
|
352
|
-
diff.ignoreFormattingDiff.addedControlIDs
|
|
349
|
+
for (const id of diff.ignoreFormattingDiff.addedControlIDs) {
|
|
353
350
|
const addedControl = diff.ignoreFormattingDiff.addedControls[id];
|
|
354
351
|
if (addedControl) {
|
|
355
352
|
logger.debug(`New Control: ${addedControl.id} - ${addedControl.title}`);
|
|
@@ -358,7 +355,7 @@ function updateProfile(from, using, logger) {
|
|
|
358
355
|
else {
|
|
359
356
|
throw new Error(`New control ${id} added but don't have the control data`);
|
|
360
357
|
}
|
|
361
|
-
}
|
|
358
|
+
}
|
|
362
359
|
// Update the existing controls
|
|
363
360
|
for (const existingControl of from.controls) {
|
|
364
361
|
const updatedControl = findUpdatedControlByAllIdentifiers(existingControl, using.controls);
|
|
@@ -398,11 +395,11 @@ function updateProfileUsingXCCDF(from, using, id, logger, ovalDefinitions) {
|
|
|
398
395
|
const updatedProfile = updateProfile(from, xccdfProfile, logger);
|
|
399
396
|
logger.debug('Creating diff markdown');
|
|
400
397
|
// Create the markdown
|
|
401
|
-
const markdown = (0,
|
|
398
|
+
const markdown = (0, diff_markdown_1.createDiffMarkdown)(updatedProfile.diff);
|
|
402
399
|
logger.debug('Profile update complete');
|
|
403
400
|
return {
|
|
404
401
|
profile: updatedProfile.profile,
|
|
405
402
|
diff: updatedProfile.diff,
|
|
406
|
-
markdown: markdown
|
|
403
|
+
markdown: markdown,
|
|
407
404
|
};
|
|
408
405
|
}
|
package/lib/utilities/xccdf.js
CHANGED
|
@@ -90,9 +90,9 @@ function convertEncodedXmlIntoJson(encodedXml, xmlParserOption = 'withArrayOptio
|
|
|
90
90
|
};
|
|
91
91
|
const parser = new fast_xml_parser_1.XMLParser(xmlParserOption === 'withArrayOption'
|
|
92
92
|
? withArrayOption
|
|
93
|
-
: xmlParserOption === 'withArrayNoEntitiesOption'
|
|
93
|
+
: (xmlParserOption === 'withArrayNoEntitiesOption'
|
|
94
94
|
? withArrayNoEntitiesOption
|
|
95
|
-
: noArrayOption);
|
|
95
|
+
: noArrayOption));
|
|
96
96
|
return parser.parse(encodedXml);
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
@@ -141,7 +141,7 @@ function removeHtmlTags(input) {
|
|
|
141
141
|
// $ matches the end of the string. This ensures the regex can handle
|
|
142
142
|
// cases where the tag is incomplete or unclosed (e.g., <div)
|
|
143
143
|
// g: Global flag to find all matches in the input string
|
|
144
|
-
return input.
|
|
144
|
+
return input.replaceAll(/<\/?[^>]+(>|$)/g, '');
|
|
145
145
|
}
|
|
146
146
|
/**
|
|
147
147
|
* Converts a severity string to a numerical impact value.
|
|
@@ -160,21 +160,20 @@ function removeHtmlTags(input) {
|
|
|
160
160
|
* @returns The numerical impact value corresponding to the severity string.
|
|
161
161
|
*/
|
|
162
162
|
function severityStringToImpact(string) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return 0.0;
|
|
163
|
+
if (new RegExp(/none|na|n\/a|not[\s()*_|]?applicable/i).exec(string)?.length) {
|
|
164
|
+
return 0;
|
|
166
165
|
}
|
|
167
|
-
if (
|
|
166
|
+
if (new RegExp(/low|cat(egory)?\s*(iii|3)/i).exec(string)?.length) {
|
|
168
167
|
return 0.3;
|
|
169
168
|
}
|
|
170
|
-
if (
|
|
169
|
+
if (new RegExp(/med(ium)?|cat(egory)?\s*(ii|2)/).exec(string)?.length) {
|
|
171
170
|
return 0.5;
|
|
172
171
|
}
|
|
173
|
-
if (
|
|
172
|
+
if (new RegExp(/high|cat(egory)?\s*(i|1)/).exec(string)?.length) {
|
|
174
173
|
return 0.7;
|
|
175
174
|
}
|
|
176
|
-
if (
|
|
177
|
-
return 1
|
|
175
|
+
if (new RegExp(/crit(ical)?|severe/).exec(string)?.length) {
|
|
176
|
+
return 1;
|
|
178
177
|
}
|
|
179
178
|
return 0.5;
|
|
180
179
|
}
|
|
@@ -192,7 +191,7 @@ function severityStringToImpact(string) {
|
|
|
192
191
|
*/
|
|
193
192
|
function impactNumberToSeverityString(impact) {
|
|
194
193
|
// Impact must be 0.0 - 1.0
|
|
195
|
-
if (impact < 0
|
|
194
|
+
if (impact < 0 || impact > 1) {
|
|
196
195
|
throw new Error('Impact cannot be less than 0.0 or greater than 1.0');
|
|
197
196
|
}
|
|
198
197
|
else {
|
|
@@ -234,7 +233,7 @@ function impactNumberToSeverityString(impact) {
|
|
|
234
233
|
function convertEncodedHTMLIntoJson(encodedHTML) {
|
|
235
234
|
if (encodedHTML) {
|
|
236
235
|
// Some STIGs regarding XSS put the < character inside of the description which breaks parsing
|
|
237
|
-
const patchedHTML = encodedHTML.
|
|
236
|
+
const patchedHTML = encodedHTML.replaceAll('"<"', '[[[REPLACE_LESS_THAN]]]');
|
|
238
237
|
const xmlChunks = [];
|
|
239
238
|
const htmlParser = new htmlparser.Parser({
|
|
240
239
|
ontext(text) {
|
|
@@ -252,32 +251,22 @@ function convertEncodedHTMLIntoJson(encodedHTML) {
|
|
|
252
251
|
const remainingFields = lodash_1.default.omit(converted.VulnDiscussion, [
|
|
253
252
|
'FalsePositives', 'FalseNegatives', 'Documentable', 'Mitigations',
|
|
254
253
|
'SeverityOverrideGuidance', 'PotentialImpacts', 'ThirdPartyTools',
|
|
255
|
-
'MitigationControl', 'Responsibility', 'IAControls'
|
|
254
|
+
'MitigationControl', 'Responsibility', 'IAControls',
|
|
256
255
|
]);
|
|
257
|
-
|
|
256
|
+
for (const [field, value] of Object.entries(remainingFields)) {
|
|
258
257
|
extractedVulnDescription += `<${field}> ${value}`;
|
|
259
|
-
}
|
|
258
|
+
}
|
|
260
259
|
cleaned = {
|
|
261
260
|
VulnDiscussion: extractedVulnDescription.replace(/\[\[\[REPLACE_LESS_THAN]]]/, '"<"'),
|
|
262
261
|
};
|
|
263
|
-
Object.entries(converted.VulnDiscussion)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
cleaned[key] = value;
|
|
269
|
-
}
|
|
270
|
-
});
|
|
262
|
+
for (const [key, value] of Object.entries(converted.VulnDiscussion)) {
|
|
263
|
+
cleaned[key] = typeof value === 'string' ? value.replace(/\[\[\[REPLACE_LESS_THAN]]]/, '"<"') : value;
|
|
264
|
+
}
|
|
271
265
|
}
|
|
272
266
|
else {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
cleaned[key] = value;
|
|
279
|
-
}
|
|
280
|
-
});
|
|
267
|
+
for (const [key, value] of Object.entries(converted)) {
|
|
268
|
+
cleaned[key] = typeof value === 'string' ? value.replace(/\[\[\[REPLACE_LESS_THAN]]]/, '"<"') : value;
|
|
269
|
+
}
|
|
281
270
|
}
|
|
282
271
|
return cleaned;
|
|
283
272
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mitre/inspec-objects",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Typescript objects for normalizing between InSpec profiles and XCCDF benchmarks",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
"build": "tsc -p ./tsconfig.build.json",
|
|
11
11
|
"dev": "npx -y ts-node test.ts",
|
|
12
12
|
"test": "vitest",
|
|
13
|
-
"
|
|
14
|
-
"lint
|
|
13
|
+
"test:ci": "vitest run",
|
|
14
|
+
"lint": "eslint --fix",
|
|
15
|
+
"lint:ci": "eslint --max-warnings 0"
|
|
15
16
|
},
|
|
16
17
|
"repository": {
|
|
17
18
|
"type": "git",
|
|
@@ -24,33 +25,33 @@
|
|
|
24
25
|
},
|
|
25
26
|
"homepage": "https://github.com/mitre/ts-inspec-objects#readme",
|
|
26
27
|
"dependencies": {
|
|
27
|
-
"@types/flat": "5.0.5",
|
|
28
28
|
"@types/he": "^1.1.2",
|
|
29
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
|
-
"@types/pretty": "^2.0.1",
|
|
34
33
|
"fast-xml-parser": "^5.0.7",
|
|
35
|
-
"flat": "6.0.1",
|
|
36
34
|
"he": "^1.2.0",
|
|
35
|
+
"htmlfy": "^1.0.1",
|
|
37
36
|
"htmlparser2": "^10.0.0",
|
|
38
37
|
"inspecjs": "^2.6.6",
|
|
39
38
|
"json-diff": "^1.0.6",
|
|
40
39
|
"jstoxml": "^7.0.1",
|
|
41
40
|
"lodash": "^4.17.21",
|
|
42
41
|
"mustache": "^4.2.0",
|
|
43
|
-
"pretty": "^2.0.0",
|
|
44
42
|
"tslib": "^2.8.1",
|
|
45
43
|
"winston": "^3.8.1",
|
|
46
44
|
"yaml": "^2.3.1"
|
|
47
45
|
},
|
|
48
46
|
"devDependencies": {
|
|
49
|
-
"@
|
|
50
|
-
"@
|
|
51
|
-
"@
|
|
52
|
-
"eslint": "^
|
|
53
|
-
"
|
|
54
|
-
"
|
|
47
|
+
"@eslint/js": "^10.0.1",
|
|
48
|
+
"@stylistic/eslint-plugin": "^5.9.0",
|
|
49
|
+
"@types/node": "^25.3.0",
|
|
50
|
+
"eslint": "^10.0.2",
|
|
51
|
+
"eslint-plugin-n": "^17.24.0",
|
|
52
|
+
"eslint-plugin-unicorn": "^63.0.0",
|
|
53
|
+
"typescript": "^5.9.3",
|
|
54
|
+
"typescript-eslint": "^8.56.1",
|
|
55
|
+
"vitest": "^4.0.1"
|
|
55
56
|
}
|
|
56
57
|
}
|
package/tsconfig.build.json
CHANGED
package/tsconfig.json
CHANGED
package/vitest.config.ts
CHANGED