@mitre/hdf-converters 2.6.20 → 2.6.21

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.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ASFFMapper = void 0;
6
+ exports.ASFFResults = exports.ASFFMapper = void 0;
7
7
  const html_entities_1 = require("html-entities");
8
8
  const inspecjs_1 = require("inspecjs");
9
9
  const lodash_1 = __importDefault(require("lodash"));
@@ -20,63 +20,189 @@ const IMPACT_MAPPING = new Map([
20
20
  const DEFAULT_NIST_TAG = ['SA-11', 'RA-5'];
21
21
  const SEVERITY_LABEL = 'Severity.Label';
22
22
  const COMPLIANCE_STATUS = 'Compliance.Status';
23
- const FROM_ASFF_TYPES_SLASH_REPLACEMENT = /{{{SLASH}}}/gi;
24
- const FIREWALL_MANAGER_REGEX = /arn:.+:securityhub:.+:.*:product\/aws\/firewall-manager/;
25
- const PROWLER_REGEX = /arn:.+:securityhub:.+:.*:product\/prowler\/prowler/;
26
- const SECURITYHUB_REGEX = /arn:.+:securityhub:.+:.*:product\/aws\/securityhub/;
27
- const TRIVY_REGEX = /arn:.+:securityhub:.+:.*:product\/aquasecurity\/aquasecurity/;
28
- const PRODUCT_ARN_MAPPING = new Map([
29
- [FIREWALL_MANAGER_REGEX, getFirewallManager()],
30
- [PROWLER_REGEX, getProwler()],
31
- [SECURITYHUB_REGEX, getSecurityHub()],
32
- [TRIVY_REGEX, getTrivy()]
23
+ var SpecialCasing;
24
+ (function (SpecialCasing) {
25
+ SpecialCasing["FirewallManager"] = "AWS Firewall Manager";
26
+ SpecialCasing["Prowler"] = "Prowler";
27
+ SpecialCasing["SecurityHub"] = "AWS Security Hub";
28
+ SpecialCasing["Trivy"] = "Aqua Trivy";
29
+ SpecialCasing["HDF2ASFF"] = "MITRE SAF HDF2ASFF";
30
+ SpecialCasing["Default"] = "Default";
31
+ })(SpecialCasing || (SpecialCasing = {}));
32
+ function whichSpecialCase(finding) {
33
+ const productArn = lodash_1.default.get(finding, 'ProductArn');
34
+ if (!productArn) {
35
+ console.trace(finding, productArn);
36
+ }
37
+ if (productArn.match(/^arn:[^:]+:securityhub:[^:]+:[^:]*:product\/aws\/firewall-manager$/)) {
38
+ return SpecialCasing.FirewallManager;
39
+ }
40
+ else if (productArn.match(/^arn:[^:]+:securityhub:[^:]+:[^:]*:product\/prowler\/prowler$/)) {
41
+ return SpecialCasing.Prowler;
42
+ }
43
+ else if (productArn.match(/^arn:[^:]+:securityhub:[^:]+:[^:]*:product\/aws\/securityhub$/)) {
44
+ return SpecialCasing.SecurityHub;
45
+ }
46
+ else if (productArn.match(/^arn:[^:]+:securityhub:[^:]+:[^:]*:product\/aquasecurity\/aquasecurity$/)) {
47
+ return SpecialCasing.Trivy;
48
+ }
49
+ else if (lodash_1.default.some(lodash_1.default.get(finding, 'FindingProviderFields.Types'), (type) => {
50
+ const version = type.split('/')[2].split('-')[0];
51
+ const [major, minor, patch] = version.split('.');
52
+ if (parseInt(major) > 1 &&
53
+ parseInt(minor) > 5 &&
54
+ parseInt(patch) > 20) {
55
+ return lodash_1.default.startsWith(type, 'MITRE/SAF/');
56
+ }
57
+ else {
58
+ return false;
59
+ }
60
+ })) {
61
+ return SpecialCasing.HDF2ASFF;
62
+ }
63
+ else {
64
+ return SpecialCasing.Default;
65
+ }
66
+ }
67
+ const SPECIAL_CASE_MAPPING = new Map([
68
+ [SpecialCasing.FirewallManager, getFirewallManager()],
69
+ [SpecialCasing.Prowler, getProwler()],
70
+ [SpecialCasing.SecurityHub, getSecurityHub()],
71
+ [SpecialCasing.Trivy, getTrivy()],
72
+ [SpecialCasing.HDF2ASFF, getHDF2ASFF()]
33
73
  ]);
34
- function replaceTypesSlashes(data) {
35
- const findings = data.Findings.map((finding) => {
36
- var _a;
37
- return {
38
- ...finding,
39
- Types: (_a = finding.Types) === null || _a === void 0 ? void 0 : _a.map((type) => type.replace(FROM_ASFF_TYPES_SLASH_REPLACEMENT, '/'))
74
+ function externalProductHandler(context, product, data, func, defaultVal) {
75
+ var _a;
76
+ if (product !== SpecialCasing.Default &&
77
+ lodash_1.default.has(SPECIAL_CASE_MAPPING.get(product), func)) {
78
+ let keywords = {};
79
+ if (context.supportingDocs.has(product)) {
80
+ keywords = { ...context.supportingDocs.get(product) };
81
+ }
82
+ return (_a = lodash_1.default.get(SPECIAL_CASE_MAPPING.get(product), func)) === null || _a === void 0 ? void 0 : _a.apply(context, [
83
+ data,
84
+ keywords
85
+ ]);
86
+ }
87
+ else {
88
+ if (typeof defaultVal === 'function') {
89
+ return defaultVal();
90
+ }
91
+ else {
92
+ return defaultVal;
93
+ }
94
+ }
95
+ }
96
+ function consolidate(context, input, file) {
97
+ const allFindings = lodash_1.default.get(file, 'Findings');
98
+ if (input.length !== allFindings.length) {
99
+ throw new Error('The number of generated controls should be the same as the number of findings at this point in the process.');
100
+ }
101
+ const idGroups = lodash_1.default.groupBy(lodash_1.default.zip(input, allFindings), (value) => {
102
+ const [hdfControl, asffFinding] = value;
103
+ return externalProductHandler(context, whichSpecialCase(asffFinding), asffFinding, 'subfindingsId', lodash_1.default.get(hdfControl, 'id'));
104
+ });
105
+ const output = [];
106
+ Object.entries(idGroups || {}).forEach((idGroup) => {
107
+ const [id, data] = idGroup;
108
+ const group = data.map((d) => d[0]);
109
+ const findings = data.map((d) => d[1]);
110
+ const productInfo = lodash_1.default.get(findings[0], 'ProductArn')
111
+ .split(':')
112
+ .slice(-1)[0]
113
+ .split('/');
114
+ const productName = externalProductHandler(context, whichSpecialCase(findings[0]), findings, 'productName', (0, html_entities_1.encode)(`${productInfo[1]}/${productInfo[2]}`));
115
+ const hasNoTitlePrefix = externalProductHandler(context, whichSpecialCase(findings[0]), null, 'doesNotHaveFindingTitlePrefix', false);
116
+ const titlePrefix = hasNoTitlePrefix ? '' : `${productName}: `;
117
+ const waiverData = externalProductHandler(context, whichSpecialCase(findings[0]), group, 'waiverData', {});
118
+ const item = {
119
+ id: id,
120
+ title: `${titlePrefix}${lodash_1.default.uniq(group.map((d) => d.title)).join(';')}`,
121
+ tags: lodash_1.default.mergeWith({}, ...group.map((d) => d.tags), (acc, cur) => {
122
+ if (acc === undefined || cur === undefined) {
123
+ return acc || cur;
124
+ }
125
+ else if (lodash_1.default.isEqual(acc, cur)) {
126
+ return acc;
127
+ }
128
+ else {
129
+ return lodash_1.default.uniq(lodash_1.default.concat([], acc, cur));
130
+ }
131
+ }),
132
+ impact: Math.max(...group.map((d) => d.impact)),
133
+ desc: externalProductHandler(context, whichSpecialCase(findings[0]), group, 'desc', lodash_1.default.uniq(group.map((d) => d.desc)).join('\n')),
134
+ descriptions: group
135
+ .map((d) => d.descriptions)
136
+ .flat()
137
+ .filter((element, index, arr) => element !== null &&
138
+ element !== undefined &&
139
+ element.data !== '' &&
140
+ index ===
141
+ arr.findIndex((e) => e !== null && e !== undefined && e.data === element.data)),
142
+ refs: group
143
+ .map((d) => d.refs)
144
+ .flat()
145
+ .filter((element) => lodash_1.default.get(element, 'url') !== undefined),
146
+ source_location: (() => {
147
+ const locs = lodash_1.default.uniq(group.map((d) => d.source_location)).filter((loc) => Object.keys(loc || {}).length !== 0);
148
+ if (locs.length === 0) {
149
+ return {};
150
+ }
151
+ else if (locs.length === 1) {
152
+ return locs[0];
153
+ }
154
+ else {
155
+ return { ref: JSON.stringify(locs) };
156
+ }
157
+ })(),
158
+ ...(Object.keys(waiverData || {}).length !== 0 && {
159
+ waiver_data: waiverData
160
+ }),
161
+ code: externalProductHandler(context, whichSpecialCase(findings[0]), group, 'code', JSON.stringify({ Findings: findings }, null, 2)),
162
+ results: group.map((d) => d.results).flat()
40
163
  };
164
+ output.push(item);
41
165
  });
42
- return { Findings: findings };
166
+ return output;
167
+ }
168
+ function wrapWithFindingsObject(output) {
169
+ if (!lodash_1.default.has(output, 'Findings')) {
170
+ if (Array.isArray(output)) {
171
+ output = { Findings: output };
172
+ }
173
+ else {
174
+ output = { Findings: [output] };
175
+ }
176
+ }
177
+ return output;
43
178
  }
44
179
  function fixFileInput(asffJson) {
180
+ let output = {};
45
181
  try {
46
- let output = JSON.parse(asffJson);
47
- if (!lodash_1.default.has(output, 'Findings')) {
48
- if (Array.isArray(output)) {
49
- output = { Findings: output };
50
- }
51
- else {
52
- output = { Findings: [output] };
53
- }
54
- }
55
- return replaceTypesSlashes(output);
182
+ output = JSON.parse(asffJson);
56
183
  }
57
184
  catch {
58
185
  const fixedInput = `[${asffJson
59
186
  .trim()
60
187
  .replace(/}\n/g, '},\n')
61
188
  .replace(/\},\n\$/g, '')}]`;
62
- let output = JSON.parse(fixedInput);
63
- if (!lodash_1.default.has(output, 'Findings')) {
64
- if (Array.isArray(output)) {
65
- output = { Findings: output };
66
- }
67
- else {
68
- output = { Findings: [output] };
69
- }
70
- }
71
- return replaceTypesSlashes(output);
189
+ output = JSON.parse(fixedInput);
72
190
  }
191
+ return wrapWithFindingsObject(output);
73
192
  }
74
193
  function getFirewallManager() {
75
194
  const findingId = (finding) => (0, html_entities_1.encode)(lodash_1.default.get(finding, 'Title'));
76
- const productName = (findings) => (0, html_entities_1.encode)(`${lodash_1.default.get(findings[0], 'ProductFields.aws/securityhub/CompanyName')} ${lodash_1.default.get(findings[0], 'ProductFields.aws/securityhub/ProductName')}`);
195
+ const productName = (findings) => {
196
+ const finding = Array.isArray(findings) ? findings[0] : findings;
197
+ return (0, html_entities_1.encode)(`${lodash_1.default.get(finding, 'ProductFields.aws/securityhub/CompanyName')} ${lodash_1.default.get(finding, 'ProductFields.aws/securityhub/ProductName')}`);
198
+ };
199
+ const filename = (findingInfo) => {
200
+ return `${productName(findingInfo[1])}.json`;
201
+ };
77
202
  return {
78
203
  findingId,
79
- productName
204
+ productName,
205
+ filename
80
206
  };
81
207
  }
82
208
  function getProwler() {
@@ -86,13 +212,24 @@ function getProwler() {
86
212
  const hyphenIndex = generatorId.indexOf('-');
87
213
  return (0, html_entities_1.encode)(generatorId.slice(hyphenIndex + 1));
88
214
  };
89
- const productName = (findings) => (0, html_entities_1.encode)(lodash_1.default.get(findings[0], 'ProductFields.ProviderName'));
215
+ const productName = (findings) => {
216
+ const finding = Array.isArray(findings) ? findings[0] : findings;
217
+ return (0, html_entities_1.encode)(lodash_1.default.get(finding, 'ProductFields.ProviderName'));
218
+ };
90
219
  const desc = () => ' ';
220
+ const filename = (findingInfo) => {
221
+ return `${productName(findingInfo[1])}.json`;
222
+ };
223
+ const meta = () => {
224
+ return { name: 'Prowler', title: 'Prowler Findings' };
225
+ };
91
226
  return {
92
227
  subfindingsCodeDesc,
93
228
  findingId,
94
229
  productName,
95
- desc
230
+ desc,
231
+ filename,
232
+ meta
96
233
  };
97
234
  }
98
235
  function getSecurityHub() {
@@ -101,7 +238,7 @@ function getSecurityHub() {
101
238
  return controls.find((control) => lodash_1.default.get(control, 'StandardsControlArn') ===
102
239
  lodash_1.default.get(finding, FINDING_STANDARDS_CONTROL_ARN));
103
240
  };
104
- const supportingDocs = (standards) => {
241
+ const securityhubSupportingDocs = (standards) => {
105
242
  let controls;
106
243
  try {
107
244
  if (Array.isArray(standards)) {
@@ -177,24 +314,25 @@ function getSecurityHub() {
177
314
  }
178
315
  };
179
316
  const productName = (findings) => {
317
+ const finding = Array.isArray(findings) ? findings[0] : findings;
180
318
  let standardName;
181
- if (lodash_1.default.get(findings[0], 'Types[0]')
319
+ if (lodash_1.default.get(finding, 'Types[0]')
182
320
  .split('/')
183
321
  .slice(-1)[0]
184
322
  .replace(/-/gi, ' ')
185
323
  .toLowerCase() ===
186
- lodash_1.default.get(findings[0], FINDING_STANDARDS_CONTROL_ARN)
324
+ lodash_1.default.get(finding, FINDING_STANDARDS_CONTROL_ARN)
187
325
  .split('/')
188
326
  .slice(-4)[0]
189
327
  .replace(/-/gi, ' ')
190
328
  .toLowerCase()) {
191
- standardName = lodash_1.default.get(findings[0], 'Types[0]')
329
+ standardName = lodash_1.default.get(finding, 'Types[0]')
192
330
  .split('/')
193
331
  .slice(-1)[0]
194
332
  .replace(/-/gi, ' ');
195
333
  }
196
334
  else {
197
- standardName = lodash_1.default.get(findings[0], FINDING_STANDARDS_CONTROL_ARN)
335
+ standardName = lodash_1.default.get(finding, FINDING_STANDARDS_CONTROL_ARN)
198
336
  .split('/')
199
337
  .slice(-4)[0]
200
338
  .replace(/-/gi, ' ')
@@ -204,17 +342,21 @@ function getSecurityHub() {
204
342
  })
205
343
  .join(' ');
206
344
  }
207
- return (0, html_entities_1.encode)(`${standardName} v${lodash_1.default.get(findings[0], FINDING_STANDARDS_CONTROL_ARN)
345
+ return (0, html_entities_1.encode)(`${standardName} v${lodash_1.default.get(finding, FINDING_STANDARDS_CONTROL_ARN)
208
346
  .split('/')
209
347
  .slice(-2)[0]}`);
210
348
  };
349
+ const filename = (findingInfo) => {
350
+ return `${productName(findingInfo[0])}.json`;
351
+ };
211
352
  return {
212
- supportingDocs,
353
+ securityhubSupportingDocs,
213
354
  findingId,
214
355
  findingImpact,
215
356
  findingNistTag,
216
357
  findingTitle,
217
- productName
358
+ productName,
359
+ filename
218
360
  };
219
361
  }
220
362
  function getTrivy() {
@@ -254,21 +396,251 @@ function getTrivy() {
254
396
  return undefined;
255
397
  }
256
398
  };
399
+ const productName = () => {
400
+ return 'Aqua Security - Trivy';
401
+ };
402
+ const doesNotHaveFindingTitlePrefix = () => true;
403
+ const filename = () => {
404
+ return `${productName()}.json`;
405
+ };
406
+ const meta = () => {
407
+ return { name: 'Trivy', title: 'Trivy Findings' };
408
+ };
257
409
  return {
258
410
  findingId,
259
411
  findingNistTag,
260
412
  subfindingsStatus,
261
- subfindingsMessage
413
+ subfindingsMessage,
414
+ doesNotHaveFindingTitlePrefix,
415
+ productName,
416
+ filename,
417
+ meta
418
+ };
419
+ }
420
+ function getHDF2ASFF() {
421
+ const replaceTypesSlashes = (type) => {
422
+ if (!lodash_1.default.isString(type)) {
423
+ return type;
424
+ }
425
+ const FROM_ASFF_TYPES_SLASH_REPLACEMENT = /{{{SLASH}}}/gi;
426
+ return type.replace(FROM_ASFF_TYPES_SLASH_REPLACEMENT, '/');
427
+ };
428
+ const objectifyTypesArray = (typesArray) => {
429
+ if (!Array.isArray(typesArray)) {
430
+ typesArray = lodash_1.default.get(typesArray, 'FindingProviderFields.Types');
431
+ }
432
+ const ret = {};
433
+ for (const typeString of typesArray) {
434
+ lodash_1.default.merge(ret, (() => {
435
+ const [type, attribute, value] = typeString.split('/');
436
+ let parsed = replaceTypesSlashes(value);
437
+ try {
438
+ parsed = JSON.parse(parsed);
439
+ }
440
+ catch { }
441
+ return { [type]: { [attribute]: parsed } };
442
+ })());
443
+ }
444
+ return ret;
445
+ };
446
+ const findExecutionFindingIndex = (asffOrFindings, asffFindingToMatch) => {
447
+ if (asffFindingToMatch) {
448
+ const targetToMatch = asffFindingToMatch.Id.split('/')[0];
449
+ return lodash_1.default.findIndex(Array.isArray(asffOrFindings)
450
+ ? asffOrFindings
451
+ : lodash_1.default.get(asffOrFindings, 'Findings'), (finding) => lodash_1.default.get(finding, 'Id').split('/').length === 2 &&
452
+ lodash_1.default.get(finding, 'Id').startsWith(targetToMatch));
453
+ }
454
+ return lodash_1.default.findIndex(Array.isArray(asffOrFindings)
455
+ ? asffOrFindings
456
+ : lodash_1.default.get(asffOrFindings, 'Findings'), (finding) => lodash_1.default.get(finding, 'Id').split('/').length === 2);
457
+ };
458
+ const preprocessingASFF = (asff) => {
459
+ const clone = lodash_1.default.cloneDeep(asff);
460
+ const index = findExecutionFindingIndex(clone);
461
+ lodash_1.default.pullAt(lodash_1.default.get(clone, 'Findings'), index);
462
+ return clone;
463
+ };
464
+ const supportingDocs = (input) => {
465
+ const [asff, docs] = input;
466
+ const index = findExecutionFindingIndex(asff);
467
+ const docsClone = lodash_1.default.cloneDeep(docs);
468
+ docsClone.set(SpecialCasing.HDF2ASFF, {
469
+ execution: lodash_1.default.get(asff, `Findings[${index}]`)
470
+ });
471
+ return docsClone;
472
+ };
473
+ const productName = (findings) => {
474
+ const finding = Array.isArray(findings) ? findings[0] : findings;
475
+ const name = lodash_1.default.get(finding, 'Id');
476
+ return (0, html_entities_1.encode)(name.split('/').slice(0, 2).join(' - '));
477
+ };
478
+ const doesNotHaveFindingTitlePrefix = () => true;
479
+ const code = (group) => {
480
+ return group[0].code || '';
481
+ };
482
+ const waiverData = (group) => {
483
+ return group[0].waiver_data || {};
484
+ };
485
+ const filename = (findingInfo) => {
486
+ const index = findExecutionFindingIndex(findingInfo[1], findingInfo[0]);
487
+ const target = replaceTypesSlashes(lodash_1.default.get(findingInfo[1][index], 'Id').split('/')[0]);
488
+ const finding = findingInfo[0];
489
+ return `${lodash_1.default.get(objectifyTypesArray(finding), 'File.Input')}-${target}.json`;
490
+ };
491
+ const mapping = (context) => {
492
+ var _a;
493
+ const execution = lodash_1.default.get(context.supportingDocs.get(SpecialCasing.HDF2ASFF), 'execution');
494
+ const executionTypes = objectifyTypesArray(execution);
495
+ const profileNames = Object.keys(executionTypes || {}).filter((type) => !['MITRE', 'File', 'Execution'].includes(type));
496
+ return {
497
+ shortcircuit: true,
498
+ platform: {
499
+ ...lodash_1.default.get(executionTypes, 'Execution.platform'),
500
+ target_id: ((_a = context.supportingDocs.get(SpecialCasing.HDF2ASFF)) === null || _a === void 0 ? void 0 : _a.execution.Id).split('/')[0]
501
+ },
502
+ version: lodash_1.default.get(executionTypes, 'Execution.version'),
503
+ statistics: lodash_1.default.get(executionTypes, 'Execution.statistics'),
504
+ profiles: lodash_1.default.map(profileNames, (profileName, index) => {
505
+ return {
506
+ name: lodash_1.default.get(executionTypes, `${profileName}.name`),
507
+ ...(lodash_1.default.has(executionTypes, `${profileName}.version`) && {
508
+ version: lodash_1.default.get(executionTypes, `${profileName}.version`)
509
+ }),
510
+ ...(lodash_1.default.has(executionTypes, `${profileName}.title`) && {
511
+ title: lodash_1.default.get(executionTypes, `${profileName}.title`)
512
+ }),
513
+ ...(lodash_1.default.has(executionTypes, `${profileName}.maintainer`) && {
514
+ maintainer: lodash_1.default.get(executionTypes, `${profileName}.maintainer`)
515
+ }),
516
+ ...(lodash_1.default.has(executionTypes, `${profileName}.summary`) && {
517
+ summary: lodash_1.default.get(executionTypes, `${profileName}.summary`)
518
+ }),
519
+ ...(lodash_1.default.has(executionTypes, `${profileName}.license`) && {
520
+ license: lodash_1.default.get(executionTypes, `${profileName}.license`)
521
+ }),
522
+ ...(lodash_1.default.has(executionTypes, `${profileName}.copyright`) && {
523
+ copyright: lodash_1.default.get(executionTypes, `${profileName}.copyright`)
524
+ }),
525
+ ...(lodash_1.default.has(executionTypes, `${profileName}.copyright_email`) && {
526
+ copyright_email: lodash_1.default.get(executionTypes, `${profileName}.copyright_email`)
527
+ }),
528
+ supports: lodash_1.default.get(executionTypes, `${profileName}.supports`, []),
529
+ attributes: lodash_1.default.get(executionTypes, `${profileName}.attributes`, []),
530
+ ...(lodash_1.default.has(executionTypes, `${profileName}.depends`) && {
531
+ depends: lodash_1.default.get(executionTypes, `${profileName}.depends`)
532
+ }),
533
+ groups: [],
534
+ ...(lodash_1.default.has(executionTypes, `${profileName}.status`) && {
535
+ status: lodash_1.default.get(executionTypes, `${profileName}.status`)
536
+ }),
537
+ ...(lodash_1.default.has(executionTypes, `${profileName}.description`) && {
538
+ description: lodash_1.default.get(executionTypes, `${profileName}.description`)
539
+ }),
540
+ ...(lodash_1.default.has(executionTypes, `${profileName}.inspec_version`) && {
541
+ inspec_version: lodash_1.default.get(executionTypes, `${profileName}.inspec_version`)
542
+ }),
543
+ ...(lodash_1.default.has(executionTypes, `${profileName}.parent_profile`) && {
544
+ parent_profile: lodash_1.default.get(executionTypes, `${profileName}.parent_profile`)
545
+ }),
546
+ ...(lodash_1.default.has(executionTypes, `${profileName}.skip_message`) && {
547
+ skip_message: lodash_1.default.get(executionTypes, `${profileName}.skip_message`)
548
+ }),
549
+ ...(lodash_1.default.has(executionTypes, `${profileName}.status_message`) && {
550
+ status_message: lodash_1.default.get(executionTypes, `${profileName}.status_message`)
551
+ }),
552
+ controls: consolidate(context, (() => {
553
+ console.log('findings length', lodash_1.default.get(context.data, 'Findings')
554
+ .length);
555
+ return lodash_1.default.map(lodash_1.default.get(context.data, 'Findings'), (finding) => {
556
+ const findingTypes = objectifyTypesArray(finding);
557
+ return {
558
+ id: lodash_1.default.get(findingTypes, 'Control.ID'),
559
+ ...(lodash_1.default.has(findingTypes, 'Control.Title') && {
560
+ title: lodash_1.default.get(findingTypes, 'Control.Title')
561
+ }),
562
+ ...(lodash_1.default.has(findingTypes, 'Control.Desc') && {
563
+ desc: lodash_1.default.get(findingTypes, 'Control.Desc')
564
+ }),
565
+ impact: lodash_1.default.get(findingTypes, 'Control.Impact'),
566
+ tags: {
567
+ ...lodash_1.default.omit(lodash_1.default.get(findingTypes, 'Tags'), ['nist']),
568
+ nist: (() => {
569
+ const nisttags = lodash_1.default.get(findingTypes, 'Tags.nist');
570
+ if (nisttags === undefined || nisttags.length === 0) {
571
+ return DEFAULT_NIST_TAG;
572
+ }
573
+ else {
574
+ return nisttags;
575
+ }
576
+ })()
577
+ },
578
+ descriptions: lodash_1.default.map(Object.entries(lodash_1.default.get(findingTypes, 'Descriptions') || {}), ([key, value]) => ({ label: key, data: value })),
579
+ refs: lodash_1.default.get(findingTypes, 'Control.Refs', []),
580
+ source_location: lodash_1.default.get(findingTypes, 'Control.Source_Location', {}),
581
+ ...(lodash_1.default.has(findingTypes, 'Control.Waiver_Data') && {
582
+ waiver_data: lodash_1.default.get(findingTypes, 'Control.Waiver_Data')
583
+ }),
584
+ code: '',
585
+ results: index === profileNames.length - 1
586
+ ? [
587
+ {
588
+ code_desc: lodash_1.default.get(findingTypes, 'Segment.code_desc'),
589
+ start_time: lodash_1.default.get(findingTypes, 'Segment.start_time'),
590
+ ...lodash_1.default.omit(lodash_1.default.get(findingTypes, 'Segment'), [
591
+ 'code_desc',
592
+ 'start_time'
593
+ ])
594
+ }
595
+ ]
596
+ : []
597
+ };
598
+ });
599
+ })(), context.data),
600
+ sha256: lodash_1.default.get(executionTypes, `${profileName}.sha256`)
601
+ };
602
+ })
603
+ };
604
+ };
605
+ return {
606
+ preprocessingASFF,
607
+ supportingDocs,
608
+ productName,
609
+ doesNotHaveFindingTitlePrefix,
610
+ code,
611
+ waiverData,
612
+ filename,
613
+ mapping
262
614
  };
263
615
  }
264
616
  class ASFFMapper extends base_converter_1.BaseConverter {
265
- constructor(asffJson, securityhubStandardsJsonArray = undefined, meta = null) {
266
- super(fixFileInput(asffJson));
267
- this.mappings = {
617
+ constructor(asff, supportingDocs, meta = undefined) {
618
+ super(asff);
619
+ this.meta = meta;
620
+ this.supportingDocs = supportingDocs;
621
+ this.setMappings();
622
+ }
623
+ statusReason(finding) {
624
+ var _a;
625
+ return (_a = lodash_1.default.get(finding, 'Compliance.StatusReasons')) === null || _a === void 0 ? void 0 : _a.map((reason) => Object.entries(reason || {}).map(([key, value]) => {
626
+ return `${(0, html_entities_1.encode)(key)}: ${(0, html_entities_1.encode)(value)}`;
627
+ })).flat().join('\n');
628
+ }
629
+ setMappings() {
630
+ this.mappings = externalProductHandler(this, whichSpecialCase(lodash_1.default.get(this.data, 'Findings[0]')), this, 'mapping', {
268
631
  platform: {
269
632
  name: 'Heimdall Tools',
270
633
  release: package_json_1.version,
271
- target_id: ''
634
+ target_id: {
635
+ transformer: (record) => {
636
+ const productInfo = lodash_1.default.get(record, 'Findings[0].ProductArn')
637
+ .split(':')
638
+ .slice(-1)[0]
639
+ .split('/');
640
+ const defaultTargetId = `${productInfo[1]} | ${productInfo[2]}`;
641
+ return externalProductHandler(this, whichSpecialCase(lodash_1.default.get(record, 'Findings[0]')), [lodash_1.default.get(record, 'Findings[0]'), record.Findings], 'productName', (0, html_entities_1.encode)(defaultTargetId));
642
+ }
643
+ }
272
644
  },
273
645
  version: package_json_1.version,
274
646
  statistics: {
@@ -302,12 +674,12 @@ class ASFFMapper extends base_converter_1.BaseConverter {
302
674
  {
303
675
  path: 'Findings',
304
676
  key: 'id',
305
- arrayTransformer: this.consolidate,
677
+ arrayTransformer: consolidate.bind(this, this),
306
678
  id: {
307
- transformer: (finding) => this.externalProductHandler(lodash_1.default.get(finding, 'ProductArn'), finding, 'findingId', (0, html_entities_1.encode)(lodash_1.default.get(finding, 'GeneratorId')))
679
+ transformer: (finding) => externalProductHandler(this, whichSpecialCase(finding), finding, 'findingId', (0, html_entities_1.encode)(lodash_1.default.get(finding, 'GeneratorId')))
308
680
  },
309
681
  title: {
310
- transformer: (finding) => this.externalProductHandler(lodash_1.default.get(finding, 'ProductArn'), finding, 'findingTitle', (0, html_entities_1.encode)(lodash_1.default.get(finding, 'Title')))
682
+ transformer: (finding) => externalProductHandler(this, whichSpecialCase(finding), finding, 'findingTitle', (0, html_entities_1.encode)(lodash_1.default.get(finding, 'Title')))
311
683
  },
312
684
  desc: {
313
685
  path: 'Description',
@@ -322,8 +694,9 @@ class ASFFMapper extends base_converter_1.BaseConverter {
322
694
  else {
323
695
  const defaultFunc = () => lodash_1.default.get(finding, SEVERITY_LABEL)
324
696
  ? lodash_1.default.get(finding, SEVERITY_LABEL)
325
- : lodash_1.default.get(finding, 'Severity.Normalized') / 100.0;
326
- impact = this.externalProductHandler(lodash_1.default.get(finding, 'ProductArn'), finding, 'findingImpact', defaultFunc);
697
+ : lodash_1.default.get(finding, 'Severity.Normalized') /
698
+ 100.0;
699
+ impact = externalProductHandler(this, whichSpecialCase(finding), finding, 'findingImpact', defaultFunc);
327
700
  }
328
701
  return typeof impact === 'string'
329
702
  ? IMPACT_MAPPING.get(impact) || 0
@@ -331,9 +704,10 @@ class ASFFMapper extends base_converter_1.BaseConverter {
331
704
  }
332
705
  },
333
706
  tags: {
707
+ transformer: (finding) => externalProductHandler(this, whichSpecialCase(finding), finding, 'findingTags', {}),
334
708
  nist: {
335
709
  transformer: (finding) => {
336
- const tags = this.externalProductHandler(lodash_1.default.get(finding, 'ProductArn'), finding, 'findingNistTag', []);
710
+ const tags = externalProductHandler(this, whichSpecialCase(finding), finding, 'findingNistTag', []);
337
711
  if (tags.length === 0) {
338
712
  return DEFAULT_NIST_TAG;
339
713
  }
@@ -363,9 +737,14 @@ class ASFFMapper extends base_converter_1.BaseConverter {
363
737
  ],
364
738
  refs: [
365
739
  {
366
- url: {
367
- path: 'SourceUrl',
368
- transformer: (input) => !Boolean(input) ? undefined : input
740
+ transformer: (finding) => {
741
+ return {
742
+ ...(lodash_1.default.has(finding, 'SourceUrl') && {
743
+ url: {
744
+ path: 'SourceUrl'
745
+ }
746
+ })
747
+ };
369
748
  }
370
749
  }
371
750
  ],
@@ -394,12 +773,12 @@ class ASFFMapper extends base_converter_1.BaseConverter {
394
773
  return inspecjs_1.ExecJSON.ControlResultStatus.Skipped;
395
774
  }
396
775
  };
397
- return this.externalProductHandler(lodash_1.default.get(finding, 'ProductArn'), finding, 'subfindingsStatus', defaultFunc);
776
+ return externalProductHandler(this, whichSpecialCase(finding), finding, 'subfindingsStatus', defaultFunc);
398
777
  }
399
778
  },
400
779
  code_desc: {
401
780
  transformer: (finding) => {
402
- let output = this.externalProductHandler(lodash_1.default.get(finding, 'ProductArn'), finding, 'subfindingsCodeDesc', '');
781
+ let output = externalProductHandler(this, whichSpecialCase(finding), finding, 'subfindingsCodeDesc', '');
403
782
  if (output) {
404
783
  output += '; ';
405
784
  }
@@ -419,8 +798,8 @@ class ASFFMapper extends base_converter_1.BaseConverter {
419
798
  return output;
420
799
  }
421
800
  },
422
- message: {
423
- transformer: (finding) => {
801
+ transformer: (finding) => {
802
+ const message = (() => {
424
803
  const defaultFunc = () => {
425
804
  const statusReason = this.statusReason(finding);
426
805
  switch (lodash_1.default.get(finding, COMPLIANCE_STATUS)) {
@@ -438,11 +817,9 @@ class ASFFMapper extends base_converter_1.BaseConverter {
438
817
  return statusReason;
439
818
  }
440
819
  };
441
- return this.externalProductHandler(lodash_1.default.get(finding, 'ProductArn'), finding, 'subfindingsMessage', defaultFunc);
442
- }
443
- },
444
- skip_message: {
445
- transformer: (finding) => {
820
+ return externalProductHandler(this, whichSpecialCase(finding), finding, 'subfindingsMessage', defaultFunc);
821
+ })();
822
+ const skipMessage = (() => {
446
823
  const statusReason = this.statusReason(finding);
447
824
  switch (lodash_1.default.get(finding, COMPLIANCE_STATUS)) {
448
825
  case undefined:
@@ -458,7 +835,13 @@ class ASFFMapper extends base_converter_1.BaseConverter {
458
835
  default:
459
836
  return undefined;
460
837
  }
461
- }
838
+ })();
839
+ return {
840
+ ...(message !== undefined && { message }),
841
+ ...(skipMessage !== undefined && {
842
+ skip_message: skipMessage
843
+ })
844
+ };
462
845
  },
463
846
  start_time: {
464
847
  transformer: (finding) => lodash_1.default.get(finding, 'LastObservedAt') ||
@@ -471,109 +854,34 @@ class ASFFMapper extends base_converter_1.BaseConverter {
471
854
  sha256: ''
472
855
  }
473
856
  ]
474
- };
475
- this.securityhubStandardsJsonArray = securityhubStandardsJsonArray;
857
+ });
858
+ }
859
+ }
860
+ exports.ASFFMapper = ASFFMapper;
861
+ class ASFFResults {
862
+ constructor(asffJson, securityhubStandardsJsonArray = undefined, meta = undefined) {
476
863
  this.meta = meta;
477
864
  this.supportingDocs = new Map();
478
- const map = PRODUCT_ARN_MAPPING.get(SECURITYHUB_REGEX);
479
- if (map) {
480
- this.supportingDocs.set(SECURITYHUB_REGEX, lodash_1.default.get(map, 'supportingDocs')(this.securityhubStandardsJsonArray));
481
- }
482
- }
483
- statusReason(finding) {
484
- var _a;
485
- return (_a = lodash_1.default.get(finding, 'Compliance.StatusReasons')) === null || _a === void 0 ? void 0 : _a.map((reason) => Object.entries(reason).map(([key, value]) => {
486
- return `${(0, html_entities_1.encode)(key)}: ${(0, html_entities_1.encode)(value)}`;
487
- })).flat().join('\n');
488
- }
489
- externalProductHandler(product, data, func, defaultVal) {
490
- var _a;
491
- let arn = null;
492
- let mapping;
493
- if ((product instanceof RegExp ||
494
- (arn = Array.from(PRODUCT_ARN_MAPPING.keys()).find((regex) => regex.test(product)))) &&
495
- (mapping = PRODUCT_ARN_MAPPING.get(arn || product)) !==
496
- undefined &&
497
- func in mapping) {
498
- let keywords = {};
499
- if (this.supportingDocs.has(arn || product)) {
500
- keywords = { ...this.supportingDocs.get(arn || product) };
501
- }
502
- return (_a = lodash_1.default.get(PRODUCT_ARN_MAPPING.get(arn || product), func)) === null || _a === void 0 ? void 0 : _a.apply(this, [data, keywords]);
503
- }
504
- else {
505
- if (typeof defaultVal === 'function') {
506
- return defaultVal();
507
- }
508
- else {
509
- return defaultVal;
510
- }
511
- }
512
- }
513
- consolidate(input, file) {
514
- const allFindings = lodash_1.default.get(file, 'Findings');
515
- const productGroups = new Map();
516
- input.forEach((item, index) => {
517
- var _a, _b, _c, _d;
518
- let arn = Array.from(PRODUCT_ARN_MAPPING.keys()).find((regex) => lodash_1.default.get(allFindings[index], 'ProductArn').match(regex));
519
- if (!arn) {
520
- const productInfo = lodash_1.default.get(allFindings[index], 'ProductArn')
521
- .split(':')
522
- .slice(-1)[0];
523
- arn = new RegExp(`arn:.+:securityhub:.+:.*:product/${productInfo.split('/')[1]}/${productInfo.split('/')[2]}`);
524
- }
525
- if (!productGroups.has(arn)) {
526
- productGroups.set(arn, new Map());
527
- }
528
- if (!((_a = productGroups.get(arn)) === null || _a === void 0 ? void 0 : _a.has(lodash_1.default.get(item, 'id')))) {
529
- (_b = productGroups.get(arn)) === null || _b === void 0 ? void 0 : _b.set(lodash_1.default.get(item, 'id'), []);
530
- }
531
- (_d = (_c = productGroups
532
- .get(arn)) === null || _c === void 0 ? void 0 : _c.get(lodash_1.default.get(item, 'id'))) === null || _d === void 0 ? void 0 : _d.push([item, allFindings[index]]);
533
- });
534
- const output = [];
535
- productGroups.forEach((idGroups, product) => {
536
- idGroups.forEach((data, id) => {
537
- const group = data.map((d) => d[0]);
538
- const findings = data.map((d) => d[1]);
539
- const productInfo = lodash_1.default.get(findings[0], 'ProductArn')
540
- .split(':')
541
- .slice(-1)[0]
542
- .split('/');
543
- const productName = this.externalProductHandler(product, findings, 'productName', (0, html_entities_1.encode)(`${productInfo[1]}/${productInfo[2]}`));
544
- const item = {
545
- id: Array.from(new Map([...productGroups].filter(([pg]) => pg !== product)).values()).find((ig) => Array.from(ig.keys()).includes(id)) !== undefined
546
- ? `[${productName}] ${id}`
547
- : id,
548
- title: `${productName}: ${lodash_1.default.uniq(group.map((d) => lodash_1.default.get(d, 'title'))).join(';')}`,
549
- tags: {
550
- nist: lodash_1.default.uniq(group.map((d) => lodash_1.default.get(d, 'tags.nist')).flat())
551
- },
552
- impact: Math.max(...group.map((d) => lodash_1.default.get(d, 'impact'))),
553
- desc: this.externalProductHandler(product, group, 'desc', lodash_1.default.uniq(group.map((d) => lodash_1.default.get(d, 'desc'))).join('\n')),
554
- descriptions: group
555
- .map((d) => lodash_1.default.get(d, 'descriptions'))
556
- .flat()
557
- .filter((element, index, arr) => element.data !== '' &&
558
- index === arr.findIndex((e) => e.data === element.data)),
559
- refs: group
560
- .map((d) => lodash_1.default.get(d, 'refs'))
561
- .flat()
562
- .filter((element) => lodash_1.default.get(element, 'url') !== undefined),
563
- source_location: {},
564
- code: JSON.stringify({ Findings: findings }, null, 2),
565
- results: group
566
- .map((d) => lodash_1.default.get(d, 'results'))
567
- .flat()
568
- };
569
- output.push(item);
570
- });
865
+ this.supportingDocs.set(SpecialCasing.SecurityHub, lodash_1.default.get(SPECIAL_CASE_MAPPING.get(SpecialCasing.SecurityHub), 'securityhubSupportingDocs', (standards) => {
866
+ throw new Error(`supportingDocs function should've been defined: ${standards}`);
867
+ })(securityhubStandardsJsonArray));
868
+ const findings = lodash_1.default.get(fixFileInput(asffJson), 'Findings');
869
+ this.data = lodash_1.default.groupBy(findings, (finding) => {
870
+ const productInfo = lodash_1.default.get(finding, 'ProductArn')
871
+ .split(':')
872
+ .slice(-1)[0]
873
+ .split('/');
874
+ const defaultFilename = `${productInfo[1]} | ${productInfo[2]}.json`;
875
+ return externalProductHandler(this, whichSpecialCase(finding), [finding, findings], 'filename', (0, html_entities_1.encode)(defaultFilename));
571
876
  });
572
- return output;
573
877
  }
574
- setMappings(customMappings) {
575
- super.setMappings(customMappings);
878
+ toHdf() {
879
+ return lodash_1.default.mapValues(this.data, (val) => {
880
+ const wrapped = wrapWithFindingsObject(val);
881
+ const ret = new ASFFMapper(externalProductHandler(this, whichSpecialCase(lodash_1.default.get(wrapped, 'Findings[0]')), wrapped, 'preprocessingASFF', wrapped), externalProductHandler(this, whichSpecialCase(lodash_1.default.get(wrapped, 'Findings[0]')), [wrapped, this.supportingDocs], 'supportingDocs', this.supportingDocs), externalProductHandler(this, whichSpecialCase(lodash_1.default.get(wrapped, 'Findings[0]')), undefined, 'meta', this.meta)).toHdf();
882
+ return ret;
883
+ });
576
884
  }
577
885
  }
578
- exports.ASFFMapper = ASFFMapper;
886
+ exports.ASFFResults = ASFFResults;
579
887
  //# sourceMappingURL=asff-mapper.js.map