@vaultcompass/vault-guard-core 1.4.4 → 1.4.5
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/scan-output.d.ts +16 -1
- package/dist/scan-output.js +94 -10
- package/package.json +1 -1
package/dist/scan-output.d.ts
CHANGED
|
@@ -62,9 +62,24 @@ export interface FormatOptions {
|
|
|
62
62
|
/**
|
|
63
63
|
* Base directory to render `file` paths relative to.
|
|
64
64
|
* Defaults to `process.cwd()`. Files outside this root are kept absolute.
|
|
65
|
-
* Pass `null` to skip relativization
|
|
65
|
+
* Pass `null` to skip relativization for `formatJson`'s `file` paths and
|
|
66
|
+
* for SARIF when no {@link scanRoot} is given either. A `scanRoot` still
|
|
67
|
+
* relativizes SARIF `artifactLocation.uri` (and diagnostic ctx) against
|
|
68
|
+
* itself in that case, so `cwd: null` does not skip SARIF relativization
|
|
69
|
+
* on its own.
|
|
66
70
|
*/
|
|
67
71
|
cwd?: string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Directory actually being scanned (the scan target), when it differs from
|
|
74
|
+
* {@link cwd}. SARIF only: `artifactLocation.uri` is relativized against
|
|
75
|
+
* this instead of `cwd`, so a finding outside the process cwd but inside the
|
|
76
|
+
* scan target still gets a relative uri. Defaults to `cwd`.
|
|
77
|
+
*
|
|
78
|
+
* `formatJson` ignores this field. Its `file` paths stay cwd-relative,
|
|
79
|
+
* because they are what the terminal output and the baseline fingerprints
|
|
80
|
+
* are keyed on.
|
|
81
|
+
*/
|
|
82
|
+
scanRoot?: string;
|
|
68
83
|
/** Non-fatal diagnostics to include in structured output. */
|
|
69
84
|
diagnostics?: Diagnostic[];
|
|
70
85
|
/** Scan timing / coverage stats for JSON and SARIF `runs[].properties`. */
|
package/dist/scan-output.js
CHANGED
|
@@ -37,24 +37,103 @@ function isWindowsStylePath(p) {
|
|
|
37
37
|
* SARIF spec requires this even when the scanner itself runs on Windows,
|
|
38
38
|
* where `path.relative` returns backslash-separated paths).
|
|
39
39
|
*
|
|
40
|
+
* The base is the scan root (the directory actually being scanned), not the
|
|
41
|
+
* process cwd. Scanning an out-of-tree target from somewhere else used to
|
|
42
|
+
* leave every uri absolute, which published the developer's home directory
|
|
43
|
+
* and OS username to whoever reads the Code Scanning upload.
|
|
44
|
+
*
|
|
45
|
+
* A file genuinely outside the scan root still stays absolute rather than
|
|
46
|
+
* becoming a `../..` traversal: SARIF relative references are resolved
|
|
47
|
+
* against `%SRCROOT%`, so a traversal out of it is not a legal uri, and there
|
|
48
|
+
* is no other root to express such a path against. In practice this only
|
|
49
|
+
* happens for a path a caller injected from outside the scan, since every
|
|
50
|
+
* file the scanner itself walks is under the target it was given -- and a
|
|
51
|
+
* non-absolute `file` is resolved against cwd below before that check runs,
|
|
52
|
+
* so a literal `..` traversal segment never reaches the returned uri either.
|
|
53
|
+
*
|
|
40
54
|
* Picks `path.win32` when either side of the comparison looks like a
|
|
41
55
|
* Windows-style path, so this is correct both when the process itself runs
|
|
42
56
|
* on Windows (native `path` is already `path.win32`) and when a
|
|
43
57
|
* Windows-style path is normalized on a POSIX host (tests, or a SARIF file
|
|
44
58
|
* produced elsewhere and re-normalized).
|
|
45
59
|
*/
|
|
46
|
-
function toSarifArtifactUri(file, cwd) {
|
|
47
|
-
if (cwd === null)
|
|
60
|
+
function toSarifArtifactUri(file, cwd, scanRoot) {
|
|
61
|
+
if (cwd === null && scanRoot === undefined)
|
|
48
62
|
return file.split('\\').join('/');
|
|
49
|
-
const
|
|
50
|
-
const impl = isWindowsStylePath(file) || isWindowsStylePath(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const rel = impl.relative(base,
|
|
63
|
+
const anchor = cwd ?? process.cwd();
|
|
64
|
+
const impl = isWindowsStylePath(file) || isWindowsStylePath(anchor) ? path_1.default.win32 : path_1.default;
|
|
65
|
+
const abs = impl.isAbsolute(file) ? file : impl.resolve(anchor, file);
|
|
66
|
+
const base = resolveSarifBase(anchor, scanRoot, impl);
|
|
67
|
+
const rel = impl.relative(base, abs);
|
|
54
68
|
if (rel.startsWith('..') || impl.isAbsolute(rel))
|
|
55
|
-
return
|
|
69
|
+
return abs.split('\\').join('/');
|
|
56
70
|
return (rel || '.').split('\\').join('/');
|
|
57
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* The effective SARIF `%SRCROOT%` base. `scanRoot` is honored only when it
|
|
74
|
+
* is genuinely outside `cwd`: a scan root nested inside cwd (or equal to
|
|
75
|
+
* it) must not narrow `%SRCROOT%` to a subdirectory, because GitHub Code
|
|
76
|
+
* Scanning (and any other SARIF consumer) resolves `%SRCROOT%` from its own
|
|
77
|
+
* knowledge of the checkout, not from this uri -- a uri relative to a
|
|
78
|
+
* subdirectory would then name a different file entirely. Mirrors
|
|
79
|
+
* `resolveScanRoot` in `packages/cli/src/utils/scan-utils.ts`, which applies
|
|
80
|
+
* the same rule when it derives `scanRoot` from the CLI's scan target in the
|
|
81
|
+
* first place.
|
|
82
|
+
*/
|
|
83
|
+
function resolveSarifBase(cwd, scanRoot, impl) {
|
|
84
|
+
if (scanRoot === undefined)
|
|
85
|
+
return cwd;
|
|
86
|
+
const rel = impl.relative(cwd, scanRoot);
|
|
87
|
+
if (rel === '' || (!rel.startsWith('..') && !impl.isAbsolute(rel)))
|
|
88
|
+
return cwd;
|
|
89
|
+
return scanRoot;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Diagnostic `ctx` reaches SARIF notifications as `JSON.stringify(d.ctx)`
|
|
93
|
+
* (see `formatSarif` above), and some diagnostic sources (`fs.permission_denied`,
|
|
94
|
+
* `file.read_error`) carry the scanned directory or file as an absolute path,
|
|
95
|
+
* plus a `detail` field that is `String(error)` -- Node's own fs error text
|
|
96
|
+
* often bakes that same absolute path in (e.g. "EACCES: permission denied,
|
|
97
|
+
* scandir '/abs/dir'"). Both leak exactly what `artifactLocation.uri`
|
|
98
|
+
* exists to avoid leaking, so ctx gets the same relativize-or-keep-absolute
|
|
99
|
+
* treatment before it is stringified into a notification: any absolute-path
|
|
100
|
+
* ctx value (`dir`, `path`, `file`, or any other field shaped that way) is
|
|
101
|
+
* run through {@link toSarifArtifactUri}, and any other string value has
|
|
102
|
+
* literal occurrences of the base directory stripped out.
|
|
103
|
+
*/
|
|
104
|
+
function sanitizeDiagnosticCtxForSarif(ctx, cwd, scanRoot) {
|
|
105
|
+
if (cwd === null && scanRoot === undefined)
|
|
106
|
+
return ctx;
|
|
107
|
+
const anchor = cwd ?? process.cwd();
|
|
108
|
+
const base = resolveSarifBase(anchor, scanRoot, path_1.default);
|
|
109
|
+
const out = {};
|
|
110
|
+
for (const [key, value] of Object.entries(ctx)) {
|
|
111
|
+
if (typeof value !== 'string') {
|
|
112
|
+
out[key] = value;
|
|
113
|
+
}
|
|
114
|
+
else if (path_1.default.isAbsolute(value)) {
|
|
115
|
+
out[key] = toSarifArtifactUri(value, cwd, scanRoot);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
out[key] = stripSarifBasePath(value, base);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Removes literal occurrences of the SARIF base directory from a free-form
|
|
125
|
+
* string (diagnostic `detail`, which is `String(error)` and can embed the
|
|
126
|
+
* scanned path inside Node's own message text). This is not a full
|
|
127
|
+
* relative-path rewrite of the string, just enough to keep the base
|
|
128
|
+
* directory out of the document, matching what `artifactLocation.uri` does
|
|
129
|
+
* for the path fields themselves.
|
|
130
|
+
*/
|
|
131
|
+
function stripSarifBasePath(text, base) {
|
|
132
|
+
if (!text.includes(base))
|
|
133
|
+
return text;
|
|
134
|
+
const withTrailingSep = base.endsWith(path_1.default.sep) ? base : base + path_1.default.sep;
|
|
135
|
+
return text.split(withTrailingSep).join('').split(base).join('.');
|
|
136
|
+
}
|
|
58
137
|
function formatJson(results, opts = {}) {
|
|
59
138
|
const fpCwd = opts.cwd === undefined ? process.cwd() : opts.cwd;
|
|
60
139
|
const output = {
|
|
@@ -112,7 +191,10 @@ function formatSarif(results, opts = {}) {
|
|
|
112
191
|
locations: [
|
|
113
192
|
{
|
|
114
193
|
physicalLocation: {
|
|
115
|
-
artifactLocation: {
|
|
194
|
+
artifactLocation: {
|
|
195
|
+
uri: toSarifArtifactUri(file, opts.cwd, opts.scanRoot),
|
|
196
|
+
uriBaseId: '%SRCROOT%',
|
|
197
|
+
},
|
|
116
198
|
region: {
|
|
117
199
|
startLine: m.line,
|
|
118
200
|
startColumn: m.column + 1,
|
|
@@ -133,7 +215,9 @@ function formatSarif(results, opts = {}) {
|
|
|
133
215
|
? opts.diagnostics.map(d => ({
|
|
134
216
|
id: d.code,
|
|
135
217
|
level: d.severity === 'error' ? 'error' : 'warning',
|
|
136
|
-
message: {
|
|
218
|
+
message: {
|
|
219
|
+
text: `${d.code}: ${JSON.stringify(sanitizeDiagnosticCtxForSarif(d.ctx, opts.cwd, opts.scanRoot))}`,
|
|
220
|
+
},
|
|
137
221
|
}))
|
|
138
222
|
: undefined;
|
|
139
223
|
const runProps = opts.run !== undefined
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaultcompass/vault-guard-core",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.5",
|
|
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",
|