Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
eb563f2
simplified basename implementation
Amndeep7 Jun 24, 2026
abec723
added more tests to handle more edge cases for basename functionality
Amndeep7 Jun 24, 2026
94b153d
modified tests to handle being concurrent better by making them indep…
Amndeep7 Jun 25, 2026
b984b40
add a test for if the target file is a symlink since resolvesafechild…
Amndeep7 Jun 25, 2026
62165af
add an additional check for if the target file is a symlink
Amndeep7 Jun 25, 2026
59932ab
converted as many locations as possible to use basename and/or resolv…
Amndeep7 Jun 25, 2026
df0c808
added filename sanitization
Amndeep7 Jun 25, 2026
ec64170
add failing test for windows shell metacharacters - might need to add…
Amndeep7 Jun 25, 2026
dab3f99
Clarifies docstring for safeFilename
Amndeep7 Jun 25, 2026
0185c2d
rename variable to make it clearer that it's a file as opposed to a d…
Amndeep7 Jun 25, 2026
9d04107
make the shell injection work properly
Amndeep7 Jun 25, 2026
307ad7d
fix the test to catch the error properly and validate against that
Amndeep7 Jun 26, 2026
e30e8c3
add safeexecfilesync command to handle the possibility of shell injec…
Amndeep7 Jun 26, 2026
a158354
modify execfilesync function slightly and add more tests
Amndeep7 Jul 6, 2026
1cb67a8
get rid of node: prefix
Amndeep7 Jul 6, 2026
9e06e0f
might be a race condition
Amndeep7 Aug 13, 2026
85b8488
maybe it wasn't the race condition. trying to do more debugging now
Amndeep7 Aug 13, 2026
c82a6ed
more debugging
Amndeep7 Aug 13, 2026
6947694
github actions apparently has a tilda in the path which we are very s…
Amndeep7 Aug 13, 2026
2578bc4
sequential probably isn't necessary then
Amndeep7 Aug 13, 2026
d8f8bde
sonarqube says to cosolidate tests
Amndeep7 Aug 13, 2026
02d0ffa
Merge branch 'main' into safe_filenames
Amndeep7 Aug 13, 2026
f686e8e
added text to the readme to clarify the security risks
Amndeep7 Aug 14, 2026
650cdb2
Merge branch 'main' into safe_filenames
Amndeep7 Aug 15, 2026
94e7fbf
Merge branch 'main' into safe_filenames
Amndeep7 Aug 15, 2026
45313c3
Merge branch 'main' into safe_filenames
Amndeep7 Aug 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,8 @@ validate threshold Validate the compliance and status counts of an HD

### Generate

NOTE: Please be aware that the `delta` and `update_controls4delta` subcommands can invoke an InSpec or CINC Auditor executable supplied through the -I/--inspecPath flag. Untrusted executable paths or arguments can create a command injection risk. Furthermore, Windows requires this invocation to use a shell which can create a risk of shell injection. The SAF CLI has been hardened so as to reduce these risks; however, as a workaround, run InSpec or CINC Auditor manually to generate the profile JSON, then provide it with the -J/--inspecJsonFile flag.

#### Delta

Update an existing InSpec profile with new or updated XCCDF guidance. With -M (runMapControls), uses a 3-tier SRG/CCI requirement-first matcher for cross-vendor deltas (e.g. RHEL 9 → Amazon Linux 2023) and persists per-control match decisions into delta.json's links[] field.
Expand Down
43 changes: 25 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"open": "^11.0.0",
"prompt-sync": "^4.2.0",
"run-script-os": "^1.1.6",
"sanitize-filename": "^1.6.4",
"table": "^6.8.1",
"tmp": "^0.2.1",
"tsimportlib": "^0.0.5",
Expand Down
5 changes: 2 additions & 3 deletions src/commands/attest/apply.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import fs from 'fs';
import path from 'path';
import { Flags } from '@oclif/core';
import _ from 'lodash';
import type { ExecJSON } from 'inspecjs';
import { addAttestationToHDF, parseXLSXAttestations, type Attestation } from '@mitre/hdf-converters';
import yaml from 'yaml';
import { basename } from '../../utils/global';
import { basename, resolveSafeChild, safeFilename } from '../../utils/global';
import { BaseCommand } from '../../utils/oclif/base_command';

export default class ApplyAttestation extends BaseCommand<typeof ApplyAttestation> {
Expand Down Expand Up @@ -74,7 +73,7 @@ export default class ApplyAttestation extends BaseCommand<typeof ApplyAttestatio
if (Object.entries(executions).length <= 1) {
fs.writeFileSync(flags.output, JSON.stringify(applied, null, 2));
} else {
fs.writeFileSync(path.join(flags.output, originalFilename), JSON.stringify(applied, null, 2));
fs.writeFileSync(resolveSafeChild(flags.output, safeFilename(originalFilename)), JSON.stringify(applied, null, 2));
}
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/commands/convert/asff2hdf.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import fs from 'fs';
import path from 'path';
import {
type AwsSecurityFindingFilters,
type DescribeStandardsControlsCommandOutput,
Expand All @@ -14,7 +13,7 @@ import { ASFFResults as Mapper, INPUT_TYPES } from '@mitre/hdf-converters';
import { NodeHttpHandler } from '@smithy/node-http-handler';
import https from 'https';
import _ from 'lodash';
import { basename, checkInput, checkSuffix } from '../../utils/global';
import { checkInput, checkSuffix, resolveSafeChild, safeFilename } from '../../utils/global';
import { createWinstonLogger } from '../../utils/logging';
import { BaseCommand } from '../../utils/oclif/base_command';

Expand Down Expand Up @@ -289,7 +288,7 @@ export default class ASFF2HDF extends BaseCommand<typeof ASFF2HDF> {
fs.mkdirSync(flags.output);
_.forOwn(results, (result, filename) => {
fs.writeFileSync(
path.join(flags.output, checkSuffix(basename(filename))),
resolveSafeChild(flags.output, safeFilename(checkSuffix(filename))),
JSON.stringify(result, null, 2),
);
});
Expand Down
5 changes: 2 additions & 3 deletions src/commands/convert/ckl2poam.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { mkdir, readFile } from 'fs/promises';
import path from 'path';
import { Flags } from '@oclif/core';
import { XMLParser } from 'fast-xml-parser';
import _ from 'lodash';
Expand All @@ -23,7 +22,7 @@ import {
extractSTIGUrl,
replaceSpecialCharacters,
} from '../../utils/ckl2poam';
import { basename, dataURLtoU8Array } from '../../utils/global';
import { basename, dataURLtoU8Array, resolveSafeChild, safeFilename } from '../../utils/global';
import { createWinstonLogger } from '../../utils/logging';
import { BaseCommand } from '../../utils/oclif/base_command';

Expand Down Expand Up @@ -318,7 +317,7 @@ export default class CKL2POAM extends BaseCommand<typeof CKL2POAM> {
currentRow += flags.rowsToSkip + 1;
}
}
return workBook.toFileAsync(path.join(flags.output, `${basename(fileName)}-${moment(new Date()).format('YYYY-MM-DD-HHmm')}.xlsm`));
return workBook.toFileAsync(resolveSafeChild(flags.output, safeFilename(`${basename(fileName)}-${moment(new Date()).format('YYYY-MM-DD-HHmm')}.xlsm`)));
}));
}
}
5 changes: 2 additions & 3 deletions src/commands/convert/conveyor2hdf.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Flags } from '@oclif/core';
import fs from 'fs';
import { ConveyorResults as Mapper, INPUT_TYPES } from '@mitre/hdf-converters';
import { basename, checkInput, checkSuffix } from '../../utils/global';
import path from 'path';
import { checkInput, checkSuffix, resolveSafeChild, safeFilename } from '../../utils/global';
import { BaseCommand } from '../../utils/oclif/base_command';
export default class Conveyor2HDF extends BaseCommand<typeof Conveyor2HDF> {
static readonly usage
Expand Down Expand Up @@ -38,7 +37,7 @@ export default class Conveyor2HDF extends BaseCommand<typeof Conveyor2HDF> {
fs.mkdirSync(flags.output);
for (const [filename, result] of Object.entries(results)) {
fs.writeFileSync(
path.join(flags.output, checkSuffix(basename(filename))),
resolveSafeChild(flags.output, safeFilename(checkSuffix(filename))),
JSON.stringify(result, null, 2),
);
}
Expand Down
11 changes: 5 additions & 6 deletions src/commands/convert/hdf2asff.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fs from 'fs';
import https from 'https';
import path from 'path';
import {
type AwsSecurityFinding,
SecurityHub,
Expand All @@ -10,7 +9,7 @@ import { FromHdfToAsffMapper as Mapper } from '@mitre/hdf-converters';
import { Flags } from '@oclif/core';
import { NodeHttpHandler } from '@smithy/node-http-handler';
import _ from 'lodash';
import { basename, checkSuffix } from '../../utils/global';
import { basename, checkSuffix, resolveSafeChild, safeFilename } from '../../utils/global';
import { BaseCommand } from '../../utils/oclif/base_command';

export default class HDF2ASFF extends BaseCommand<typeof HDF2ASFF> {
Expand Down Expand Up @@ -104,19 +103,19 @@ export default class HDF2ASFF extends BaseCommand<typeof HDF2ASFF> {
const outputFolder = flags.output.replace('.json', '') || 'asff-output';
fs.mkdirSync(outputFolder);
if (convertedSlices.length === 1) {
const outfilePath = path.join(
const outfilePath = resolveSafeChild(
outputFolder,
checkSuffix(basename(flags.output)),
safeFilename(checkSuffix(flags.output)),
);
fs.writeFileSync(
outfilePath,
JSON.stringify(convertedSlices[0], null, 2),
);
} else {
for (const [index, slice] of convertedSlices.entries()) {
const outfilePath = path.join(
const outfilePath = resolveSafeChild(
outputFolder,
`${checkSuffix(basename(flags.output || '')).replace('.json', '')}.p${index}.json`,
safeFilename(`${checkSuffix(basename(flags.output || '')).replace('.json', '')}.p${index}.json`),
);
fs.writeFileSync(outfilePath, JSON.stringify(slice, null, 2));
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/convert/hdf2csv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import _ from 'lodash';
import type { Logger } from 'winston';
import type { ControlSetRows } from '../../types/csv';
import { convertRow, csvExportFields } from '../../utils/csv';
import { basename } from '../../utils/global';
import { basename, resolveSafeChild, safeFilename } from '../../utils/global';
import { createWinstonLogger } from '../../utils/logging';
import { BaseCommand } from '../../utils/oclif/base_command';

Expand Down Expand Up @@ -86,7 +86,7 @@ export default class HDF2CSV extends BaseCommand<typeof HDF2CSV> {
if (flags.interactive) {
const interactiveFlags = await this.getFlags();
inputFile = interactiveFlags.inputFile;
outputFile = path.join(interactiveFlags.outputDirectory, interactiveFlags.outputFileName);
outputFile = resolveSafeChild(interactiveFlags.outputDirectory, safeFilename(interactiveFlags.outputFileName));
includeFields = interactiveFlags.fields.join(',');
truncateFields = Boolean(interactiveFlags.truncateFields);
} else if (this.requiredFlagsProvided(flags)) {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/convert/hdf2html.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs';
import path from 'path';
import { FileExportTypes, FromHDFToHTMLMapper as Mapper } from '@mitre/hdf-converters';
import { Command, Flags } from '@oclif/core';
import { basename } from '../../utils/global';

export default class HDF2HTML extends Command {
static readonly usage = 'convert hdf2html -i <hdf-scan-results-json>... -o <output-html> [-t <output-type>] [-h]';
Expand All @@ -22,7 +22,7 @@ export default class HDF2HTML extends Command {
async run() {
const { flags } = await this.parse(HDF2HTML);

const files = flags.input.map((file, i) => ({ data: fs.readFileSync(file, 'utf8'), fileName: path.basename(file), fileID: `${i}` }));
const files = flags.input.map((file, i) => ({ data: fs.readFileSync(file, 'utf8'), fileName: basename(file), fileID: `${i}` }));

const converter = await new Mapper(files, FileExportTypes[flags.type as keyof typeof FileExportTypes]).toHTML();
fs.writeFileSync(flags.output, converter);
Expand Down
20 changes: 12 additions & 8 deletions src/commands/convert/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
ZapMapper,
} from '@mitre/hdf-converters';
import { Flags } from '@oclif/core';
import { basename, checkSuffix } from '../../utils/global';
import { basename, checkSuffix, resolveSafeChild, safeFilename } from '../../utils/global';
import { BaseCommand } from '../../utils/oclif/base_command';
import ASFF2HDF from './asff2hdf';
import Zap2HDF from './zap2hdf';
Expand Down Expand Up @@ -75,7 +75,7 @@ export default class Convert extends BaseCommand<typeof Convert> {
filename: basename(filePath),
});
switch (
Convert.detectedType // skipcq: JS-0047
Convert.detectedType
) {
case 'asff': {
return ASFF2HDF.flags;
Expand Down Expand Up @@ -130,7 +130,7 @@ export default class Convert extends BaseCommand<typeof Convert> {
fs.mkdirSync(flags.output);
_.forOwn(results, (result, filename) => {
fs.writeFileSync(
path.join(flags.output, checkSuffix(basename(filename))),
resolveSafeChild(flags.output, safeFilename(checkSuffix(filename))),
JSON.stringify(result, null, 2),
);
});
Expand Down Expand Up @@ -161,7 +161,7 @@ export default class Convert extends BaseCommand<typeof Convert> {
fs.mkdirSync(flags.output);
for (const [filename, result] of Object.entries(results)) {
fs.writeFileSync(
path.join(flags.output, checkSuffix(basename(filename))),
resolveSafeChild(flags.output, safeFilename(checkSuffix(filename))),
JSON.stringify(result, null, 2),
);
}
Expand Down Expand Up @@ -241,9 +241,11 @@ export default class Convert extends BaseCommand<typeof Convert> {
const result = converter.toHdf();
const pluralResults = Array.isArray(result) ? result : [];
const singularResult = pluralResults.length === 0;
const outputBase = path.dirname(flags.output);
const outputPrefix = safeFilename(flags.output.replaceAll(/\.json/gi, ''));
for (const element of pluralResults) {
fs.writeFileSync(
`${flags.output.replaceAll(/\.json/gi, '')}-${basename(_.get(element, 'platform.target_id') || '')}.json`,
resolveSafeChild(outputBase, safeFilename(`${outputPrefix}-${basename(_.get(element, 'platform.target_id') || '')}.json`)),
JSON.stringify(element, null, 2),
);
}
Expand Down Expand Up @@ -294,9 +296,9 @@ export default class Convert extends BaseCommand<typeof Convert> {
fs.mkdirSync(flags.output);
_.forOwn(results, (result) => {
fs.writeFileSync(
path.join(
resolveSafeChild(
flags.output,
basename(`${_.get(result, 'platform.target_id')}.json`),
safeFilename(`${_.get(result, 'platform.target_id')}.json`),
),
JSON.stringify(result, null, 2),
);
Expand Down Expand Up @@ -327,9 +329,11 @@ export default class Convert extends BaseCommand<typeof Convert> {
const result = converter.toHdf();
const pluralResults = Array.isArray(result) ? result : [];
const singularResult = pluralResults.length === 0;
const outputBase = path.dirname(flags.output);
const outputPrefix = safeFilename(flags.output.replaceAll(/\.json/gi, ''));
for (const element of pluralResults) {
fs.writeFileSync(
`${flags.output.replaceAll(/\.json/gi, '')}-${basename(_.get(element, 'platform.target_id') || '')}.json`,
resolveSafeChild(outputBase, safeFilename(`${outputPrefix}-${basename(_.get(element, 'platform.target_id') || '')}.json`)),
JSON.stringify(element, null, 2),
);
}
Expand Down
Loading
Loading