@vaultcompass/vault-guard 1.3.0 → 1.4.1
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/dist/cli-entry.js +6 -0
- package/dist/cli.js +5 -3
- package/dist/commands/check.d.ts +1 -1
- package/dist/commands/check.js +2 -2
- package/dist/commands/scan.d.ts +1 -1
- package/dist/commands/scan.js +38 -4
- package/dist/utils/scan-utils.d.ts +1 -1
- package/dist/utils/scan-utils.js +17 -4
- package/package.json +3 -3
package/dist/cli-entry.js
CHANGED
|
@@ -8,6 +8,12 @@ if (nodeMajor < 22) {
|
|
|
8
8
|
'Some features may not work correctly.');
|
|
9
9
|
}
|
|
10
10
|
const program = (0, cli_1.buildCli)();
|
|
11
|
+
// npx (and some wrappers) forward a leading `--` into argv. Commander treats
|
|
12
|
+
// that as end-of-options, so `--format sarif` is ignored and text banners
|
|
13
|
+
// pollute machine-readable stdout. Drop a single leading separator.
|
|
14
|
+
if (process.argv[2] === '--') {
|
|
15
|
+
process.argv.splice(2, 1);
|
|
16
|
+
}
|
|
11
17
|
// Parse arguments and execute command
|
|
12
18
|
program.parseAsync().catch((error) => {
|
|
13
19
|
// Handle errors
|
package/dist/cli.js
CHANGED
|
@@ -79,9 +79,10 @@ function buildCli() {
|
|
|
79
79
|
.argument('[path]', 'Path to scan', '.')
|
|
80
80
|
.option('-f, --format <format>', 'Output format: text | json | sarif', 'text')
|
|
81
81
|
.option('--staged', 'Scan git staged files only (uses index vs HEAD)', false)
|
|
82
|
+
.option('--fail-on <severity>', 'Minimum severity that fails the scan: critical | high | medium | low | none (default: medium, or fail_on in .vault-guard.json)')
|
|
82
83
|
.action(async (path, options) => {
|
|
83
84
|
const format = options.format ?? 'text';
|
|
84
|
-
const exitCode = await (0, scan_1.scanCommand)(path, format, Boolean(options.staged));
|
|
85
|
+
const exitCode = await (0, scan_1.scanCommand)(path, format, Boolean(options.staged), options.failOn);
|
|
85
86
|
setExitCode(exitCode);
|
|
86
87
|
});
|
|
87
88
|
program
|
|
@@ -149,8 +150,9 @@ function buildCli() {
|
|
|
149
150
|
.command('check')
|
|
150
151
|
.description('Scan files with config and baselines')
|
|
151
152
|
.argument('[files...]', 'Files to check')
|
|
152
|
-
.
|
|
153
|
-
|
|
153
|
+
.option('--fail-on <severity>', 'Minimum severity that fails the check: critical | high | medium | low | none (default: medium, or fail_on in .vault-guard.json)')
|
|
154
|
+
.action(async (files, options) => {
|
|
155
|
+
const exitCode = await (0, check_1.checkCommand)(files, options.failOn);
|
|
154
156
|
setExitCode(exitCode);
|
|
155
157
|
});
|
|
156
158
|
program
|
package/dist/commands/check.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function checkCommand(files: string[]): Promise<number>;
|
|
1
|
+
export declare function checkCommand(files: string[], failOn?: string): Promise<number>;
|
package/dist/commands/check.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkCommand = checkCommand;
|
|
4
4
|
const scan_1 = require("./scan");
|
|
5
|
-
async function checkCommand(files) {
|
|
6
|
-
return (0, scan_1.scanCommand)(files.length > 0 ? files : '.', 'text', false);
|
|
5
|
+
async function checkCommand(files, failOn) {
|
|
6
|
+
return (0, scan_1.scanCommand)(files.length > 0 ? files : '.', 'text', false, failOn);
|
|
7
7
|
}
|
package/dist/commands/scan.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type OutputFormat = 'text' | 'json' | 'sarif';
|
|
2
|
-
export declare function scanCommand(targetPath: string | string[], format?: OutputFormat, staged?: boolean): Promise<number>;
|
|
2
|
+
export declare function scanCommand(targetPath: string | string[], format?: OutputFormat, staged?: boolean, failOnFlag?: string): Promise<number>;
|
package/dist/commands/scan.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.scanCommand = scanCommand;
|
|
|
7
7
|
const vault_guard_core_1 = require("@vaultcompass/vault-guard-core");
|
|
8
8
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
9
|
const scan_utils_1 = require("../utils/scan-utils");
|
|
10
|
-
async function scanCommand(targetPath, format = 'text', staged = false) {
|
|
10
|
+
async function scanCommand(targetPath, format = 'text', staged = false, failOnFlag) {
|
|
11
11
|
const cwd = process.cwd();
|
|
12
12
|
const targetPaths = Array.isArray(targetPath) ? targetPath : [targetPath];
|
|
13
13
|
const targetLabel = targetPaths.length === 1 ? targetPaths[0] : `${targetPaths.length} paths`;
|
|
@@ -25,6 +25,18 @@ async function scanCommand(targetPath, format = 'text', staged = false) {
|
|
|
25
25
|
}
|
|
26
26
|
throw e;
|
|
27
27
|
}
|
|
28
|
+
// Resolve the gate threshold before scanning so an invalid value fails fast
|
|
29
|
+
// rather than after a long scan.
|
|
30
|
+
const failOnResolved = (0, vault_guard_core_1.resolveFailOn)(failOnFlag, config.fail_on);
|
|
31
|
+
if (!failOnResolved.ok) {
|
|
32
|
+
console.error(chalk_1.default.red('❌ Invalid fail-on value:'), chalk_1.default.white(failOnResolved.invalid));
|
|
33
|
+
console.error(chalk_1.default.gray(` Expected one of: ${vault_guard_core_1.FAIL_ON_VALUES.join(' | ')}\n`));
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
const failOn = failOnResolved.threshold;
|
|
37
|
+
// True when neither the flag nor the config chose a threshold. Drives the
|
|
38
|
+
// 1.4.0 upgrade notice below: users who picked a value have already decided.
|
|
39
|
+
const gateIsImplicitDefault = failOnFlag === undefined && config.fail_on === undefined;
|
|
28
40
|
const scanner = new vault_guard_core_1.SecretScanner(config);
|
|
29
41
|
// Merge config ignore paths and patterns into a single list for file filtering.
|
|
30
42
|
const configIgnorePatterns = [
|
|
@@ -125,21 +137,36 @@ async function scanCommand(targetPath, format = 'text', staged = false) {
|
|
|
125
137
|
const { results: afterBaseline, suppressed: baselineSuppressed } = (0, vault_guard_core_1.filterResultsByBaseline)(process.cwd(), results, baselineLoad.fingerprints);
|
|
126
138
|
results = afterBaseline;
|
|
127
139
|
const durationMs = Date.now() - t0;
|
|
140
|
+
const totalMatches = results.reduce((n, r) => n + r.matches.length, 0);
|
|
141
|
+
const blocking = (0, vault_guard_core_1.countBlockingMatches)(results, failOn);
|
|
128
142
|
const run = {
|
|
129
143
|
duration_ms: durationMs,
|
|
130
144
|
files_scanned: stats.filesScanned,
|
|
131
145
|
bytes_scanned: stats.bytesScanned,
|
|
132
146
|
patterns_active: scanner.getActivePatternCount(),
|
|
133
147
|
diagnostics_count: diagnostics.length,
|
|
148
|
+
fail_on: failOn,
|
|
149
|
+
blocking_matches: blocking,
|
|
134
150
|
...(baselineSuppressed > 0 ? { baseline_suppressed: baselineSuppressed } : {}),
|
|
135
151
|
};
|
|
152
|
+
// Upgrade notice for the 1.4.0 default change. Before 1.4.0 any finding
|
|
153
|
+
// failed the scan; now the implicit default is `medium`. When that
|
|
154
|
+
// difference is what decides this run's outcome (findings exist, none
|
|
155
|
+
// block, and the user never chose a threshold), say so once on stderr —
|
|
156
|
+
// stderr so JSON/SARIF stdout stays parseable, and only for the implicit
|
|
157
|
+
// default so setting `fail_on` anywhere silences it for good.
|
|
158
|
+
if (gateIsImplicitDefault && totalMatches > 0 && blocking === 0) {
|
|
159
|
+
console.error(chalk_1.default.yellow(`note: earlier vault-guard versions failed on any finding; since 1.4.0 the default gate is "medium".`));
|
|
160
|
+
console.error(chalk_1.default.gray(` This run would have failed before. Set "fail_on" in .vault-guard.json ("low" restores the old\n` +
|
|
161
|
+
` behaviour, "medium" keeps this one) to silence this note.`));
|
|
162
|
+
}
|
|
136
163
|
if (format === 'json') {
|
|
137
164
|
process.stdout.write((0, scan_utils_1.formatJson)(results, { diagnostics, run }) + '\n');
|
|
138
|
-
return
|
|
165
|
+
return blocking === 0 ? 0 : 1;
|
|
139
166
|
}
|
|
140
167
|
if (format === 'sarif') {
|
|
141
168
|
process.stdout.write((0, scan_utils_1.formatSarif)(results, { diagnostics, run }) + '\n');
|
|
142
|
-
return
|
|
169
|
+
return blocking === 0 ? 0 : 1;
|
|
143
170
|
}
|
|
144
171
|
// Text mode: print one-line diagnostic summary when any non-fatal issues occurred
|
|
145
172
|
if (diagnostics.length > 0) {
|
|
@@ -149,7 +176,14 @@ async function scanCommand(targetPath, format = 'text', staged = false) {
|
|
|
149
176
|
console.log(chalk_1.default.green.bold('✅ SUCCESS:'), chalk_1.default.white('No secrets found\n'));
|
|
150
177
|
return 0;
|
|
151
178
|
}
|
|
152
|
-
(0, scan_utils_1.displayScanResults)(results);
|
|
179
|
+
(0, scan_utils_1.displayScanResults)(results, blocking);
|
|
180
|
+
if (blocking === 0) {
|
|
181
|
+
// Findings exist but all sit below the gate. Say so explicitly — a silent
|
|
182
|
+
// exit 0 after printing findings reads like a bug.
|
|
183
|
+
console.log(chalk_1.default.white(`${totalMatches} finding(s), none at or above severity "${failOn}" — not failing the gate.`));
|
|
184
|
+
console.log(chalk_1.default.gray(` Tighten with --fail-on low or "fail_on": "low" in .vault-guard.json\n`));
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
153
187
|
return 1;
|
|
154
188
|
}
|
|
155
189
|
catch (error) {
|
|
@@ -70,4 +70,4 @@ export declare function scanFiles(targetPaths: string[], scanner: SecretScanner,
|
|
|
70
70
|
* - The redacted match value (`sk-a…(37c)`) is shown last and intentionally
|
|
71
71
|
* low-information.
|
|
72
72
|
*/
|
|
73
|
-
export declare function displayScanResults(results: ScanResult[]): void;
|
|
73
|
+
export declare function displayScanResults(results: ScanResult[], blocking?: number): void;
|
package/dist/utils/scan-utils.js
CHANGED
|
@@ -296,13 +296,22 @@ function scanFiles(targetPaths, scanner, options = {}) {
|
|
|
296
296
|
* - The redacted match value (`sk-a…(37c)`) is shown last and intentionally
|
|
297
297
|
* low-information.
|
|
298
298
|
*/
|
|
299
|
-
function displayScanResults(results) {
|
|
299
|
+
function displayScanResults(results, blocking) {
|
|
300
300
|
if (results.length === 0) {
|
|
301
301
|
console.log(chalk_1.default.green.bold('✅ SUCCESS:'), chalk_1.default.white('No secrets found\n'));
|
|
302
302
|
return;
|
|
303
303
|
}
|
|
304
304
|
const totalSecrets = results.reduce((sum, r) => sum + r.matches.length, 0);
|
|
305
|
-
|
|
305
|
+
// `blocking` is how many findings sit at or above the `--fail-on` threshold.
|
|
306
|
+
// When none do we still list everything, but the headline must not say
|
|
307
|
+
// "BLOCKED" over a run that is about to exit 0.
|
|
308
|
+
const willBlock = blocking === undefined || blocking > 0;
|
|
309
|
+
if (willBlock) {
|
|
310
|
+
console.log(chalk_1.default.red.bold('🚨 BLOCKED:'), chalk_1.default.white(`Found ${totalSecrets} secret${totalSecrets > 1 ? 's' : ''}\n`));
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
console.log(chalk_1.default.yellow.bold('⚠️ REPORT:'), chalk_1.default.white(`Found ${totalSecrets} finding${totalSecrets > 1 ? 's' : ''} below the fail threshold\n`));
|
|
314
|
+
}
|
|
306
315
|
for (const { file, matches } of results) {
|
|
307
316
|
const relativePath = relativeForDisplay(file);
|
|
308
317
|
for (const match of matches) {
|
|
@@ -313,7 +322,9 @@ function displayScanResults(results) {
|
|
|
313
322
|
}
|
|
314
323
|
}
|
|
315
324
|
console.log('');
|
|
316
|
-
|
|
325
|
+
if (willBlock) {
|
|
326
|
+
console.log(chalk_1.default.red.bold('❌ BLOCKED:'), chalk_1.default.white('Commit blocked — remove secrets before pushing\n'));
|
|
327
|
+
}
|
|
317
328
|
}
|
|
318
329
|
/** cwd-relative when inside cwd, absolute otherwise. Matches scan-output behaviour. */
|
|
319
330
|
function relativeForDisplay(file) {
|
|
@@ -346,8 +357,10 @@ function getSeverityEmoji(severity) {
|
|
|
346
357
|
return '⚠️';
|
|
347
358
|
case 'medium':
|
|
348
359
|
return 'ℹ️';
|
|
360
|
+
// Not a checkmark: every line here is a finding, and a green tick beside
|
|
361
|
+
// one reads as "this file is clean".
|
|
349
362
|
case 'low':
|
|
350
|
-
return '
|
|
363
|
+
return '🔵';
|
|
351
364
|
default:
|
|
352
365
|
return '•';
|
|
353
366
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaultcompass/vault-guard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Block secrets at commit and in CI. Pre-commit hooks, SARIF output, and fast staged-file scans for AI-native workflows.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"chalk": "^4.1.2",
|
|
38
38
|
"commander": "^12.0.0",
|
|
39
|
-
"@vaultcompass/vault-guard-core": "1.
|
|
40
|
-
"@vaultcompass/vault-guard-telemetry": "1.
|
|
39
|
+
"@vaultcompass/vault-guard-core": "1.4.1",
|
|
40
|
+
"@vaultcompass/vault-guard-telemetry": "1.4.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/jest": "^30.0.0",
|