Skip to content

Add capability for Delta to use an URL or XCCDF packages (.zip) #3482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
65 changes: 39 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1471,41 +1471,51 @@ validate threshold Validate the compliance and status counts of an HD

#### Delta

See the wiki for more information on 👉 [Delta](https://github.com/mitre/saf/wiki/Delta-(WIP)).
See the wiki for more information on 👉 [Delta](https://github.com/mitre/saf/wiki/Delta).

```
Update an existing InSpec profile with updated XCCDF guidance

USAGE
$ saf generate delta [-L info|warn|debug|verbose] [-J <value> | --interactive] [-X <value> | ] [-o <value> | ]
[-O <value> | ] [-r <value> | ] [-T rule|group|cis|version | ] [-M -c <value>]
$ saf generate delta [-h] [-L info|warn|debug|verbose] [-J <value> | --interactive] [-X <value> | -U <value>]
[-o <value> | ] [-O <value> | ] [-r <value> | ] [-T rule|group|cis|version | ] [-M -c <value>]

FLAGS
-J, --inspecJsonFile=<value> (required if not --interactive) Input execution/profile JSON file - can be generated using the "inspec json <profile path> | jq . > profile.json" command
-X, --xccdfXmlFile=<value> (required if not --interactive) The XCCDF XML file containing the new guidance - in the form of .xml file
-o, --deltaOutputDir=<value> (required if not --interactive) The output folder for the updated profile - if not empty it will be overwritten
-J, --inspecJsonFile=<value> InSpec Profile Controls JSON summary file
- can be generated using the "[cinc-auditor or inspec] json <profile path> | jq . > profile.json" command
-M, --runMapControls Run the approximate string matching process
-O, --ovalXmlFile=<value> The OVAL XML file containing definitions used in the new guidance - in the form of .xml file
-T, --idType=<option> [default: rule] Control ID Types:
'rule' - Vulnerability IDs (ex. 'SV-XXXXX'),
'group' - Group IDs (ex. 'V-XXXXX'),
'cis' - CIS Rule IDs (ex. C-1.1.1.1),
'version' - Version IDs (ex. RHEL-07-010020 - also known as STIG IDs)
-T, --idType=<option> [default: rule] Control ID Types: 'rule' - Vulnerability IDs (ex. 'SV-XXXXX'), 'group' - Group IDs (ex. 'V-XXXXX'), 'cis' - CIS Rule IDs
(ex. C-1.1.1.1), 'version' - Version IDs (ex. RHEL-07-010020 - also known as STIG IDs)
<options: rule|group|cis|version>
-M, --runMapControls Run the approximate string matching process
-c, --controlsDir=<value> The InSpec profile directory containing the controls being updated (controls Delta is processing)
-r, --report=<value> Output markdown report file - must have an extension of .md
-U, --xccdfUrl=<value> (required [-X or -U] or --interactive) The URL for the XCCDF package containing the new guidance (.zip, e.g., DISA STIG downloads)
-X, --xccdfXmlFile=<value> (required [-X or -U] or --interactive) The XCCDF File containing the new guidance (.xml or .zip)
-c, --controlsDir=<value> (required with -M or -J not provided) The InSpec profile directory containing the controls being updated (controls Delta is processing)
-o, --deltaOutputDir=<value> (required if not --interactive) The output folder for the updated profile (will contain the controls that delta was applied too)
- if it is not empty, it will be overwritten. Do not use the original controls directory
-r, --reportFile=<value> Output markdown report file - must have an extension of .md

GLOBAL FLAGS
-h, --help Show CLI help
-L, --logLevel=<option> [default: info] Specify level for logging (if implemented by the CLI command)
<options: info|warn|debug|verbose>
<options: info|warn|debug|verbose>
--interactive Collect input tags interactively (not available on all CLI commands)

EXAMPLES
$ saf generate delta -J <profile_json_file.json> -X <xccdf_guidance_file.xml, -o <updated_controls_directory>
Running the CLI interactively
$ saf generate delta --interactive

$ saf generate delta -J <profile_json_file.json> -X <xccdf_guidance_file.xml, -o <updated_controls_directory> -M
-c <controls_directory_being_processed_by_delta>
Providing a XCCDF (File), a Profile Controls Summary, and no Fuzzy matching)
$ saf generate delta -X <xccdf_benchmarks.[xml, zip]>, -J <profile_summary.json> -c <current-controls-dir> -o <updated_controls_dir>, [options]

Providing a XCCDF (URL), a Profile Controls Summary, and no Fuzzy matching)
$ saf generate delta -U <URL-to-benchmark.zip>, -J <profile_summary.json> -c <current-controls-dir> -o <updated_controls_dir>, [options]

Providing a XCCDF (File), a Profile Controls Summary, with Fuzzy matching)
$ saf generate delta -X <xccdf_benchmarks.[xml, zip]>, -J <profile_summary.json> -c <current-controls-dir> -o <updated_controls_dir>, -M, [options]

Providing a XCCDF (URL), a Profile Controls Summary, with Fuzzy matching)
$ saf generate delta -U <URL-to-benchmark.zip>, -J <profile_summary.json> -c <current-controls-dir> -o <updated_controls_dir>, -M, [options]

```
[top](#generate-data-reports-and-more)
Expand All @@ -1515,10 +1525,11 @@ Use this process prior of running `generate delta`. The process updates the cont

```
USAGE
$ saf generate update_controls4delta -X <value> -J <value> -c <value> [-P V|VS] [--[no-]useXccdfGroupId] [--[no-]backupControls] [--[no-]formatControls] [-L info|warn|debug|verbose]
$ saf generate update_controls4delta [-X <value> | -U <value>] -c <value> [-J <value>] [-P V|SV] [-g] [-f] [-b] [-h] [--interactive] [-L info|warn|debug|verbose]

FLAGS
-X, --xccdfXmlFile=<value> (required) The XCCDF XML file containing the new guidance - in the form of an .xml file
-U, --xccdfUrl=<value> (required [-X or -U]) The URL pointing to the XCCDF file containing the new guidance (DISA STIG downloads)
-X, --xccdfXmlFile=<value> (required [-X or -U]) The XCCDF XML file containing the new guidance - in the form of .xml file
-c, --controlsDir=<value> (required) The InSpec profile controls directory containing the profiles to be updated
-J, --inspecJsonFile=<value> Input execution/profile JSON file - can be generated using the "inspec json <profile path> > profile.json"
command. If not provided the `inspec` CLI must be installed
Expand All @@ -1537,11 +1548,13 @@ GLOBAL FLAGS
--interactive Collect input tags interactively (not available on all CLI commands)

EXAMPLES
$ saf generate update_controls4delta -X ./the_xccdf_guidance_file.xml -c the_controls_directory -L debug
$ saf generate update_controls4delta -X ./the_xccdf_guidance_file.xml -c the_controls_directory -g -L debug
$ saf generate update_controls4delta -X ./the_xccdf_guidance_file.xml -J ./the_profile_json -c the_controls_directory -L debug
$ saf generate update_controls4delta -X ./the_xccdf_guidance_file.xml -c the_controls_directory --no-formatControls -P SV -L debug
$ saf generate update_controls4delta -X ./the_xccdf_guidance_file.xml -c the_controls_directory --no-backupControls --no-formatControls -P SV -L debug
Providing an XCCDF File
$ saf generate update_controls4delta -X ./the_xccdf_guidance_file.xml [-J <profile_json_file.json>]
[-c the_controls_directory --no-backupControls --no-formatControls -P <V or SV> -g -L debug]

Providing an URL point to an ZIP XCCDF (from DISA STIG downloads)
$ saf generate update_controls4delta -U <URL to DISA STIGs downloads> [-J <profile_json_file.json>]
[-c the_controls_directory --no-backupControls --no-formatControls -P <V or SV> -g -L debug]

```
[top](#generate-data-reports-and-more)
Expand Down Expand Up @@ -1936,7 +1949,7 @@ EXAMPLES

### NOTICE

© 2022 The MITRE Corporation.
© 2022-2025 The MITRE Corporation.

Approved for Public Release; Distribution Unlimited. Case Number 18-3678.

Expand Down
177 changes: 92 additions & 85 deletions eslint.config.cjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
/* eslint-disable @stylistic/no-tabs */
/* eslint-disable unicorn/no-useless-fallback-in-spread */
/* eslint-disable no-undef */
//-----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// NOTE: The following plugins have been removed from the package.json file
// eslint-plugin-oclif linter repo has been archived
// eslint-config-oclif-typescript linter repo has been archived

// -----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Eslint packages used:
// "eslint" –> The core ESLint package. It is the base linter.
// NOTE: Styles have moved from eslint to @stylistic/eslint-plugin (js and ts)
// Built-in stylistic rules for JavaScript and Typescript
// We are still using legacy config, so we need to install v3.x with
// npm i -D @stylistic/eslint-plugin@3
// We are now using the @stylistic/eslint-plugin v4.x, it requires
// dynamic loading (see notes bellow)
// "@stylistic/eslint-plugin" -> Used for general stylistic rules, which can be
// applied to both JavaScript and TypeScript.
// See here for additional information on ESLint Stylistic Stylistic Formatting
Expand Down Expand Up @@ -44,15 +45,15 @@
//
// "eslint-formatter-stylish" –> A package that provides a stylish formatter for
// ESLint. It’s optional but useful for a more readable output.
//-----------------------------------------------------------------------------
// ----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// TODO: May need to use the chai plugin in the future // skipcq: JS-0099
// npm install --save-dev eslint-plugin-chai-friendly
// And configure it into plugins: { "chai-friendly": chaiPLugin }
//-----------------------------------------------------------------------------
// ----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Eslint rules Settings
// Severity Levels ("off", "warn", "error")
// "off" -> Disables the rule.
Expand All @@ -72,123 +73,129 @@
// "semi": ["error", "always"], // Requires semicolons
// "quotes": ["error", "never"], // Disallows quotes (uses backticks when possible)
// "array-bracket-newline": ["error", { "minItems": 3 }] Forces newline for arrays with 3+ items
//-----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
const eslint = require('@eslint/js')
const nodePlugin = require("eslint-plugin-n")
const stylistic = require('@stylistic/eslint-plugin')
const nodePlugin = require('eslint-plugin-n')
const tsPlugin = require('@typescript-eslint/eslint-plugin')
const tsParser = require('@typescript-eslint/parser')
// const chaiPlugin = require("eslint-plugin-chai-friendly")

//-----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// ESLint configuration
module.exports = [
// Core ESLint settings
{
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
ecmaVersion: 'latest',
sourceType: 'module',
},
},
eslint.configs.recommended, // Recommended JS rules

{
files: ["**/*.ts", "**/*.tsx"],
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: tsParser,
},
plugins: {
"@typescript-eslint": tsPlugin,
'@stylistic': stylistic,
"n": nodePlugin
'@typescript-eslint': tsPlugin,
'n': nodePlugin,
// "chai-friendly": chaiPLugin
},
ignores: [
'/node_modules', // Ignore dependencies
'/lib', // Ignore build output
'/node_modules', // Ignore dependencies
'/lib', // Ignore build output
],
rules: {
"@typescript-eslint/no-explicit-any": "error", // Disallow 'any'
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/typedef": "error",

// stylistic rules
"@stylistic/array-bracket-spacing": ["error", "never"],
"@stylistic/array-bracket-newline": ["warn", { "multiline": true }],
"@stylistic/indent": ["warn", 2, { "SwitchCase": 1 }],
"@stylistic/max-statements-per-line": ["warn", { "max": 1, "ignoredNodes": ['BreakStatement'] }],
"@stylistic/no-multi-spaces": "warn", // Disallow multiple spaces except for alignment
"@stylistic/no-trailing-spaces": "warn",
"@stylistic/object-curly-spacing": ["warn", "never"],
"@stylistic/quotes": ["error", "single", { "avoidEscape": true }], // Allows double quotes (") when escaping single quotes
"@stylistic/semi": ["warn", "never"],
'@typescript-eslint/no-explicit-any': 'error', // Disallow 'any'
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/typedef': 'error',

// eslint rules
"camelcase": "off",
"complexity": ["warn", 30],
"max-nested-callbacks": "warn",
'camelcase': 'off',
'complexity': ['warn', 30],
'max-nested-callbacks': 'warn',

"no-control-regex": "warn",
"no-console": "off",
"no-constant-condition": "warn",
"no-undef": "off",
"no-unused-vars": "warn",
"no-unused-expressions": "error",
"no-await-in-loop": "off",
'no-control-regex': 'warn',
'no-console': 'off',
'no-constant-condition': 'warn',
'no-undef': 'off',
'no-unused-vars': 'warn',
'no-unused-expressions': 'error',
'no-await-in-loop': 'off',

// eslint-plugin-n rules (these were moved from eslint-plugin-node)
"n/exports-style": [
"error",
"exports",
'n/exports-style': [
'error',
'exports',
{
"allowBatchAssign": false
}
'allowBatchAssign': false,
},
],
'n/no-missing-import': 'off',
'n/no-process-exit': 'off',
'n/no-unpublished-import': [
'error', {
'ignoreTypeImport': true,
'ignorePrivate': true,
},
],
"n/no-missing-import": "off",
"n/no-process-exit": "off",
"n/no-unpublished-import": ["error", {
"ignoreTypeImport": true,
"ignorePrivate": true
}],
},
},
// Possible refactor to use unicorn (eslint-plugin-unicorn is an ESM-only module)
// without having to import() dynamically inside an async function. This causes
// node to display this warning:

// Unicorn (eslint-plugin-unicorn) and Stylistic (@stylistic/eslint-plugin)
// are ESM-only module, need to import them dynamically inside an async
// function. This causes node to display this warning:
// (node:11996) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time
// This is cause by: const unicorn = await import("eslint-plugin-unicorn")

// Load unicorn plugin dynamically (comment the code out to preclude unicorn linting)
// Load unicorn and @stylistic plugin dynamically
(async () => {
const unicorn = await import("eslint-plugin-unicorn")
const unicorn = await import('eslint-plugin-unicorn')
const stylisticPlugin = await import('@stylistic/eslint-plugin')

return {
plugins: {
unicorn: unicorn.default, // Use `.default` because it's an ESM module
'@stylistic': stylisticPlugin.default,
},
rules: {
// Ensure that recommended rules exist before accessing them
...(unicorn.default.configs?.recommended?.rules || {}),
"unicorn/better-regex": "off",
"unicorn/consistent-function-scoping": "off",
"unicorn/explicit-length-check": "off",
"unicorn/filename-case": "off",
"unicorn/import-style": "off",
"unicorn/numeric-separators-style": "off",
"unicorn/prefer-node-protocol": "off",
"unicorn/prefer-module": "off",
"unicorn/prefer-code-point": "off",
"unicorn/prefer-json-parse-buffer": "off",
"unicorn/prefer-top-level-await": "off",
"unicorn/prefer-number-properties": "off",
"unicorn/prevent-abbreviations": "off",
"unicorn/no-null": "off",
"unicorn/no-hex-escape": "off",
"unicorn/no-zero-fractions": "off",
"unicorn/no-array-for-each": "off",
"unicorn/no-process-exit": "off",
"unicorn/no-nested-ternary": "off",
"unicorn/no-named-default": "off",
'unicorn/better-regex': 'off',
'unicorn/consistent-function-scoping': 'off',
'unicorn/explicit-length-check': 'off',
'unicorn/filename-case': 'off',
'unicorn/import-style': 'off',
'unicorn/numeric-separators-style': 'off',
'unicorn/prefer-node-protocol': 'off',
'unicorn/prefer-module': 'off',
'unicorn/prefer-code-point': 'off',
'unicorn/prefer-json-parse-buffer': 'off',
'unicorn/prefer-top-level-await': 'off',
'unicorn/prefer-number-properties': 'off',
'unicorn/prevent-abbreviations': 'off',
'unicorn/no-null': 'off',
'unicorn/no-hex-escape': 'off',
'unicorn/no-zero-fractions': 'off',
'unicorn/no-array-for-each': 'off',
'unicorn/no-process-exit': 'off',
'unicorn/no-nested-ternary': 'off',
'unicorn/no-named-default': 'off',

// stylistic rules
...(stylisticPlugin.default.configs?.recommended?.rules || {}),
'@stylistic/array-bracket-spacing': ['error', 'never'],
'@stylistic/array-bracket-newline': ['warn', {'multiline': true}],
'@stylistic/brace-style': ['error', '1tbs', {'allowSingleLine': true}],
'@stylistic/indent': ['warn', 2, {'SwitchCase': 1}],
'@stylistic/block-spacing': 'off',
'@stylistic/quote-props': 'off',
'@stylistic/multiline-ternary': 'off',
'@stylistic/max-statements-per-line': ['warn', {'max': 1, 'ignoredNodes': ['BreakStatement']}],
'@stylistic/no-multi-spaces': 'warn', // Disallow multiple spaces except for alignment
'@stylistic/no-trailing-spaces': 'warn',
'@stylistic/object-curly-spacing': ['warn', 'never'],
'@stylistic/quotes': ['error', 'single', {'avoidEscape': true}], // Allows double quotes (") when escaping single quotes
'@stylistic/semi': ['warn', 'never'],
},
};
}
})(),
]
]
Loading
Loading