@vaultcompass/vault-guard-core 1.0.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.
- package/LICENSE +21 -0
- package/dist/baseline.d.ts +24 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +87 -0
- package/dist/baseline.js.map +1 -0
- package/dist/config-validate.d.ts +13 -0
- package/dist/config-validate.d.ts.map +1 -0
- package/dist/config-validate.js +111 -0
- package/dist/config-validate.js.map +1 -0
- package/dist/config.d.ts +69 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +106 -0
- package/dist/config.js.map +1 -0
- package/dist/diagnostics.d.ts +64 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +59 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/errors.d.ts +63 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +98 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/match-fingerprint.d.ts +7 -0
- package/dist/match-fingerprint.d.ts.map +1 -0
- package/dist/match-fingerprint.js +28 -0
- package/dist/match-fingerprint.js.map +1 -0
- package/dist/scan-output.d.ts +65 -0
- package/dist/scan-output.d.ts.map +1 -0
- package/dist/scan-output.js +140 -0
- package/dist/scan-output.js.map +1 -0
- package/dist/scanners/index.d.ts +5 -0
- package/dist/scanners/index.d.ts.map +1 -0
- package/dist/scanners/index.js +21 -0
- package/dist/scanners/index.js.map +1 -0
- package/dist/scanners/pre-commit-hook.d.ts +41 -0
- package/dist/scanners/pre-commit-hook.d.ts.map +1 -0
- package/dist/scanners/pre-commit-hook.js +389 -0
- package/dist/scanners/pre-commit-hook.js.map +1 -0
- package/dist/scanners/secret-scanner.d.ts +99 -0
- package/dist/scanners/secret-scanner.d.ts.map +1 -0
- package/dist/scanners/secret-scanner.js +422 -0
- package/dist/scanners/secret-scanner.js.map +1 -0
- package/dist/scanners/token-counter.d.ts +27 -0
- package/dist/scanners/token-counter.d.ts.map +1 -0
- package/dist/scanners/token-counter.js +121 -0
- package/dist/scanners/token-counter.js.map +1 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/entropy.d.ts +17 -0
- package/dist/utils/entropy.d.ts.map +1 -0
- package/dist/utils/entropy.js +35 -0
- package/dist/utils/entropy.js.map +1 -0
- package/dist/utils/file-utils.d.ts +39 -0
- package/dist/utils/file-utils.d.ts.map +1 -0
- package/dist/utils/file-utils.js +442 -0
- package/dist/utils/file-utils.js.map +1 -0
- package/dist/utils/git-utils.d.ts +12 -0
- package/dist/utils/git-utils.d.ts.map +1 -0
- package/dist/utils/git-utils.js +55 -0
- package/dist/utils/git-utils.js.map +1 -0
- package/dist/utils/path-severity.d.ts +17 -0
- package/dist/utils/path-severity.d.ts.map +1 -0
- package/dist/utils/path-severity.js +96 -0
- package/dist/utils/path-severity.js.map +1 -0
- package/dist/utils/placeholder.d.ts +53 -0
- package/dist/utils/placeholder.d.ts.map +1 -0
- package/dist/utils/placeholder.js +198 -0
- package/dist/utils/placeholder.js.map +1 -0
- package/dist/utils/regex-safety.d.ts +102 -0
- package/dist/utils/regex-safety.d.ts.map +1 -0
- package/dist/utils/regex-safety.js +193 -0
- package/dist/utils/regex-safety.js.map +1 -0
- package/dist/utils/scan-file.d.ts +29 -0
- package/dist/utils/scan-file.d.ts.map +1 -0
- package/dist/utils/scan-file.js +125 -0
- package/dist/utils/scan-file.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vault & Compass LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FileScanResult } from './scan-output';
|
|
2
|
+
/** Default baseline filename (same discovery walk as `.vault-guard.json`). */
|
|
3
|
+
export declare const BASELINE_FILENAME = ".vault-guard.baseline.json";
|
|
4
|
+
export interface BaselineFileV1 {
|
|
5
|
+
version: 1;
|
|
6
|
+
fingerprints: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface LoadBaselineOutcome {
|
|
9
|
+
/** Absolute path of the baseline file that was read, if any. */
|
|
10
|
+
sourcePath?: string;
|
|
11
|
+
fingerprints: Set<string>;
|
|
12
|
+
/** Present when a baseline file existed but JSON was invalid or wrong shape. */
|
|
13
|
+
parseError?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Walk the same directories as {@link loadConfig} and load the nearest
|
|
17
|
+
* `.vault-guard.baseline.json`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadBaseline(startDir?: string): LoadBaselineOutcome;
|
|
20
|
+
export declare function filterResultsByBaseline(cwd: string | null, results: FileScanResult[], baseline: Set<string>): {
|
|
21
|
+
results: FileScanResult[];
|
|
22
|
+
suppressed: number;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=baseline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAIpD,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,+BAA+B,CAAC;AAE9D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,GAAE,MAAsB,GAAG,mBAAmB,CA+ClF;AAED,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,OAAO,EAAE,cAAc,EAAE,EACzB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GACpB;IAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAiBnD"}
|
package/dist/baseline.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BASELINE_FILENAME = void 0;
|
|
7
|
+
exports.loadBaseline = loadBaseline;
|
|
8
|
+
exports.filterResultsByBaseline = filterResultsByBaseline;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const config_1 = require("./config");
|
|
12
|
+
const match_fingerprint_1 = require("./match-fingerprint");
|
|
13
|
+
/** Default baseline filename (same discovery walk as `.vault-guard.json`). */
|
|
14
|
+
exports.BASELINE_FILENAME = '.vault-guard.baseline.json';
|
|
15
|
+
/**
|
|
16
|
+
* Walk the same directories as {@link loadConfig} and load the nearest
|
|
17
|
+
* `.vault-guard.baseline.json`.
|
|
18
|
+
*/
|
|
19
|
+
function loadBaseline(startDir = process.cwd()) {
|
|
20
|
+
const dirs = (0, config_1.listConfigSearchDirs)(startDir);
|
|
21
|
+
for (const dir of dirs) {
|
|
22
|
+
const filePath = path_1.default.join(dir, exports.BASELINE_FILENAME);
|
|
23
|
+
if (!fs_1.default.existsSync(filePath))
|
|
24
|
+
continue;
|
|
25
|
+
let raw;
|
|
26
|
+
try {
|
|
27
|
+
raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
31
|
+
return {
|
|
32
|
+
sourcePath: filePath,
|
|
33
|
+
fingerprints: new Set(),
|
|
34
|
+
parseError: `read failed: ${detail}`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
40
|
+
return { sourcePath: filePath, fingerprints: new Set(), parseError: 'not an object' };
|
|
41
|
+
}
|
|
42
|
+
const v = parsed.version;
|
|
43
|
+
const fps = parsed.fingerprints;
|
|
44
|
+
if (v !== 1) {
|
|
45
|
+
return {
|
|
46
|
+
sourcePath: filePath,
|
|
47
|
+
fingerprints: new Set(),
|
|
48
|
+
parseError: `unsupported version (expected 1, got ${String(v)})`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(fps)) {
|
|
52
|
+
return { sourcePath: filePath, fingerprints: new Set(), parseError: 'fingerprints must be an array' };
|
|
53
|
+
}
|
|
54
|
+
const out = new Set();
|
|
55
|
+
for (const x of fps) {
|
|
56
|
+
if (typeof x === 'string' && x.length > 0)
|
|
57
|
+
out.add(x);
|
|
58
|
+
}
|
|
59
|
+
return { sourcePath: filePath, fingerprints: out };
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
63
|
+
return { sourcePath: filePath, fingerprints: new Set(), parseError: `JSON: ${detail}` };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { fingerprints: new Set() };
|
|
67
|
+
}
|
|
68
|
+
function filterResultsByBaseline(cwd, results, baseline) {
|
|
69
|
+
if (baseline.size === 0)
|
|
70
|
+
return { results, suppressed: 0 };
|
|
71
|
+
let suppressed = 0;
|
|
72
|
+
const out = [];
|
|
73
|
+
for (const r of results) {
|
|
74
|
+
const kept = [];
|
|
75
|
+
for (const m of r.matches) {
|
|
76
|
+
const fp = (0, match_fingerprint_1.fingerprintForMatch)(cwd, r.file, m);
|
|
77
|
+
if (baseline.has(fp))
|
|
78
|
+
suppressed++;
|
|
79
|
+
else
|
|
80
|
+
kept.push(m);
|
|
81
|
+
}
|
|
82
|
+
if (kept.length > 0)
|
|
83
|
+
out.push({ file: r.file, matches: kept });
|
|
84
|
+
}
|
|
85
|
+
return { results: out, suppressed };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { VaultGuardConfig } from './config';
|
|
2
|
+
/**
|
|
3
|
+
* Structural validation for parsed `.vault-guard.json` (no file I/O).
|
|
4
|
+
* Used by `vault-guard config validate` and for tooling that cannot run the scanner.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateVaultGuardConfig(value: unknown): {
|
|
7
|
+
ok: true;
|
|
8
|
+
config: VaultGuardConfig;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
errors: string[];
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=config-validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-validate.d.ts","sourceRoot":"","sources":["../src/config-validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAIjD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAoGjI"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateVaultGuardConfig = validateVaultGuardConfig;
|
|
4
|
+
const SEVERITIES = new Set(['critical', 'high', 'medium', 'low', 'off']);
|
|
5
|
+
/**
|
|
6
|
+
* Structural validation for parsed `.vault-guard.json` (no file I/O).
|
|
7
|
+
* Used by `vault-guard config validate` and for tooling that cannot run the scanner.
|
|
8
|
+
*/
|
|
9
|
+
function validateVaultGuardConfig(value) {
|
|
10
|
+
const errors = [];
|
|
11
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
12
|
+
return { ok: false, errors: ['root must be a JSON object'] };
|
|
13
|
+
}
|
|
14
|
+
const o = value;
|
|
15
|
+
for (const key of Object.keys(o)) {
|
|
16
|
+
const allowed = new Set([
|
|
17
|
+
'ignore',
|
|
18
|
+
'severity_overrides',
|
|
19
|
+
'extra_patterns',
|
|
20
|
+
'extra_patterns_unsafe',
|
|
21
|
+
'entropy_threshold',
|
|
22
|
+
]);
|
|
23
|
+
if (!allowed.has(key)) {
|
|
24
|
+
errors.push(`unknown top-level key: ${JSON.stringify(key)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (o.ignore !== undefined) {
|
|
28
|
+
if (o.ignore === null || typeof o.ignore !== 'object' || Array.isArray(o.ignore)) {
|
|
29
|
+
errors.push('ignore must be an object');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const ig = o.ignore;
|
|
33
|
+
for (const k of Object.keys(ig)) {
|
|
34
|
+
if (k !== 'paths' && k !== 'patterns') {
|
|
35
|
+
errors.push(`ignore: unknown key ${JSON.stringify(k)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (ig.paths !== undefined && !isStringArray(ig.paths)) {
|
|
39
|
+
errors.push('ignore.paths must be an array of strings');
|
|
40
|
+
}
|
|
41
|
+
if (ig.patterns !== undefined && !isStringArray(ig.patterns)) {
|
|
42
|
+
errors.push('ignore.patterns must be an array of strings');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (o.severity_overrides !== undefined) {
|
|
47
|
+
if (o.severity_overrides === null || typeof o.severity_overrides !== 'object' || Array.isArray(o.severity_overrides)) {
|
|
48
|
+
errors.push('severity_overrides must be an object');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
for (const [id, sev] of Object.entries(o.severity_overrides)) {
|
|
52
|
+
if (typeof id !== 'string' || id.length === 0) {
|
|
53
|
+
errors.push('severity_overrides keys must be non-empty strings');
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (typeof sev !== 'string' || !SEVERITIES.has(sev)) {
|
|
57
|
+
errors.push(`severity_overrides[${JSON.stringify(id)}] must be critical|high|medium|low|off`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (o.extra_patterns_unsafe !== undefined && typeof o.extra_patterns_unsafe !== 'boolean') {
|
|
63
|
+
errors.push('extra_patterns_unsafe must be a boolean');
|
|
64
|
+
}
|
|
65
|
+
if (o.entropy_threshold !== undefined) {
|
|
66
|
+
if (typeof o.entropy_threshold !== 'number' || !Number.isFinite(o.entropy_threshold)) {
|
|
67
|
+
errors.push('entropy_threshold must be a finite number');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (o.extra_patterns !== undefined) {
|
|
71
|
+
if (!Array.isArray(o.extra_patterns)) {
|
|
72
|
+
errors.push('extra_patterns must be an array');
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
o.extra_patterns.forEach((entry, i) => {
|
|
76
|
+
if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
77
|
+
errors.push(`extra_patterns[${i}] must be an object`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const ep = entry;
|
|
81
|
+
if (typeof ep.id !== 'string' || ep.id.length === 0) {
|
|
82
|
+
errors.push(`extra_patterns[${i}].id must be a non-empty string`);
|
|
83
|
+
}
|
|
84
|
+
if (typeof ep.regex !== 'string' || ep.regex.length === 0) {
|
|
85
|
+
errors.push(`extra_patterns[${i}].regex must be a non-empty string`);
|
|
86
|
+
}
|
|
87
|
+
if (typeof ep.severity !== 'string' || !isSeverity(ep.severity)) {
|
|
88
|
+
errors.push(`extra_patterns[${i}].severity must be critical|high|medium|low`);
|
|
89
|
+
}
|
|
90
|
+
if (ep.description !== undefined && typeof ep.description !== 'string') {
|
|
91
|
+
errors.push(`extra_patterns[${i}].description must be a string`);
|
|
92
|
+
}
|
|
93
|
+
if (ep.min_entropy !== undefined) {
|
|
94
|
+
if (typeof ep.min_entropy !== 'number' || !Number.isFinite(ep.min_entropy)) {
|
|
95
|
+
errors.push(`extra_patterns[${i}].min_entropy must be a finite number`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (errors.length > 0)
|
|
102
|
+
return { ok: false, errors };
|
|
103
|
+
return { ok: true, config: value };
|
|
104
|
+
}
|
|
105
|
+
function isStringArray(v) {
|
|
106
|
+
return Array.isArray(v) && v.every(x => typeof x === 'string');
|
|
107
|
+
}
|
|
108
|
+
function isSeverity(v) {
|
|
109
|
+
return v === 'critical' || v === 'high' || v === 'medium' || v === 'low';
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=config-validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-validate.js","sourceRoot":"","sources":["../src/config-validate.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { SecretMatch } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Shape of .vault-guard.json in a repository root.
|
|
4
|
+
*
|
|
5
|
+
* YAML support (.vault-guard.yml) deferred to a follow-up to avoid adding a runtime
|
|
6
|
+
* dependency on the core package.
|
|
7
|
+
*/
|
|
8
|
+
export interface VaultGuardConfig {
|
|
9
|
+
/** Glob patterns or file paths to skip entirely during scanning. */
|
|
10
|
+
ignore?: {
|
|
11
|
+
paths?: string[];
|
|
12
|
+
patterns?: string[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Override severity for built-in pattern keys, or set to "off" to disable a
|
|
16
|
+
* pattern entirely. Keys match the pattern id strings in secret-scanner.ts.
|
|
17
|
+
*/
|
|
18
|
+
severity_overrides?: Record<string, SecretMatch['severity'] | 'off'>;
|
|
19
|
+
/**
|
|
20
|
+
* Additional patterns to run alongside the built-in set. Regex strings are
|
|
21
|
+
* compiled at scanner construction time and validated by
|
|
22
|
+
* `utils/regex-safety.ts` against catastrophic-backtracking shapes.
|
|
23
|
+
*/
|
|
24
|
+
extra_patterns?: Array<{
|
|
25
|
+
id: string;
|
|
26
|
+
regex: string;
|
|
27
|
+
severity: SecretMatch['severity'];
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Optional minimum Shannon entropy (bits/char) for the matched value. */
|
|
30
|
+
min_entropy?: number;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Bypass the ReDoS-safety heuristic for `extra_patterns`. Length cap still
|
|
34
|
+
* applies as a backstop. Set this only if you have audited every pattern
|
|
35
|
+
* and accept the catastrophic-backtracking risk.
|
|
36
|
+
*/
|
|
37
|
+
extra_patterns_unsafe?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Override the default Shannon entropy threshold (3.5 bits/char) used by
|
|
40
|
+
* generic catch-all patterns. Lower = more matches but more false positives.
|
|
41
|
+
*/
|
|
42
|
+
entropy_threshold?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Directories searched for `.vault-guard.json` / baseline files, in order
|
|
46
|
+
* (nearest to `startDir` first). Same policy as {@link loadConfig}.
|
|
47
|
+
*/
|
|
48
|
+
export declare function listConfigSearchDirs(startDir?: string): string[];
|
|
49
|
+
/**
|
|
50
|
+
* Load the nearest Vault Guard config file.
|
|
51
|
+
*
|
|
52
|
+
* Search policy (security-relevant):
|
|
53
|
+
* - If `startDir` is inside a git repository: search from `startDir` up to
|
|
54
|
+
* the repo root (inclusive). Never ascend past `.git` — a config in a
|
|
55
|
+
* parent directory of the repo root could change what counts as a secret
|
|
56
|
+
* for the user (severity overrides, extra patterns) without their consent.
|
|
57
|
+
* - If `startDir` is NOT inside a git repository: search **only** `startDir`.
|
|
58
|
+
* This prevents accidental loading of `~/.vault-guard.json` (or any other
|
|
59
|
+
* ancestor) when the user runs the CLI in `/tmp` or similar.
|
|
60
|
+
*
|
|
61
|
+
* Throws `ConfigError` on JSON parse failure (do not fail silent — a typo in
|
|
62
|
+
* `.vault-guard.json` is indistinguishable from "no config" if we swallow it).
|
|
63
|
+
*/
|
|
64
|
+
/**
|
|
65
|
+
* First existing config path on the {@link listConfigSearchDirs} walk, or `null`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function findVaultGuardConfigPath(startDir?: string): string | null;
|
|
68
|
+
export declare function loadConfig(startDir?: string): VaultGuardConfig;
|
|
69
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGtC;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,CAAC;IACrE;;;;OAIG;IACH,cAAc,CAAC,EAAE,KAAK,CAAC;QACrB,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;QAClC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,0EAA0E;QAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IACH;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAgCD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,GAAE,MAAsB,GAAG,MAAM,EAAE,CAG/E;AAED;;;;;;;;;;;;;;GAcG;AACH;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CASxF;AAED,wBAAgB,UAAU,CAAC,QAAQ,GAAE,MAAsB,GAAG,gBAAgB,CAgC7E"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.listConfigSearchDirs = listConfigSearchDirs;
|
|
7
|
+
exports.findVaultGuardConfigPath = findVaultGuardConfigPath;
|
|
8
|
+
exports.loadConfig = loadConfig;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
const CONFIG_FILENAMES = ['.vault-guard.json', '.vault-guard.local.json'];
|
|
13
|
+
/**
|
|
14
|
+
* Walk up from `startDir` looking for a `.git` entry (directory in regular
|
|
15
|
+
* checkouts, file in worktrees). Returns the first directory containing one,
|
|
16
|
+
* or `null` if no git repo is found before the filesystem root.
|
|
17
|
+
*/
|
|
18
|
+
function findRepoRoot(startDir) {
|
|
19
|
+
let dir = startDir;
|
|
20
|
+
while (true) {
|
|
21
|
+
if (fs_1.default.existsSync(path_1.default.join(dir, '.git')))
|
|
22
|
+
return dir;
|
|
23
|
+
const parent = path_1.default.dirname(dir);
|
|
24
|
+
if (parent === dir)
|
|
25
|
+
return null;
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Build `[startDir, ..., root]` (inclusive on both ends). */
|
|
30
|
+
function ascendInclusive(startDir, root) {
|
|
31
|
+
const out = [];
|
|
32
|
+
let dir = startDir;
|
|
33
|
+
while (true) {
|
|
34
|
+
out.push(dir);
|
|
35
|
+
if (dir === root)
|
|
36
|
+
return out;
|
|
37
|
+
const parent = path_1.default.dirname(dir);
|
|
38
|
+
if (parent === dir)
|
|
39
|
+
return out;
|
|
40
|
+
dir = parent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Directories searched for `.vault-guard.json` / baseline files, in order
|
|
45
|
+
* (nearest to `startDir` first). Same policy as {@link loadConfig}.
|
|
46
|
+
*/
|
|
47
|
+
function listConfigSearchDirs(startDir = process.cwd()) {
|
|
48
|
+
const repoRoot = findRepoRoot(startDir);
|
|
49
|
+
return repoRoot ? ascendInclusive(startDir, repoRoot) : [startDir];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Load the nearest Vault Guard config file.
|
|
53
|
+
*
|
|
54
|
+
* Search policy (security-relevant):
|
|
55
|
+
* - If `startDir` is inside a git repository: search from `startDir` up to
|
|
56
|
+
* the repo root (inclusive). Never ascend past `.git` — a config in a
|
|
57
|
+
* parent directory of the repo root could change what counts as a secret
|
|
58
|
+
* for the user (severity overrides, extra patterns) without their consent.
|
|
59
|
+
* - If `startDir` is NOT inside a git repository: search **only** `startDir`.
|
|
60
|
+
* This prevents accidental loading of `~/.vault-guard.json` (or any other
|
|
61
|
+
* ancestor) when the user runs the CLI in `/tmp` or similar.
|
|
62
|
+
*
|
|
63
|
+
* Throws `ConfigError` on JSON parse failure (do not fail silent — a typo in
|
|
64
|
+
* `.vault-guard.json` is indistinguishable from "no config" if we swallow it).
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* First existing config path on the {@link listConfigSearchDirs} walk, or `null`.
|
|
68
|
+
*/
|
|
69
|
+
function findVaultGuardConfigPath(startDir = process.cwd()) {
|
|
70
|
+
const dirs = listConfigSearchDirs(startDir);
|
|
71
|
+
for (const dir of dirs) {
|
|
72
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
73
|
+
const filePath = path_1.default.join(dir, filename);
|
|
74
|
+
if (fs_1.default.existsSync(filePath))
|
|
75
|
+
return filePath;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
function loadConfig(startDir = process.cwd()) {
|
|
81
|
+
const dirs = listConfigSearchDirs(startDir);
|
|
82
|
+
for (const dir of dirs) {
|
|
83
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
84
|
+
const filePath = path_1.default.join(dir, filename);
|
|
85
|
+
if (!fs_1.default.existsSync(filePath))
|
|
86
|
+
continue;
|
|
87
|
+
let raw;
|
|
88
|
+
try {
|
|
89
|
+
raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
93
|
+
throw new errors_1.ConfigError(`Failed to read Vault Guard config at ${filePath}: ${detail}`, filePath);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(raw);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
100
|
+
throw new errors_1.ConfigError(`Failed to parse Vault Guard config at ${filePath}: ${detail}`, filePath);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DiagnosticBus — lightweight structured error channel for vault-guard.
|
|
3
|
+
*
|
|
4
|
+
* Motivation (Audit §14): the codebase had pervasive `catch {}` silent swallows.
|
|
5
|
+
* For a security tool, every silent fallback is an undetected miss:
|
|
6
|
+
* - A corrupt `.vault-guard.json` silently reverts to defaults
|
|
7
|
+
* - `git diff --cached` failing produces a false ✅ on pre-commit
|
|
8
|
+
* - A `>10 MB` file is skipped without any output
|
|
9
|
+
* - A ReDoS-unsafe `extra_pattern` is dropped with no feedback
|
|
10
|
+
*
|
|
11
|
+
* Instead of adding a third-party logging dep, we funnel every "would have
|
|
12
|
+
* been silently swallowed" event through this typed channel, emit it in
|
|
13
|
+
* JSON/SARIF output, and print a one-line summary in text mode.
|
|
14
|
+
*/
|
|
15
|
+
export type DiagnosticSeverity = 'warning' | 'error';
|
|
16
|
+
export type DiagnosticCode = 'config.parse_error' | 'config.read_error' | 'config.unsafe_extra_pattern' | 'pattern.invalid' | 'pattern.too_long' | 'pattern.redos_unsafe' | 'file.too_large' | 'file.line_too_long' | 'file.read_error' | 'fs.permission_denied' | 'git.staged_files_failed' | 'baseline.invalid';
|
|
17
|
+
/**
|
|
18
|
+
* Structured non-fatal warning/error emitted during a scan.
|
|
19
|
+
*/
|
|
20
|
+
export interface Diagnostic {
|
|
21
|
+
/** Stable machine-readable diagnostic identifier. */
|
|
22
|
+
code: DiagnosticCode;
|
|
23
|
+
/** Severity level for UI/reporting surfaces. */
|
|
24
|
+
severity: DiagnosticSeverity;
|
|
25
|
+
/**
|
|
26
|
+
* Free-form context payload (file path, error message, pattern id, etc.).
|
|
27
|
+
*
|
|
28
|
+
* Keep values JSON-serializable because this object is emitted in JSON and SARIF.
|
|
29
|
+
*/
|
|
30
|
+
ctx: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Collects diagnostics during a single scan run.
|
|
34
|
+
*
|
|
35
|
+
* Callers add diagnostics with `bus.add(...)`. At the end of a run the CLI
|
|
36
|
+
* calls `bus.drain()` to retrieve them all and include them in JSON/SARIF
|
|
37
|
+
* output and/or print a summary to stderr.
|
|
38
|
+
*
|
|
39
|
+
* Intentionally not a singleton — one bus per `scanCommand` / `proxyCommand`
|
|
40
|
+
* invocation so that concurrent processes don't share state.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const bus = new DiagnosticBus();
|
|
45
|
+
* bus.add({
|
|
46
|
+
* code: 'file.too_large',
|
|
47
|
+
* severity: 'warning',
|
|
48
|
+
* ctx: { file: 'assets/dump.bin', bytes: 73400320 },
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* const diagnostics = bus.drain();
|
|
52
|
+
* // Pass diagnostics to formatJson/formatSarif.
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare class DiagnosticBus {
|
|
56
|
+
private readonly items;
|
|
57
|
+
add(d: Diagnostic): void;
|
|
58
|
+
/** Returns all diagnostics and clears the bus. */
|
|
59
|
+
drain(): Diagnostic[];
|
|
60
|
+
/** Returns all diagnostics without clearing. */
|
|
61
|
+
peek(): readonly Diagnostic[];
|
|
62
|
+
get size(): number;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAAC;AAErD,MAAM,MAAM,cAAc,GACtB,oBAAoB,GACpB,mBAAmB,GACnB,6BAA6B,GAC7B,iBAAiB,GACjB,kBAAkB,GAClB,sBAAsB,GACtB,gBAAgB,GAChB,oBAAoB,GACpB,iBAAiB,GACjB,sBAAsB,GACtB,yBAAyB,GACzB,kBAAkB,CAAC;AAEvB;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,qDAAqD;IACrD,IAAI,EAAE,cAAc,CAAC;IACrB,gDAAgD;IAChD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAE1C,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI;IAIxB,kDAAkD;IAClD,KAAK,IAAI,UAAU,EAAE;IAIrB,gDAAgD;IAChD,IAAI,IAAI,SAAS,UAAU,EAAE;IAI7B,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DiagnosticBus — lightweight structured error channel for vault-guard.
|
|
4
|
+
*
|
|
5
|
+
* Motivation (Audit §14): the codebase had pervasive `catch {}` silent swallows.
|
|
6
|
+
* For a security tool, every silent fallback is an undetected miss:
|
|
7
|
+
* - A corrupt `.vault-guard.json` silently reverts to defaults
|
|
8
|
+
* - `git diff --cached` failing produces a false ✅ on pre-commit
|
|
9
|
+
* - A `>10 MB` file is skipped without any output
|
|
10
|
+
* - A ReDoS-unsafe `extra_pattern` is dropped with no feedback
|
|
11
|
+
*
|
|
12
|
+
* Instead of adding a third-party logging dep, we funnel every "would have
|
|
13
|
+
* been silently swallowed" event through this typed channel, emit it in
|
|
14
|
+
* JSON/SARIF output, and print a one-line summary in text mode.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.DiagnosticBus = void 0;
|
|
18
|
+
/**
|
|
19
|
+
* Collects diagnostics during a single scan run.
|
|
20
|
+
*
|
|
21
|
+
* Callers add diagnostics with `bus.add(...)`. At the end of a run the CLI
|
|
22
|
+
* calls `bus.drain()` to retrieve them all and include them in JSON/SARIF
|
|
23
|
+
* output and/or print a summary to stderr.
|
|
24
|
+
*
|
|
25
|
+
* Intentionally not a singleton — one bus per `scanCommand` / `proxyCommand`
|
|
26
|
+
* invocation so that concurrent processes don't share state.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const bus = new DiagnosticBus();
|
|
31
|
+
* bus.add({
|
|
32
|
+
* code: 'file.too_large',
|
|
33
|
+
* severity: 'warning',
|
|
34
|
+
* ctx: { file: 'assets/dump.bin', bytes: 73400320 },
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* const diagnostics = bus.drain();
|
|
38
|
+
* // Pass diagnostics to formatJson/formatSarif.
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
class DiagnosticBus {
|
|
42
|
+
items = [];
|
|
43
|
+
add(d) {
|
|
44
|
+
this.items.push(d);
|
|
45
|
+
}
|
|
46
|
+
/** Returns all diagnostics and clears the bus. */
|
|
47
|
+
drain() {
|
|
48
|
+
return this.items.splice(0);
|
|
49
|
+
}
|
|
50
|
+
/** Returns all diagnostics without clearing. */
|
|
51
|
+
peek() {
|
|
52
|
+
return this.items;
|
|
53
|
+
}
|
|
54
|
+
get size() {
|
|
55
|
+
return this.items.length;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.DiagnosticBus = DiagnosticBus;
|
|
59
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAkCH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAa,aAAa;IACP,KAAK,GAAiB,EAAE,CAAC;IAE1C,GAAG,CAAC,CAAa;QACf,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IAED,kDAAkD;IAClD,KAAK;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;IAED,gDAAgD;IAChD,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;CACF;AApBD,sCAoBC"}
|