@vaultcompass/vault-guard-core 1.2.3 → 1.3.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/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/scanners/pre-commit-hook.d.ts +10 -0
- package/dist/scanners/pre-commit-hook.js +95 -7
- package/dist/scanners/token-counter.js +3 -2
- package/dist/utils/git-utils.d.ts +10 -1
- package/dist/utils/git-utils.js +31 -9
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export * from './scan-output';
|
|
|
10
10
|
export * from './diagnostics';
|
|
11
11
|
export { shannonEntropy, DEFAULT_ENTROPY_THRESHOLD } from './utils/entropy';
|
|
12
12
|
export { isPlaceholderSecret, isNonSecretConnectionString, isSampleJwt, isRedactedTemplateValue, isEnvVarNameToken } from './utils/placeholder';
|
|
13
|
-
export { getGitStagedFilePaths, isInsideGitWorkTree } from './utils/git-utils';
|
|
13
|
+
export { getGitStagedFilePaths, readGitIndexFile, isInsideGitWorkTree } from './utils/git-utils';
|
|
14
14
|
export { validateRegexSafety, validateRegexLength, mapRegexSafetyReasonToDiagnosticCode, mapPatternRejectionReasonToDiagnosticCode, REGEX_REASON_TO_DIAGNOSTIC_CODE, REGEX_MAX_LENGTH, REGEX_MAX_QUANTIFIERS, } from './utils/regex-safety';
|
|
15
15
|
export { scanTextFileAsync, scanTextFileSync } from './utils/scan-file';
|
|
16
16
|
export { applyPathAwareSeverity, isTestFilePath } from './utils/path-severity';
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.isTestFilePath = exports.applyPathAwareSeverity = exports.scanTextFileSync = exports.scanTextFileAsync = exports.REGEX_MAX_QUANTIFIERS = exports.REGEX_MAX_LENGTH = exports.REGEX_REASON_TO_DIAGNOSTIC_CODE = exports.mapPatternRejectionReasonToDiagnosticCode = exports.mapRegexSafetyReasonToDiagnosticCode = exports.validateRegexLength = exports.validateRegexSafety = exports.isInsideGitWorkTree = exports.getGitStagedFilePaths = exports.isEnvVarNameToken = exports.isRedactedTemplateValue = exports.isSampleJwt = exports.isNonSecretConnectionString = exports.isPlaceholderSecret = exports.DEFAULT_ENTROPY_THRESHOLD = exports.shannonEntropy = exports.fingerprintForMatch = void 0;
|
|
17
|
+
exports.isTestFilePath = exports.applyPathAwareSeverity = exports.scanTextFileSync = exports.scanTextFileAsync = exports.REGEX_MAX_QUANTIFIERS = exports.REGEX_MAX_LENGTH = exports.REGEX_REASON_TO_DIAGNOSTIC_CODE = exports.mapPatternRejectionReasonToDiagnosticCode = exports.mapRegexSafetyReasonToDiagnosticCode = exports.validateRegexLength = exports.validateRegexSafety = exports.isInsideGitWorkTree = exports.readGitIndexFile = exports.getGitStagedFilePaths = exports.isEnvVarNameToken = exports.isRedactedTemplateValue = exports.isSampleJwt = exports.isNonSecretConnectionString = exports.isPlaceholderSecret = exports.DEFAULT_ENTROPY_THRESHOLD = exports.shannonEntropy = exports.fingerprintForMatch = void 0;
|
|
18
18
|
__exportStar(require("./types"), exports);
|
|
19
19
|
__exportStar(require("./errors"), exports);
|
|
20
20
|
__exportStar(require("./scanners"), exports);
|
|
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "isRedactedTemplateValue", { enumerable: true, ge
|
|
|
37
37
|
Object.defineProperty(exports, "isEnvVarNameToken", { enumerable: true, get: function () { return placeholder_1.isEnvVarNameToken; } });
|
|
38
38
|
var git_utils_1 = require("./utils/git-utils");
|
|
39
39
|
Object.defineProperty(exports, "getGitStagedFilePaths", { enumerable: true, get: function () { return git_utils_1.getGitStagedFilePaths; } });
|
|
40
|
+
Object.defineProperty(exports, "readGitIndexFile", { enumerable: true, get: function () { return git_utils_1.readGitIndexFile; } });
|
|
40
41
|
Object.defineProperty(exports, "isInsideGitWorkTree", { enumerable: true, get: function () { return git_utils_1.isInsideGitWorkTree; } });
|
|
41
42
|
var regex_safety_1 = require("./utils/regex-safety");
|
|
42
43
|
Object.defineProperty(exports, "validateRegexSafety", { enumerable: true, get: function () { return regex_safety_1.validateRegexSafety; } });
|
|
@@ -18,6 +18,10 @@ export declare class PreCommitHook {
|
|
|
18
18
|
* Absolute path to the \`pre-commit\` hook file for the given manager.
|
|
19
19
|
*/
|
|
20
20
|
getPreCommitHookPath(cwd: string, manager?: HookManager): string;
|
|
21
|
+
/**
|
|
22
|
+
* Absolute path to the Windows \`pre-commit.cmd\` companion (native manager only).
|
|
23
|
+
*/
|
|
24
|
+
getPreCommitCmdPath(cwd: string): string;
|
|
21
25
|
install(options?: InstallHookOptions): {
|
|
22
26
|
success: boolean;
|
|
23
27
|
message: string;
|
|
@@ -28,6 +32,12 @@ export declare class PreCommitHook {
|
|
|
28
32
|
message: string;
|
|
29
33
|
};
|
|
30
34
|
isInstalled(options?: InstallHookOptions): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Write or refresh our `pre-commit.cmd`. Never overwrites a foreign file.
|
|
37
|
+
* @returns whether our companion is present afterwards
|
|
38
|
+
*/
|
|
39
|
+
private writeNativeCmdCompanion;
|
|
40
|
+
private removeNativeCmdCompanion;
|
|
31
41
|
private installNative;
|
|
32
42
|
private uninstallNative;
|
|
33
43
|
private installHusky;
|
|
@@ -35,6 +35,30 @@ echo "❌ COMMIT BLOCKED: secrets detected in staged files"
|
|
|
35
35
|
echo "💡 Fix or unstage, then retry. Emergency bypass (discouraged): git commit --no-verify"
|
|
36
36
|
exit 1
|
|
37
37
|
`;
|
|
38
|
+
/**
|
|
39
|
+
* Optional `pre-commit.cmd` beside the POSIX hook. Git for Windows runs the
|
|
40
|
+
* extensionless `pre-commit` via sh; a few clients may invoke `.cmd` directly.
|
|
41
|
+
*/
|
|
42
|
+
const NATIVE_HOOK_CMD = `@echo off
|
|
43
|
+
REM vault-guard pre-commit (installed by @vaultcompass/vault-guard)
|
|
44
|
+
where vault-guard >nul 2>&1
|
|
45
|
+
if errorlevel 1 (
|
|
46
|
+
echo ❌ vault-guard: command not found ^(install: npm i -g @vaultcompass/vault-guard^)
|
|
47
|
+
exit /b 1
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
echo 🔍 vault-guard: scanning staged files...
|
|
51
|
+
call vault-guard scan --staged
|
|
52
|
+
if errorlevel 1 (
|
|
53
|
+
echo.
|
|
54
|
+
echo ❌ COMMIT BLOCKED: secrets detected in staged files
|
|
55
|
+
echo 💡 Fix or unstage, then retry. Emergency bypass ^(discouraged^): git commit --no-verify
|
|
56
|
+
exit /b 1
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
echo ✅ vault-guard: no secrets in staged files
|
|
60
|
+
exit /b 0
|
|
61
|
+
`;
|
|
38
62
|
/** Husky-friendly hook (sources \`_/husky.sh\` when present). */
|
|
39
63
|
const HUSKY_HOOK_SCRIPT = `#!/usr/bin/env sh
|
|
40
64
|
if [ -f "$(dirname "$0")/_/husky.sh" ]; then
|
|
@@ -110,6 +134,12 @@ class PreCommitHook {
|
|
|
110
134
|
}
|
|
111
135
|
return path_1.default.join(this.getEffectiveHooksDir(cwd).hooksDir, 'pre-commit');
|
|
112
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Absolute path to the Windows \`pre-commit.cmd\` companion (native manager only).
|
|
139
|
+
*/
|
|
140
|
+
getPreCommitCmdPath(cwd) {
|
|
141
|
+
return path_1.default.join(this.getEffectiveHooksDir(cwd).hooksDir, 'pre-commit.cmd');
|
|
142
|
+
}
|
|
113
143
|
install(options = {}) {
|
|
114
144
|
const cwd = options.cwd ?? process.cwd();
|
|
115
145
|
const manager = options.manager ?? 'native';
|
|
@@ -160,9 +190,37 @@ class PreCommitHook {
|
|
|
160
190
|
// -------------------------------------------------------------------------
|
|
161
191
|
// native (Git hooks / core.hooksPath)
|
|
162
192
|
// -------------------------------------------------------------------------
|
|
193
|
+
/**
|
|
194
|
+
* Write or refresh our `pre-commit.cmd`. Never overwrites a foreign file.
|
|
195
|
+
* @returns whether our companion is present afterwards
|
|
196
|
+
*/
|
|
197
|
+
writeNativeCmdCompanion(hooksDir) {
|
|
198
|
+
const cmdPath = path_1.default.join(hooksDir, 'pre-commit.cmd');
|
|
199
|
+
if (fs_1.default.existsSync(cmdPath)) {
|
|
200
|
+
const existing = fs_1.default.readFileSync(cmdPath, 'utf-8');
|
|
201
|
+
const isOurs = existing.includes('vault-guard') && existing.includes('scan --staged');
|
|
202
|
+
if (!isOurs) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
fs_1.default.writeFileSync(cmdPath, NATIVE_HOOK_CMD, { encoding: 'utf-8' });
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
removeNativeCmdCompanion(hooksDir) {
|
|
210
|
+
const cmdPath = path_1.default.join(hooksDir, 'pre-commit.cmd');
|
|
211
|
+
if (!fs_1.default.existsSync(cmdPath))
|
|
212
|
+
return false;
|
|
213
|
+
const content = fs_1.default.readFileSync(cmdPath, 'utf-8');
|
|
214
|
+
if (!content.includes('vault-guard') || !content.includes('scan --staged')) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
fs_1.default.unlinkSync(cmdPath);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
163
220
|
installNative(cwd) {
|
|
164
221
|
const { hooksDir, viaHooksPath } = this.getEffectiveHooksDir(cwd);
|
|
165
222
|
const hookPath = path_1.default.join(hooksDir, 'pre-commit');
|
|
223
|
+
const cmdPath = path_1.default.join(hooksDir, 'pre-commit.cmd');
|
|
166
224
|
try {
|
|
167
225
|
if (!fs_1.default.existsSync(hooksDir)) {
|
|
168
226
|
fs_1.default.mkdirSync(hooksDir, { recursive: true });
|
|
@@ -170,18 +228,31 @@ class PreCommitHook {
|
|
|
170
228
|
if (fs_1.default.existsSync(hookPath)) {
|
|
171
229
|
const existing = fs_1.default.readFileSync(hookPath, 'utf-8');
|
|
172
230
|
if (existing.includes('vault-guard') && existing.includes('scan --staged')) {
|
|
231
|
+
// Refresh the Windows companion if missing or stale (never clobber foreign).
|
|
232
|
+
const cmdOk = this.writeNativeCmdCompanion(hooksDir);
|
|
173
233
|
return {
|
|
174
234
|
success: true,
|
|
175
|
-
message:
|
|
235
|
+
message: cmdOk
|
|
236
|
+
? 'Hook already installed (POSIX + Windows .cmd companion)'
|
|
237
|
+
: fs_1.default.existsSync(cmdPath)
|
|
238
|
+
? 'Hook already installed (left foreign pre-commit.cmd untouched)'
|
|
239
|
+
: 'Hook already installed',
|
|
176
240
|
hookPath,
|
|
177
241
|
};
|
|
178
242
|
}
|
|
179
243
|
}
|
|
180
244
|
fs_1.default.writeFileSync(hookPath, NATIVE_HOOK_SCRIPT, { mode: 0o755 });
|
|
245
|
+
const cmdOk = this.writeNativeCmdCompanion(hooksDir);
|
|
181
246
|
const hint = viaHooksPath
|
|
182
247
|
? `Installed to hooksPath: ${hooksDir}`
|
|
183
|
-
:
|
|
184
|
-
|
|
248
|
+
: cmdOk
|
|
249
|
+
? 'Installed to .git/hooks/pre-commit (+ pre-commit.cmd)'
|
|
250
|
+
: 'Installed to .git/hooks/pre-commit';
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
message: `Pre-commit hook installed (${hint})`,
|
|
254
|
+
hookPath,
|
|
255
|
+
};
|
|
185
256
|
}
|
|
186
257
|
catch (error) {
|
|
187
258
|
const hookError = new errors_1.HookError(`Failed to install hook: ${error}`, 'install');
|
|
@@ -189,17 +260,34 @@ class PreCommitHook {
|
|
|
189
260
|
}
|
|
190
261
|
}
|
|
191
262
|
uninstallNative(cwd) {
|
|
192
|
-
const
|
|
263
|
+
const { hooksDir } = this.getEffectiveHooksDir(cwd);
|
|
264
|
+
const hookPath = path_1.default.join(hooksDir, 'pre-commit');
|
|
265
|
+
const cmdRemoved = this.removeNativeCmdCompanion(hooksDir);
|
|
193
266
|
if (!fs_1.default.existsSync(hookPath)) {
|
|
194
|
-
return {
|
|
267
|
+
return {
|
|
268
|
+
success: true,
|
|
269
|
+
message: cmdRemoved
|
|
270
|
+
? 'Removed Windows pre-commit.cmd companion'
|
|
271
|
+
: 'No hook to remove',
|
|
272
|
+
};
|
|
195
273
|
}
|
|
196
274
|
const content = fs_1.default.readFileSync(hookPath, 'utf-8');
|
|
197
275
|
if (!content.includes('vault-guard')) {
|
|
198
|
-
return {
|
|
276
|
+
return {
|
|
277
|
+
success: true,
|
|
278
|
+
message: cmdRemoved
|
|
279
|
+
? 'Removed Windows pre-commit.cmd companion'
|
|
280
|
+
: 'No vault-guard hook to remove',
|
|
281
|
+
};
|
|
199
282
|
}
|
|
200
283
|
try {
|
|
201
284
|
fs_1.default.unlinkSync(hookPath);
|
|
202
|
-
return {
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
message: cmdRemoved
|
|
288
|
+
? 'Pre-commit hook and Windows .cmd companion removed'
|
|
289
|
+
: 'Pre-commit hook removed',
|
|
290
|
+
};
|
|
203
291
|
}
|
|
204
292
|
catch (error) {
|
|
205
293
|
const hookError = new errors_1.HookError(`Failed to remove hook: ${error}`, 'uninstall');
|
|
@@ -96,8 +96,9 @@ class TokenCounter {
|
|
|
96
96
|
return ['.png', '.jpg', '.jpeg', '.gif', '.pdf', '.zip', '.lock', '.log'].includes(ext);
|
|
97
97
|
}
|
|
98
98
|
getExtension(filePath) {
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
// Use basename so parent dirs with '.' (e.g. macOS temp paths) are ignored.
|
|
100
|
+
const ext = path_1.default.extname(path_1.default.basename(filePath));
|
|
101
|
+
return ext.length > 0 ? ext : '(no ext)';
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
exports.TokenCounter = TokenCounter;
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Return absolute paths of files staged for commit (cached index vs HEAD).
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Uses `--diff-filter=ACMRT` so deleted index entries are excluded, but
|
|
5
|
+
* **does not** require the path to exist in the worktree. A staged add that
|
|
6
|
+
* was later deleted from disk (`AD` in `git status`) still appears — that
|
|
7
|
+
* blob will be committed and must be scanned.
|
|
4
8
|
*
|
|
5
9
|
* Throws `GitError` on git failure rather than returning an empty list.
|
|
6
10
|
* Returning `[]` silently on git failure would produce a false "✅ nothing
|
|
7
11
|
* staged" result in pre-commit, letting secrets through undetected.
|
|
8
12
|
*/
|
|
9
13
|
export declare function getGitStagedFilePaths(cwd?: string): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Read a staged blob from the index (`git show :path`), not the worktree.
|
|
16
|
+
* `relativePath` may use OS separators; it is normalized to git's `/` form.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readGitIndexFile(cwd: string, relativePath: string): string;
|
|
10
19
|
/** True when `cwd` is inside a work tree with a `.git` directory or file. */
|
|
11
20
|
export declare function isInsideGitWorkTree(cwd?: string): boolean;
|
package/dist/utils/git-utils.js
CHANGED
|
@@ -4,24 +4,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getGitStagedFilePaths = getGitStagedFilePaths;
|
|
7
|
+
exports.readGitIndexFile = readGitIndexFile;
|
|
7
8
|
exports.isInsideGitWorkTree = isInsideGitWorkTree;
|
|
8
9
|
const child_process_1 = require("child_process");
|
|
9
|
-
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const errors_1 = require("../errors");
|
|
12
12
|
/**
|
|
13
13
|
* Return absolute paths of files staged for commit (cached index vs HEAD).
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
|
+
* Uses `--diff-filter=ACMRT` so deleted index entries are excluded, but
|
|
16
|
+
* **does not** require the path to exist in the worktree. A staged add that
|
|
17
|
+
* was later deleted from disk (`AD` in `git status`) still appears — that
|
|
18
|
+
* blob will be committed and must be scanned.
|
|
15
19
|
*
|
|
16
20
|
* Throws `GitError` on git failure rather than returning an empty list.
|
|
17
21
|
* Returning `[]` silently on git failure would produce a false "✅ nothing
|
|
18
22
|
* staged" result in pre-commit, letting secrets through undetected.
|
|
19
23
|
*/
|
|
20
24
|
function getGitStagedFilePaths(cwd = process.cwd()) {
|
|
21
|
-
const
|
|
25
|
+
const args = ['diff', '--cached', '--name-only', '--diff-filter=ACMRT', '-z'];
|
|
22
26
|
let out;
|
|
23
27
|
try {
|
|
24
|
-
out = (0, child_process_1.
|
|
28
|
+
out = (0, child_process_1.execFileSync)('git', args, {
|
|
25
29
|
cwd,
|
|
26
30
|
encoding: 'utf-8',
|
|
27
31
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -29,19 +33,37 @@ function getGitStagedFilePaths(cwd = process.cwd()) {
|
|
|
29
33
|
}
|
|
30
34
|
catch (err) {
|
|
31
35
|
throw new errors_1.GitError(`Failed to list staged files — is this a git repository? (cwd: ${cwd})\n` +
|
|
32
|
-
`Run 'git status' to verify.\nUnderlying error: ${String(err)}`,
|
|
36
|
+
`Run 'git status' to verify.\nUnderlying error: ${String(err)}`, `git ${args.join(' ')}`, err);
|
|
33
37
|
}
|
|
34
38
|
return out
|
|
35
|
-
.split('\
|
|
39
|
+
.split('\0')
|
|
36
40
|
.map((line) => line.trim())
|
|
37
41
|
.filter(Boolean)
|
|
38
|
-
.map((rel) => path_1.default.resolve(cwd, rel))
|
|
39
|
-
|
|
42
|
+
.map((rel) => path_1.default.resolve(cwd, rel));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Read a staged blob from the index (`git show :path`), not the worktree.
|
|
46
|
+
* `relativePath` may use OS separators; it is normalized to git's `/` form.
|
|
47
|
+
*/
|
|
48
|
+
function readGitIndexFile(cwd, relativePath) {
|
|
49
|
+
const normalized = relativePath.split(path_1.default.sep).join('/');
|
|
50
|
+
const args = ['show', `:${normalized}`];
|
|
51
|
+
try {
|
|
52
|
+
return (0, child_process_1.execFileSync)('git', args, {
|
|
53
|
+
cwd,
|
|
54
|
+
encoding: 'utf-8',
|
|
55
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
56
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
throw new errors_1.GitError(`Failed to read staged blob for ${normalized}\nUnderlying error: ${String(err)}`, `git ${args.join(' ')}`, err);
|
|
61
|
+
}
|
|
40
62
|
}
|
|
41
63
|
/** True when `cwd` is inside a work tree with a `.git` directory or file. */
|
|
42
64
|
function isInsideGitWorkTree(cwd = process.cwd()) {
|
|
43
65
|
try {
|
|
44
|
-
(0, child_process_1.
|
|
66
|
+
(0, child_process_1.execFileSync)('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
45
67
|
cwd,
|
|
46
68
|
encoding: 'utf-8',
|
|
47
69
|
stdio: ['ignore', 'pipe', 'pipe'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaultcompass/vault-guard-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Secret-scanning engine: vendor-anchored patterns, entropy gating, baselines, SARIF/JSON, hook helpers.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|