@vaultcompass/vault-guard-core 1.4.5 → 1.4.6

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/README.md CHANGED
@@ -68,6 +68,7 @@ Full rule reference: [docs/RULES.md](https://github.com/vaultcompasshq/vault-gua
68
68
  | `fingerprintForMatch` | Baseline fingerprinting (no raw secrets stored) |
69
69
  | `scanTextFileAsync` / `scanTextFileSync` | Stream-aware file scanning |
70
70
  | `getGitStagedFilePaths` | Staged-file enumeration for hooks |
71
+ | `getGitWorkTreeRoot` | Repository root for a directory, the base every staged path resolves against |
71
72
 
72
73
  ## Prefer the CLI?
73
74
 
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, isCodeIdentifierReference, isPasswordHash, isPemHeaderWithoutBody } from './utils/placeholder';
13
- export { getGitStagedFilePaths, readGitIndexFile, isInsideGitWorkTree } from './utils/git-utils';
13
+ export { getGitStagedFilePaths, readGitIndexFile, isInsideGitWorkTree, getGitWorkTreeRoot, } 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, isLocalePath } 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.resolveFailOn = exports.countBlockingMatches = exports.meetsFailThreshold = exports.isFailOnThreshold = exports.FAIL_ON_VALUES = exports.DEFAULT_FAIL_ON = exports.isLocalePath = 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.isPemHeaderWithoutBody = exports.isPasswordHash = exports.isCodeIdentifierReference = exports.isEnvVarNameToken = exports.isRedactedTemplateValue = exports.isSampleJwt = exports.isNonSecretConnectionString = exports.isPlaceholderSecret = exports.DEFAULT_ENTROPY_THRESHOLD = exports.shannonEntropy = exports.fingerprintForMatch = void 0;
17
+ exports.resolveFailOn = exports.countBlockingMatches = exports.meetsFailThreshold = exports.isFailOnThreshold = exports.FAIL_ON_VALUES = exports.DEFAULT_FAIL_ON = exports.isLocalePath = 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.getGitWorkTreeRoot = exports.isInsideGitWorkTree = exports.readGitIndexFile = exports.getGitStagedFilePaths = exports.isPemHeaderWithoutBody = exports.isPasswordHash = exports.isCodeIdentifierReference = 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);
@@ -42,6 +42,7 @@ var git_utils_1 = require("./utils/git-utils");
42
42
  Object.defineProperty(exports, "getGitStagedFilePaths", { enumerable: true, get: function () { return git_utils_1.getGitStagedFilePaths; } });
43
43
  Object.defineProperty(exports, "readGitIndexFile", { enumerable: true, get: function () { return git_utils_1.readGitIndexFile; } });
44
44
  Object.defineProperty(exports, "isInsideGitWorkTree", { enumerable: true, get: function () { return git_utils_1.isInsideGitWorkTree; } });
45
+ Object.defineProperty(exports, "getGitWorkTreeRoot", { enumerable: true, get: function () { return git_utils_1.getGitWorkTreeRoot; } });
45
46
  var regex_safety_1 = require("./utils/regex-safety");
46
47
  Object.defineProperty(exports, "validateRegexSafety", { enumerable: true, get: function () { return regex_safety_1.validateRegexSafety; } });
47
48
  Object.defineProperty(exports, "validateRegexLength", { enumerable: true, get: function () { return regex_safety_1.validateRegexLength; } });
@@ -25,6 +25,20 @@ export interface JsonRunMetadata {
25
25
  * this field rather than `summary.secrets`.
26
26
  */
27
27
  blocking_matches?: number;
28
+ /**
29
+ * Files the scan opened but could not read, so their contents were never
30
+ * examined. Omitted when zero.
31
+ *
32
+ * A non-zero value means the run is INCOMPLETE: `blocking_matches` counts
33
+ * only what the scanner actually looked at, so a clean gate result sits on
34
+ * top of files nobody checked. On the `--staged` path this is fatal on its
35
+ * own (exit 2) because that file list is exactly what is about to be
36
+ * committed; on a directory scan it is reported and counted but does not
37
+ * fail the gate, since an unreadable file inside a walked tree is an
38
+ * ordinary occurrence. Unreadable DIRECTORIES are not counted here -- they
39
+ * surface as `fs.permission_denied` diagnostics instead.
40
+ */
41
+ unscannable_files?: number;
28
42
  }
29
43
  export interface JsonOutput {
30
44
  version: string;
@@ -233,6 +233,9 @@ function formatSarif(results, opts = {}) {
233
233
  ...(opts.run.baseline_suppressed !== undefined
234
234
  ? { baseline_suppressed: opts.run.baseline_suppressed }
235
235
  : {}),
236
+ ...(opts.run.unscannable_files !== undefined
237
+ ? { unscannable_files: opts.run.unscannable_files }
238
+ : {}),
236
239
  },
237
240
  }
238
241
  : undefined;
@@ -44,12 +44,28 @@ if ! command -v vault-guard >/dev/null 2>&1; then
44
44
  fi
45
45
 
46
46
  echo "🔍 vault-guard: scanning staged files..."
47
- if vault-guard scan --staged; then
47
+ # Capture the status rather than branching on \`if vault-guard ...\`: after a
48
+ # bare \`if\` with no else branch, \`$?\` is the IF STATEMENT's status (0), not
49
+ # the command's, so the exit code would be lost. \`|| status=$?\` also keeps
50
+ # \`set -e\` from aborting here, because the failure is handled.
51
+ status=0
52
+ vault-guard scan --staged || status=$?
53
+
54
+ if [ "$status" -eq 0 ]; then
48
55
  echo "✅ vault-guard: no secrets in staged files"
49
56
  exit 0
50
57
  fi
51
58
 
52
59
  echo ""
60
+ if [ "$status" -eq 2 ]; then
61
+ # Exit 2 means vault-guard could not finish the scan, not that it found
62
+ # something. Claiming secrets were detected would be false, and offering
63
+ # --no-verify beside it would recommend skipping a check that never ran.
64
+ echo "❌ COMMIT BLOCKED: vault-guard could not complete the scan"
65
+ echo " See the message above for what went unexamined. This commit has not been checked."
66
+ exit 1
67
+ fi
68
+
53
69
  echo "❌ COMMIT BLOCKED: secrets detected in staged files"
54
70
  echo "💡 Fix or unstage, then retry. Emergency bypass (discouraged): git commit --no-verify"
55
71
  exit 1
@@ -68,6 +84,14 @@ if errorlevel 1 (
68
84
 
69
85
  echo 🔍 vault-guard: scanning staged files...
70
86
  call vault-guard scan --staged
87
+ REM \`if errorlevel N\` is true for anything >= N, so 2 must be tested BEFORE
88
+ REM 1 or an incomplete scan would fall into the secrets-detected branch.
89
+ if errorlevel 2 (
90
+ echo.
91
+ echo ❌ COMMIT BLOCKED: vault-guard could not complete the scan
92
+ echo See the message above for what went unexamined. This commit has not been checked.
93
+ exit /b 1
94
+ )
71
95
  if errorlevel 1 (
72
96
  echo.
73
97
  echo ❌ COMMIT BLOCKED: secrets detected in staged files
@@ -91,12 +115,25 @@ if ! command -v vault-guard >/dev/null 2>&1; then
91
115
  fi
92
116
 
93
117
  echo "🔍 vault-guard: scanning staged files..."
94
- vault-guard scan --staged || {
118
+ status=0
119
+ vault-guard scan --staged || status=$?
120
+
121
+ if [ "$status" -eq 2 ]; then
122
+ # Could not finish the scan, as opposed to finding something. No
123
+ # --no-verify hint here: the check never ran, so bypassing it is not the
124
+ # remedy. See the native template for the same reasoning.
125
+ echo ""
126
+ echo "❌ COMMIT BLOCKED: vault-guard could not complete the scan"
127
+ echo " See the message above for what went unexamined. This commit has not been checked."
128
+ exit 1
129
+ fi
130
+
131
+ if [ "$status" -ne 0 ]; then
95
132
  echo ""
96
133
  echo "❌ COMMIT BLOCKED: secrets detected in staged files"
97
134
  echo "💡 git commit --no-verify to bypass (discouraged)"
98
135
  exit 1
99
- }
136
+ fi
100
137
  echo "✅ vault-guard: no secrets in staged files"
101
138
  `;
102
139
  const LEFTHOOK_LOCAL = `# Merged by Lefthook with lefthook.yml — added by vault-guard install-hook
@@ -1,3 +1,30 @@
1
+ /**
2
+ * Absolute, symlink-resolved path of the worktree root containing `cwd`.
3
+ *
4
+ * Every path git reports for the index is relative to this directory, and
5
+ * `git show :<path>` resolves its argument against it too, so it is the only
6
+ * correct base for turning git's output into filesystem paths. Deriving it
7
+ * from `cwd` instead (what this module used to do) is right only when the
8
+ * caller happens to be standing at the root.
9
+ *
10
+ * Throws `GitError` rather than falling back to `cwd`: a wrong root silently
11
+ * mis-resolves every staged path, which is exactly the failure this function
12
+ * exists to prevent.
13
+ */
14
+ export declare function getGitWorkTreeRoot(cwd?: string): string;
15
+ /**
16
+ * Exact argv passed to git when listing staged files, exported so the
17
+ * safety-critical parts of it can be asserted directly.
18
+ *
19
+ * The `-c diff.relative=false` override is deliberately BELT AND BRACES with
20
+ * running the command at the worktree root (see getGitStagedFilePaths):
21
+ * either one alone makes git's output root-relative. That redundancy is
22
+ * wanted in a published security gate, but redundant code that no test can
23
+ * observe is code the next person deletes as dead weight -- so
24
+ * `git-utils.test.ts` pins this argv, and removing the flag fails a test
25
+ * even though behaviour would not change.
26
+ */
27
+ export declare const STAGED_DIFF_ARGV: readonly string[];
1
28
  /**
2
29
  * Return absolute paths of files staged for commit (cached index vs HEAD).
3
30
  *
@@ -6,6 +33,14 @@
6
33
  * was later deleted from disk (`AD` in `git status`) still appears — that
7
34
  * blob will be committed and must be scanned.
8
35
  *
36
+ * Three independent things keep `diff.relative` from deciding what the gate
37
+ * sees: the config is forced off in the argv, the command runs AT the
38
+ * worktree root where the setting has nothing to make relative, and the
39
+ * output is resolved against that root rather than against `cwd`. Any one of
40
+ * the first two would do; both are here because this is a pre-commit gate on
41
+ * a published package, and the failure mode is silent under-reporting rather
42
+ * than an error anyone would notice.
43
+ *
9
44
  * Throws `GitError` on git failure rather than returning an empty list.
10
45
  * Returning `[]` silently on git failure would produce a false "✅ nothing
11
46
  * staged" result in pre-commit, letting secrets through undetected.
@@ -13,8 +48,19 @@
13
48
  export declare function getGitStagedFilePaths(cwd?: string): string[];
14
49
  /**
15
50
  * 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.
51
+ *
52
+ * `filePath` may be absolute or relative to `cwd`, and may use OS
53
+ * separators. It is re-expressed relative to the worktree root before it
54
+ * reaches git, because git's `:<path>` revision syntax is root-relative by
55
+ * definition. Handing it a path relative to a subdirectory is what produced
56
+ * "fatal: path 'pkg/deep/staged.ts' is in the index, but not 'staged.ts'"
57
+ * and, one silent downgrade later, a clean bill of health over a real
58
+ * staged credential.
59
+ *
60
+ * Unlike the staged listing, this command needs no worktree-root `cwd` of
61
+ * its own: `:<path>` is defined as root-relative, so once the path has been
62
+ * re-expressed the working directory git runs in cannot change the answer.
17
63
  */
18
- export declare function readGitIndexFile(cwd: string, relativePath: string): string;
64
+ export declare function readGitIndexFile(cwd: string, filePath: string): string;
19
65
  /** True when `cwd` is inside a work tree with a `.git` directory or file. */
20
66
  export declare function isInsideGitWorkTree(cwd?: string): boolean;
@@ -3,12 +3,131 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.STAGED_DIFF_ARGV = void 0;
7
+ exports.getGitWorkTreeRoot = getGitWorkTreeRoot;
6
8
  exports.getGitStagedFilePaths = getGitStagedFilePaths;
7
9
  exports.readGitIndexFile = readGitIndexFile;
8
10
  exports.isInsideGitWorkTree = isInsideGitWorkTree;
9
11
  const child_process_1 = require("child_process");
12
+ const fs_1 = __importDefault(require("fs"));
10
13
  const path_1 = __importDefault(require("path"));
11
14
  const errors_1 = require("../errors");
15
+ /**
16
+ * Repository config that must never be allowed to decide what vault-guard
17
+ * sees, prepended to every git command this module runs.
18
+ *
19
+ * `diff.relative=false` is the load-bearing one. `diff.relative` is an
20
+ * ordinary, user-settable repo config, and with it on git makes
21
+ * `git diff --cached --name-only` both print paths relative to the process
22
+ * cwd instead of the worktree root AND omit every staged path outside that
23
+ * cwd. A pre-commit gate whose file list can be emptied by a line in
24
+ * `.git/config` is not a gate, so the setting is overridden per invocation
25
+ * rather than trusted.
26
+ *
27
+ * `core.quotePath=false` is belt-and-braces: every command below already
28
+ * passes `-z` or reads a single blob, so git never applies path quoting as
29
+ * things stand. Forcing it here means a future edit that drops `-z` cannot
30
+ * silently reintroduce `"\303\251.env"`-style names that would then resolve
31
+ * to a file that does not exist and be skipped.
32
+ *
33
+ * These are `-c key=value` arguments in the argv array, not a `git config`
34
+ * write: nothing on the user's disk is modified.
35
+ */
36
+ const FORCED_GIT_CONFIG = ['-c', 'diff.relative=false', '-c', 'core.quotePath=false'];
37
+ /**
38
+ * Resolve symlinks in a directory path, falling back to the input when it
39
+ * cannot be resolved. Git reports the physical worktree root, so a caller
40
+ * that passes a symlinked path (`/var/folders/...` on macOS, where the real
41
+ * directory is `/private/var/folders/...`) would otherwise produce a bogus
42
+ * `path.relative` against it.
43
+ */
44
+ function canonicalDir(dir) {
45
+ try {
46
+ return fs_1.default.realpathSync.native(dir);
47
+ }
48
+ catch {
49
+ return dir;
50
+ }
51
+ }
52
+ /**
53
+ * Successful worktree-root lookups, keyed by the `cwd` asked about.
54
+ *
55
+ * `readGitIndexFile` needs the root for every staged file it reads, and a
56
+ * pre-commit run can have hundreds; without this each one would spawn a
57
+ * second git process on the latency-sensitive path. Only successes are
58
+ * cached, so a failure is retried and still throws. A directory's worktree
59
+ * root does not change under a running scan, and the key set is bounded by
60
+ * the number of distinct directories a single process asks about (one, in
61
+ * every current caller).
62
+ */
63
+ const worktreeRootCache = new Map();
64
+ /**
65
+ * Absolute, symlink-resolved path of the worktree root containing `cwd`.
66
+ *
67
+ * Every path git reports for the index is relative to this directory, and
68
+ * `git show :<path>` resolves its argument against it too, so it is the only
69
+ * correct base for turning git's output into filesystem paths. Deriving it
70
+ * from `cwd` instead (what this module used to do) is right only when the
71
+ * caller happens to be standing at the root.
72
+ *
73
+ * Throws `GitError` rather than falling back to `cwd`: a wrong root silently
74
+ * mis-resolves every staged path, which is exactly the failure this function
75
+ * exists to prevent.
76
+ */
77
+ function getGitWorkTreeRoot(cwd = process.cwd()) {
78
+ const cached = worktreeRootCache.get(cwd);
79
+ if (cached !== undefined)
80
+ return cached;
81
+ const args = [...FORCED_GIT_CONFIG, 'rev-parse', '--show-toplevel'];
82
+ let out;
83
+ try {
84
+ out = (0, child_process_1.execFileSync)('git', args, {
85
+ cwd,
86
+ encoding: 'utf-8',
87
+ stdio: ['ignore', 'pipe', 'pipe'],
88
+ });
89
+ }
90
+ catch (err) {
91
+ throw new errors_1.GitError(`Failed to locate the git worktree root. Is this a git repository? (cwd: ${cwd})\n` +
92
+ `Run 'git status' to verify.\nUnderlying error: ${String(err)}`, `git ${args.join(' ')}`, err);
93
+ }
94
+ const root = out.trim();
95
+ if (!root) {
96
+ throw new errors_1.GitError(`git reported no worktree root for ${cwd} (bare repository?).`, `git ${args.join(' ')}`, undefined);
97
+ }
98
+ const resolved = canonicalDir(root);
99
+ worktreeRootCache.set(cwd, resolved);
100
+ return resolved;
101
+ }
102
+ /**
103
+ * Exact argv passed to git when listing staged files, exported so the
104
+ * safety-critical parts of it can be asserted directly.
105
+ *
106
+ * The `-c diff.relative=false` override is deliberately BELT AND BRACES with
107
+ * running the command at the worktree root (see getGitStagedFilePaths):
108
+ * either one alone makes git's output root-relative. That redundancy is
109
+ * wanted in a published security gate, but redundant code that no test can
110
+ * observe is code the next person deletes as dead weight -- so
111
+ * `git-utils.test.ts` pins this argv, and removing the flag fails a test
112
+ * even though behaviour would not change.
113
+ */
114
+ exports.STAGED_DIFF_ARGV = [
115
+ ...FORCED_GIT_CONFIG,
116
+ 'diff',
117
+ '--cached',
118
+ '--name-only',
119
+ '--diff-filter=ACMRT',
120
+ // A staged submodule pointer bump is listed here as an ordinary path,
121
+ // but its index entry is a gitlink (mode 160000) rather than a blob, so
122
+ // `git show :<path>` answers "fatal: bad object". There is no content
123
+ // behind a gitlink for this scanner to read, and treating one as an
124
+ // unreadable file made every routine submodule bump block the commit
125
+ // with a message about detected secrets. Dropping the entry is correct,
126
+ // not a suppression: nothing about the pointer is scannable, and the
127
+ // submodule's own contents are that repository's own gate to run.
128
+ '--ignore-submodules=all',
129
+ '-z',
130
+ ];
12
131
  /**
13
132
  * Return absolute paths of files staged for commit (cached index vs HEAD).
14
133
  *
@@ -17,16 +136,25 @@ const errors_1 = require("../errors");
17
136
  * was later deleted from disk (`AD` in `git status`) still appears — that
18
137
  * blob will be committed and must be scanned.
19
138
  *
139
+ * Three independent things keep `diff.relative` from deciding what the gate
140
+ * sees: the config is forced off in the argv, the command runs AT the
141
+ * worktree root where the setting has nothing to make relative, and the
142
+ * output is resolved against that root rather than against `cwd`. Any one of
143
+ * the first two would do; both are here because this is a pre-commit gate on
144
+ * a published package, and the failure mode is silent under-reporting rather
145
+ * than an error anyone would notice.
146
+ *
20
147
  * Throws `GitError` on git failure rather than returning an empty list.
21
148
  * Returning `[]` silently on git failure would produce a false "✅ nothing
22
149
  * staged" result in pre-commit, letting secrets through undetected.
23
150
  */
24
151
  function getGitStagedFilePaths(cwd = process.cwd()) {
25
- const args = ['diff', '--cached', '--name-only', '--diff-filter=ACMRT', '-z'];
152
+ const root = getGitWorkTreeRoot(cwd);
153
+ const args = [...exports.STAGED_DIFF_ARGV];
26
154
  let out;
27
155
  try {
28
156
  out = (0, child_process_1.execFileSync)('git', args, {
29
- cwd,
157
+ cwd: root,
30
158
  encoding: 'utf-8',
31
159
  stdio: ['ignore', 'pipe', 'pipe'],
32
160
  });
@@ -35,19 +163,35 @@ function getGitStagedFilePaths(cwd = process.cwd()) {
35
163
  throw new errors_1.GitError(`Failed to list staged files — is this a git repository? (cwd: ${cwd})\n` +
36
164
  `Run 'git status' to verify.\nUnderlying error: ${String(err)}`, `git ${args.join(' ')}`, err);
37
165
  }
166
+ // `-z` gives NUL-separated, unquoted, verbatim paths, so entries are used
167
+ // exactly as git produced them. Trimming here would corrupt the legal (if
168
+ // unusual) filename with leading or trailing whitespace into a path that
169
+ // does not exist.
38
170
  return out
39
171
  .split('\0')
40
- .map((line) => line.trim())
41
172
  .filter(Boolean)
42
- .map((rel) => path_1.default.resolve(cwd, rel));
173
+ .map((rel) => path_1.default.resolve(root, rel));
43
174
  }
44
175
  /**
45
176
  * 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.
177
+ *
178
+ * `filePath` may be absolute or relative to `cwd`, and may use OS
179
+ * separators. It is re-expressed relative to the worktree root before it
180
+ * reaches git, because git's `:<path>` revision syntax is root-relative by
181
+ * definition. Handing it a path relative to a subdirectory is what produced
182
+ * "fatal: path 'pkg/deep/staged.ts' is in the index, but not 'staged.ts'"
183
+ * and, one silent downgrade later, a clean bill of health over a real
184
+ * staged credential.
185
+ *
186
+ * Unlike the staged listing, this command needs no worktree-root `cwd` of
187
+ * its own: `:<path>` is defined as root-relative, so once the path has been
188
+ * re-expressed the working directory git runs in cannot change the answer.
47
189
  */
48
- function readGitIndexFile(cwd, relativePath) {
49
- const normalized = relativePath.split(path_1.default.sep).join('/');
50
- const args = ['show', `:${normalized}`];
190
+ function readGitIndexFile(cwd, filePath) {
191
+ const root = getGitWorkTreeRoot(cwd);
192
+ const abs = path_1.default.resolve(canonicalDir(cwd), filePath);
193
+ const rootRelative = path_1.default.relative(root, abs).split(path_1.default.sep).join('/');
194
+ const args = [...FORCED_GIT_CONFIG, 'show', `:${rootRelative}`];
51
195
  try {
52
196
  return (0, child_process_1.execFileSync)('git', args, {
53
197
  cwd,
@@ -57,13 +201,13 @@ function readGitIndexFile(cwd, relativePath) {
57
201
  });
58
202
  }
59
203
  catch (err) {
60
- throw new errors_1.GitError(`Failed to read staged blob for ${normalized}\nUnderlying error: ${String(err)}`, `git ${args.join(' ')}`, err);
204
+ throw new errors_1.GitError(`Failed to read staged blob for ${rootRelative}\nUnderlying error: ${String(err)}`, `git ${args.join(' ')}`, err);
61
205
  }
62
206
  }
63
207
  /** True when `cwd` is inside a work tree with a `.git` directory or file. */
64
208
  function isInsideGitWorkTree(cwd = process.cwd()) {
65
209
  try {
66
- (0, child_process_1.execFileSync)('git', ['rev-parse', '--is-inside-work-tree'], {
210
+ (0, child_process_1.execFileSync)('git', [...FORCED_GIT_CONFIG, 'rev-parse', '--is-inside-work-tree'], {
67
211
  cwd,
68
212
  encoding: 'utf-8',
69
213
  stdio: ['ignore', 'pipe', 'pipe'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaultcompass/vault-guard-core",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
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",