@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.
@@ -2,6 +2,7 @@ name: Auto approve and Merge Dependabot PRs
2
2
  on:
3
3
  pull_request_target:
4
4
  types: [labeled]
5
+
5
6
  permissions:
6
7
  pull-requests: write
7
8
  contents: write
@@ -1,19 +1,23 @@
1
1
  name: Build and Pack TS-InSpec-Objects
2
+
2
3
  on:
3
4
  push:
4
5
  branches: [ main ]
5
6
  pull_request:
6
7
 
8
+ permissions:
9
+ contents: read
10
+
7
11
  jobs:
8
12
  build:
9
13
  name: Build and Pack TS-InSpec-Objects
10
14
  runs-on: ubuntu-24.04
11
15
 
12
16
  steps:
13
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v5
14
18
 
15
19
  - name: Setup Node.js
16
- uses: actions/setup-node@v4
20
+ uses: actions/setup-node@v6
17
21
  with:
18
22
  node-version: 22
19
23
  cache: 'npm'
@@ -2,10 +2,13 @@ name: Draft Release
2
2
 
3
3
  on:
4
4
  push:
5
- # branches to consider in the event; optional, defaults to all
6
5
  branches:
7
6
  - main
8
7
 
8
+ permissions:
9
+ contents: write
10
+ pull-requests: read
11
+
9
12
  jobs:
10
13
  update_draft_release:
11
14
  runs-on: ubuntu-24.04
@@ -1,19 +1,23 @@
1
1
  name: Run TS-InSpec-Objects E2E Tests
2
+
2
3
  on:
3
4
  push:
4
5
  branches: [ main ]
5
6
  pull_request:
6
7
 
8
+ permissions:
9
+ contents: read
10
+
7
11
  jobs:
8
12
  build:
9
13
  name: Run TS-InSpec-Objects E2E Tests
10
14
  runs-on: ubuntu-24.04
11
15
 
12
16
  steps:
13
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v5
14
18
 
15
19
  - name: Setup Node.js
16
- uses: actions/setup-node@v4
20
+ uses: actions/setup-node@v6
17
21
  with:
18
22
  node-version: 22
19
23
  cache: 'npm'
@@ -22,4 +26,4 @@ jobs:
22
26
  run: npm ci
23
27
 
24
28
  - name: Run e2e tests
25
- run: npm test
29
+ run: npm run test:ci
@@ -1,9 +1,13 @@
1
1
  name: Lint TS-InSpec-Objects
2
+
2
3
  on:
3
4
  push:
4
5
  branches: [ main ]
5
6
  pull_request:
6
7
 
8
+ permissions:
9
+ contents: read
10
+
7
11
  jobs:
8
12
  build:
9
13
  name: Lint TS-InSpec-Objects
@@ -11,10 +15,10 @@ jobs:
11
15
 
12
16
  steps:
13
17
  - name: Checkout code
14
- uses: actions/checkout@v4
18
+ uses: actions/checkout@v5
15
19
 
16
20
  - name: Setup Node.js
17
- uses: actions/setup-node@v4
21
+ uses: actions/setup-node@v6
18
22
  with:
19
23
  node-version: 22
20
24
  cache: 'npm'
@@ -1,17 +1,22 @@
1
1
  name: Build and Release NPM to GPR (GitHub Package Registry)
2
+
2
3
  on:
3
4
  release:
4
5
  types: [published]
5
6
  workflow_dispatch:
6
7
 
8
+ permissions:
9
+ contents: read
10
+ packages: write
11
+
7
12
  jobs:
8
13
  build-deploy:
9
14
  runs-on: ubuntu-24.04
10
15
  steps:
11
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v5
12
17
 
13
18
  - name: Setup node
14
- uses: actions/setup-node@v4
19
+ uses: actions/setup-node@v6
15
20
  with:
16
21
  node-version: 22
17
22
  registry-url: 'https://npm.pkg.github.com'
@@ -1,21 +1,29 @@
1
1
  name: Push @mitre/inspec-objects to NPM
2
+
2
3
  on:
3
4
  release:
4
5
  types: [published]
5
6
  workflow_dispatch:
6
7
 
8
+ permissions:
9
+ id-token: write # required for trusted publishing's use of OIDC
10
+ contents: read
11
+
7
12
  jobs:
8
13
  build-deploy:
9
14
  runs-on: ubuntu-24.04
10
15
  steps:
11
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v5
12
17
 
13
18
  - name: setup node
14
- uses: actions/setup-node@v4
19
+ uses: actions/setup-node@v6
15
20
  with:
16
21
  node-version: 22
17
22
  registry-url: 'https://registry.npmjs.org'
18
23
 
24
+ - name: Manually update npm to 11.5.1 or later which is the required version for trusted publishing to work # this step can be removed after the ubuntu runner has the correct minimum version of npm installed
25
+ run: npm install -g npm@latest
26
+
19
27
  - name: Install project dependencies
20
28
  run: npm ci
21
29
 
@@ -30,5 +38,3 @@ jobs:
30
38
 
31
39
  - name: Publish inspec-objects to NPM
32
40
  run: npm publish --access public mitre-inspec-objects-*.tgz
33
- env:
34
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,41 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import js from '@eslint/js';
3
+ import stylistic from '@stylistic/eslint-plugin';
4
+ import tseslint from 'typescript-eslint';
5
+ import unicorn from 'eslint-plugin-unicorn';
6
+ import n from 'eslint-plugin-n';
7
+
8
+ export default defineConfig([
9
+ {
10
+ ignores: ['node_modules/**', 'lib/**'],
11
+ },
12
+ js.configs.recommended,
13
+ ...tseslint.configs.recommended,
14
+ unicorn.configs.recommended,
15
+ stylistic.configs.customize({
16
+ braceStyle: '1tbs',
17
+ indent: [2, { SwitchCase: 1 }],
18
+ semi: true,
19
+ quoteProps: 'as-needed',
20
+ quotes: 'single',
21
+ }),
22
+ {
23
+ languageOptions: {
24
+ parser: tseslint.parser,
25
+ ecmaVersion: 'latest',
26
+ sourceType: 'module',
27
+ },
28
+ plugins: {
29
+ '@typescript-eslint': tseslint.plugin,
30
+ unicorn,
31
+ n,
32
+ },
33
+ rules: {
34
+ '@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
35
+ '@typescript-eslint/no-explicit-any': 'off',
36
+ 'unicorn/no-null': 'off',
37
+ 'unicorn/prefer-node-protocol': 'off',
38
+ 'unicorn/prevent-abbreviations': 'off',
39
+ },
40
+ },
41
+ ]);
package/lib/index.d.ts CHANGED
@@ -3,7 +3,5 @@ export * from './parsers/oval';
3
3
  export * from './parsers/xccdf';
4
4
  export * from './utilities/diff';
5
5
  export * from './utilities/update';
6
- import Control from './objects/control';
7
- import Profile from './objects/profile';
8
- export { Control };
9
- export { Profile };
6
+ export { default as Control } from './objects/control';
7
+ export { default as Profile } from './objects/profile';
package/lib/index.js CHANGED
@@ -7,7 +7,7 @@ tslib_1.__exportStar(require("./parsers/oval"), exports);
7
7
  tslib_1.__exportStar(require("./parsers/xccdf"), exports);
8
8
  tslib_1.__exportStar(require("./utilities/diff"), exports);
9
9
  tslib_1.__exportStar(require("./utilities/update"), exports);
10
- const control_1 = tslib_1.__importDefault(require("./objects/control"));
11
- exports.Control = control_1.default;
12
- const profile_1 = tslib_1.__importDefault(require("./objects/profile"));
13
- exports.Profile = profile_1.default;
10
+ var control_1 = require("./objects/control");
11
+ Object.defineProperty(exports, "Control", { enumerable: true, get: function () { return tslib_1.__importDefault(control_1).default; } });
12
+ var profile_1 = require("./objects/profile");
13
+ Object.defineProperty(exports, "Profile", { enumerable: true, get: function () { return tslib_1.__importDefault(profile_1).default; } });
@@ -5099,5 +5099,5 @@ exports.data = {
5099
5099
  'CCI-005144': 'SR-12',
5100
5100
  'CCI-005145': 'SR-12',
5101
5101
  'CCI-005146': 'SR-12',
5102
- 'CCI-005147': 'AT-2 a 1'
5102
+ 'CCI-005147': 'AT-2 a 1',
5103
5103
  };
@@ -3,8 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.objectifyDescriptions = objectifyDescriptions;
4
4
  const tslib_1 = require("tslib");
5
5
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
6
- const flat_1 = require("flat");
7
- const flat_2 = require("flat");
8
6
  const global_1 = require("../utilities/global");
9
7
  const logging_1 = require("../utilities/logging");
10
8
  /**
@@ -19,9 +17,9 @@ const logging_1 = require("../utilities/logging");
19
17
  function objectifyDescriptions(descs) {
20
18
  if (Array.isArray(descs)) {
21
19
  const descriptions = {};
22
- descs.forEach((description) => {
20
+ for (const description of descs) {
23
21
  descriptions[description.label] = description.data;
24
- });
22
+ }
25
23
  return descriptions;
26
24
  }
27
25
  return descs || {};
@@ -56,13 +54,12 @@ class Control {
56
54
  * If provided, the properties of the data object will be assigned to the instance.
57
55
  */
58
56
  constructor(data) {
59
- this.tags = {};
60
57
  this.refs = [];
61
58
  this.tags = {};
62
59
  if (data) {
63
- Object.entries(data).forEach(([key, value]) => {
60
+ for (const [key, value] of Object.entries(structuredClone(data))) {
64
61
  lodash_1.default.set(this, key, value);
65
- });
62
+ }
66
63
  }
67
64
  }
68
65
  /**
@@ -73,13 +70,7 @@ class Control {
73
70
  * @returns {Control} A new Control object created from the unformatted data.
74
71
  */
75
72
  toUnformattedObject() {
76
- const flattened = (0, flat_1.flatten)(this);
77
- Object.entries(flattened).forEach(([key, value]) => {
78
- if (typeof value === 'string') {
79
- lodash_1.default.set(flattened, key, value);
80
- }
81
- });
82
- return new Control((0, flat_2.unflatten)(flattened));
73
+ return new Control(this);
83
74
  }
84
75
  /**
85
76
  * Converts the control object to a string representation in a specific format.
@@ -108,11 +99,11 @@ class Control {
108
99
  result += ` desc "${this.desc}"\n`;
109
100
  }
110
101
  if (this.descs) {
111
- Object.entries(this.descs).forEach(([key, subDesc]) => {
102
+ for (const [key, subDesc] of Object.entries(this.descs)) {
112
103
  if (subDesc) {
113
104
  result += ` desc '${key}', "${subDesc}"\n`;
114
105
  }
115
- });
106
+ }
116
107
  }
117
108
  if (this.impact) {
118
109
  result += ` impact ${this.impact}\n`;
@@ -121,34 +112,18 @@ class Control {
121
112
  result += ` impact ${this.impact.toFixed(1)}\n`;
122
113
  }
123
114
  if (this.refs) {
124
- this.refs.forEach((ref) => {
125
- var _a;
126
- if (typeof ref === 'string') {
127
- result += ` ref "${ref}"\n`;
128
- }
129
- else {
130
- result += ` ref ${((_a = ref.ref) === null || _a === void 0 ? void 0 : _a.toString()) || ''}, url: ${ref.url || ''}`;
131
- }
132
- });
115
+ for (const ref of this.refs) {
116
+ result += typeof ref === 'string' ? ` ref "${ref}"\n` : ` ref ${ref.ref?.toString() || ''}, url: ${ref.url || ''}`;
117
+ }
133
118
  }
134
- Object.entries(this.tags).forEach(([tag, value]) => {
119
+ for (const [tag, value] of Object.entries(this.tags)) {
135
120
  if (typeof value === 'object') {
136
- if (Array.isArray(value) && typeof value[0] === 'string') {
137
- result += ` tag ${tag}: ${JSON.stringify(value)}\n`;
138
- }
139
- else {
140
- result += ` tag '${tag}': ${(value == null ? 'nil' : value)}\n`;
141
- }
121
+ result += Array.isArray(value) && typeof value[0] === 'string' ? ` tag ${tag}: ${JSON.stringify(value)}\n` : ` tag '${tag}': ${(value == undefined ? 'nil' : value)}\n`;
142
122
  }
143
123
  else if (typeof value === 'string') {
144
- if (value.includes('"')) {
145
- result += ` tag "${tag}": "${value}"\n`;
146
- }
147
- else {
148
- result += ` tag '${tag}': '${value}'\n`;
149
- }
124
+ result += value.includes('"') ? ` tag "${tag}": "${value}"\n` : ` tag '${tag}': '${value}'\n`;
150
125
  }
151
- });
126
+ }
152
127
  if (this.describe) {
153
128
  result += '\n';
154
129
  result += this.describe;
@@ -184,57 +159,47 @@ class Control {
184
159
  if (this.title) {
185
160
  result += ` title ${(0, global_1.escapeQuotes)(this.title)}\n`;
186
161
  }
187
- else {
188
- if (verbose) {
189
- logger.error(`${this.id} does not have a title`);
190
- }
162
+ else if (verbose) {
163
+ logger.error(`${this.id} does not have a title`);
191
164
  }
192
165
  // This is the known 'default' description - on previous version this content was repeated on descriptions processed by "descs"
193
166
  if (this.desc) {
194
167
  result += ` desc ${(0, global_1.escapeQuotes)(this.desc)}\n`;
195
168
  }
196
- else {
197
- if (verbose) {
198
- logger.error(`${this.id} does not have a desc`);
199
- }
169
+ else if (verbose) {
170
+ logger.error(`${this.id} does not have a desc`);
200
171
  }
201
172
  if (this.descs) {
202
- Object.entries(this.descs).forEach(([key, subDesc]) => {
173
+ for (const [key, subDesc] of Object.entries(this.descs)) {
203
174
  if (subDesc) {
204
175
  if (key.match('default') && this.desc) {
205
- if (subDesc != this.desc) {
206
- // The "default" keyword may have the same content as the desc content for backward compatibility with different historical InSpec versions.
207
- // In that case, we can ignore writing the "default" subdescription field.
208
- // If they are different, however, someone may be trying to use the keyword "default" for a unique subdescription, which should not be done.
209
- if (verbose) {
210
- logger.error(`${this.id} has a subdescription called "default" with contents that do not match the main description. "Default" should not be used as a keyword for unique sub-descriptions.`);
211
- }
176
+ // The "default" keyword may have the same content as the desc content for backward compatibility with different historical InSpec versions.
177
+ // In that case, we can ignore writing the "default" subdescription field.
178
+ // If they are different, however, someone may be trying to use the keyword "default" for a unique subdescription, which should not be done.
179
+ if (subDesc != this.desc && verbose) {
180
+ logger.error(`${this.id} has a subdescription called "default" with contents that do not match the main description. "Default" should not be used as a keyword for unique sub-descriptions.`);
212
181
  }
213
182
  }
214
183
  else {
215
184
  result += ` desc '${key}', ${(0, global_1.escapeQuotes)(subDesc)}\n`;
216
185
  }
217
186
  }
218
- else {
219
- if (verbose) {
220
- logger.warn(`${this.id} does not have a desc for the value ${key}`);
221
- }
187
+ else if (verbose) {
188
+ logger.warn(`${this.id} does not have a desc for the value ${key}`);
222
189
  }
223
- });
190
+ }
224
191
  }
225
192
  if (this.impact !== undefined) {
226
193
  result += ` impact ${(this.impact <= 0 ? this.impact.toFixed(1) : this.impact)}\n`;
227
194
  }
228
- else {
229
- if (verbose) {
230
- logger.error(`${this.id} does not have an impact`);
231
- }
195
+ else if (verbose) {
196
+ logger.error(`${this.id} does not have an impact`);
232
197
  }
233
- //-------------------------------------------------------------------------
198
+ // -------------------------------------------------------------------------
234
199
  // This may not be necessary, leaving commented code for posterity. Once we
235
200
  // have implemented the process and determined that there isn't any side
236
201
  // effects we can remove the commented code
237
- //-------------------------------------------------------------------------
202
+ // -------------------------------------------------------------------------
238
203
  // if (this.refs) {
239
204
  // this.refs.forEach((ref) => {
240
205
  // if (typeof ref === 'string') {
@@ -244,25 +209,25 @@ class Control {
244
209
  // }
245
210
  // });
246
211
  // }
247
- Object.entries(this.tags).forEach(([tag, value]) => {
212
+ for (const [tag, value] of Object.entries(this.tags)) {
248
213
  if (value) {
249
214
  if (typeof value === 'object') {
250
215
  if (Array.isArray(value) && typeof value[0] === 'string') {
251
216
  // The goal is to keep the style similar to cookstyle formatting
252
217
  result += ` tag ${tag}: ${JSON.stringify(value)
253
- .replace(/"/g, "'") // replace the double quotes with single quotes, ex: ["V-72029","SV-86653"] -> ['V-72029','SV-86653']
218
+ .replaceAll('"', "'") // replace the double quotes with single quotes, ex: ["V-72029","SV-86653"] -> ['V-72029','SV-86653']
254
219
  .split("','") // split the items in the string
255
220
  .join("', '")}\n`; // join them together using single quote and a space, ex: ['V-72029','SV-86653'] -> ['V-72029', 'SV-86653']
256
221
  }
257
222
  else {
258
223
  // Convert JSON Object to Ruby Hash
259
224
  const stringifiedObject = JSON.stringify(value, null, 2)
260
- .replace(/\n/g, '\n ')
261
- .replace(/\{\n {6}/g, '{')
262
- .replace(/\[\n {8}/g, '[')
263
- .replace(/\n {6}\]/g, ']')
264
- .replace(/\n {4}\}/g, '}')
265
- .replace(/": \[/g, '" => [');
225
+ .replaceAll('\n', '\n ')
226
+ .replaceAll(/\{\n {6}/g, '{')
227
+ .replaceAll(/\[\n {8}/g, '[')
228
+ .replaceAll(/\n {6}\]/g, ']')
229
+ .replaceAll(/\n {4}\}/g, '}')
230
+ .replaceAll('": [', '" => [');
266
231
  result += ` tag ${tag}: ${stringifiedObject}\n`;
267
232
  }
268
233
  }
@@ -272,17 +237,12 @@ class Control {
272
237
  }
273
238
  else {
274
239
  const nilTagList = ['severity', 'satisfies'];
275
- if (nilTagList.includes(tag)) {
276
- result += ` tag ${tag}: nil\n`;
277
- }
278
- else {
279
- result += ` tag '${tag}'\n`;
280
- }
240
+ result += nilTagList.includes(tag) ? ` tag ${tag}: nil\n` : ` tag '${tag}'\n`;
281
241
  if (verbose) {
282
242
  logger.info(`${this.id} does not have a value for tag: ${tag}`);
283
243
  }
284
244
  }
285
- });
245
+ }
286
246
  if (this.describe) {
287
247
  result += '\n';
288
248
  result += this.describe;
@@ -53,9 +53,9 @@ class Profile {
53
53
  this.files = [];
54
54
  this.controls = [];
55
55
  if (data) {
56
- Object.entries(data).forEach(([key, value]) => {
56
+ for (const [key, value] of Object.entries(data)) {
57
57
  lodash_1.default.set(this, key, value);
58
- });
58
+ }
59
59
  }
60
60
  }
61
61
  /**
@@ -79,7 +79,7 @@ class Profile {
79
79
  version: this.version,
80
80
  supports: this.supports,
81
81
  depends: this.depends,
82
- //inspec_version: this.inspec_version,
82
+ // inspec_version: this.inspec_version,
83
83
  inspec_version: yaml_1.default.stringify(`${this.inspec_version}`, { defaultStringType: 'QUOTE_DOUBLE' }),
84
84
  });
85
85
  }
@@ -95,12 +95,12 @@ class Profile {
95
95
  */
96
96
  toUnformattedObject() {
97
97
  const unformattedProfile = new Profile(this);
98
- Object.entries(this).forEach(([key, value]) => {
98
+ for (const [key, value] of Object.entries(this)) {
99
99
  if (typeof value === 'string') {
100
100
  lodash_1.default.set(unformattedProfile, key, (0, global_1.unformatText)(value));
101
101
  }
102
- });
103
- unformattedProfile.controls = this.controls.map((control) => control.toUnformattedObject());
102
+ }
103
+ unformattedProfile.controls = this.controls.map(control => control.toUnformattedObject());
104
104
  return unformattedProfile;
105
105
  }
106
106
  }
@@ -29,7 +29,7 @@ function processEvaluation(evaluationInput) {
29
29
  description: lodash_1.default.get(topLevelProfile.data, 'description'),
30
30
  version: topLevelProfile.data.version,
31
31
  });
32
- topLevelProfile.contains.forEach((control) => {
32
+ for (const control of topLevelProfile.contains) {
33
33
  profile.controls.push(new control_1.default({
34
34
  id: control.data.id,
35
35
  title: control.data.title,
@@ -38,7 +38,7 @@ function processEvaluation(evaluationInput) {
38
38
  descs: (0, control_1.objectifyDescriptions)(control.hdf.wraps.descriptions),
39
39
  tags: control.hdf.wraps.tags,
40
40
  }));
41
- });
41
+ }
42
42
  return profile;
43
43
  }
44
44
  /**
@@ -59,7 +59,7 @@ function processProfileJSON(profileInput) {
59
59
  description: lodash_1.default.get(profileInput.data, 'description'),
60
60
  version: profileInput.data.version,
61
61
  });
62
- profileInput.data.controls.forEach((control) => {
62
+ for (const control of profileInput.data.controls) {
63
63
  const newControl = new control_1.default({
64
64
  id: control.id,
65
65
  title: control.title,
@@ -72,17 +72,15 @@ function processProfileJSON(profileInput) {
72
72
  newControl.describe = (0, update_1.getExistingDescribeFromControl)(newControl);
73
73
  // Migrate check and fix text from tags to descriptions
74
74
  if (newControl.tags.check && !newControl.descs.check) {
75
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
76
75
  lodash_1.default.set(newControl.descs, 'check', control.tags.check);
77
76
  lodash_1.default.set(newControl.tags, 'check', undefined);
78
77
  }
79
78
  if (newControl.tags.fix && !newControl.descs.fix) {
80
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
81
79
  lodash_1.default.set(newControl.descs, 'fix', control.tags.fix);
82
80
  lodash_1.default.set(newControl.tags, 'fix', undefined);
83
81
  }
84
82
  profile.controls.push(newControl);
85
- });
83
+ }
86
84
  return profile;
87
85
  }
88
86
  /**
@@ -110,7 +108,7 @@ function processExecJSON(execJSON) {
110
108
  */
111
109
  function processInSpecProfile(json) {
112
110
  const convertedFile = (0, inspecjs_1.convertFile)(json, true);
113
- let profile = new profile_1.default();
111
+ let profile;
114
112
  if (convertedFile['1_0_ExecJson']) {
115
113
  profile = processEvaluation((0, inspecjs_1.contextualizeEvaluation)(convertedFile['1_0_ExecJson'])).toUnformattedObject();
116
114
  }