@visulima/vis 1.0.0-alpha.1 → 1.0.0-alpha.11
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/CHANGELOG.md +403 -12
- package/LICENSE.md +283 -0
- package/README.md +254 -9
- package/dist/bin.js +9 -146
- package/dist/config/index.d.ts +1818 -0
- package/dist/config/index.js +2 -0
- package/dist/generate/index.d.ts +157 -0
- package/dist/generate/index.js +3 -0
- package/dist/packem_chunks/applyDefaults.js +336 -0
- package/dist/packem_chunks/bin.js +9577 -0
- package/dist/packem_chunks/doctor-probe.js +112 -0
- package/dist/packem_chunks/fix.js +234 -0
- package/dist/packem_chunks/handler.js +99 -0
- package/dist/packem_chunks/handler10.js +53 -0
- package/dist/packem_chunks/handler11.js +32 -0
- package/dist/packem_chunks/handler12.js +100 -0
- package/dist/packem_chunks/handler13.js +25 -0
- package/dist/packem_chunks/handler14.js +916 -0
- package/dist/packem_chunks/handler15.js +206 -0
- package/dist/packem_chunks/handler16.js +124 -0
- package/dist/packem_chunks/handler17.js +13 -0
- package/dist/packem_chunks/handler18.js +106 -0
- package/dist/packem_chunks/handler19.js +19 -0
- package/dist/packem_chunks/handler2.js +75 -0
- package/dist/packem_chunks/handler20.js +29 -0
- package/dist/packem_chunks/handler21.js +222 -0
- package/dist/packem_chunks/handler22.js +237 -0
- package/dist/packem_chunks/handler23.js +101 -0
- package/dist/packem_chunks/handler24.js +110 -0
- package/dist/packem_chunks/handler25.js +402 -0
- package/dist/packem_chunks/handler26.js +13 -0
- package/dist/packem_chunks/handler27.js +63 -0
- package/dist/packem_chunks/handler28.js +34 -0
- package/dist/packem_chunks/handler29.js +458 -0
- package/dist/packem_chunks/handler3.js +95 -0
- package/dist/packem_chunks/handler30.js +170 -0
- package/dist/packem_chunks/handler31.js +530 -0
- package/dist/packem_chunks/handler32.js +214 -0
- package/dist/packem_chunks/handler33.js +119 -0
- package/dist/packem_chunks/handler34.js +630 -0
- package/dist/packem_chunks/handler35.js +283 -0
- package/dist/packem_chunks/handler36.js +542 -0
- package/dist/packem_chunks/handler37.js +762 -0
- package/dist/packem_chunks/handler38.js +989 -0
- package/dist/packem_chunks/handler39.js +574 -0
- package/dist/packem_chunks/handler4.js +90 -0
- package/dist/packem_chunks/handler40.js +1685 -0
- package/dist/packem_chunks/handler41.js +1088 -0
- package/dist/packem_chunks/handler42.js +797 -0
- package/dist/packem_chunks/handler43.js +2658 -0
- package/dist/packem_chunks/handler44.js +3886 -0
- package/dist/packem_chunks/handler45.js +2574 -0
- package/dist/packem_chunks/handler46.js +3769 -0
- package/dist/packem_chunks/handler47.js +1491 -0
- package/dist/packem_chunks/handler5.js +174 -0
- package/dist/packem_chunks/handler6.js +95 -0
- package/dist/packem_chunks/handler7.js +115 -0
- package/dist/packem_chunks/handler8.js +12 -0
- package/dist/packem_chunks/handler9.js +29 -0
- package/dist/packem_chunks/heal-accept.js +522 -0
- package/dist/packem_chunks/heal.js +673 -0
- package/dist/packem_chunks/index.js +873 -0
- package/dist/packem_chunks/loader.js +23 -0
- package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +1316 -0
- package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +5 -0
- package/dist/packem_shared/ai-analysis-CHeB1joD.js +367 -0
- package/dist/packem_shared/ai-cache-Be_jexe4.js +142 -0
- package/dist/packem_shared/ai-fix-B9iQVcD2.js +379 -0
- package/dist/packem_shared/cache-directory-2qvs4goY.js +98 -0
- package/dist/packem_shared/catalog-BJTtyi-O.js +1371 -0
- package/dist/packem_shared/dependency-scan-A0KSklpG.js +188 -0
- package/dist/packem_shared/docker-2iZzc280.js +181 -0
- package/dist/packem_shared/failure-log-Cz3Z4SKL.js +100 -0
- package/dist/packem_shared/flakiness-goTxXuCX.js +180 -0
- package/dist/packem_shared/otel-DCvqCTz_.js +158 -0
- package/dist/packem_shared/otelPlugin-DFaLDvJf.js +3 -0
- package/dist/packem_shared/registry-CbqXI0rc.js +272 -0
- package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +130 -0
- package/dist/packem_shared/runtime-check-Cobi3p6l.js +127 -0
- package/dist/packem_shared/selectors-SM69TfqC.js +194 -0
- package/dist/packem_shared/symbols-Ta7g2nU-.js +14 -0
- package/dist/packem_shared/toolchain-BdZd9eBi.js +975 -0
- package/dist/packem_shared/typosquats-C-bCh3PX.js +1210 -0
- package/dist/packem_shared/use-measured-height-CNP0vT4M.js +20 -0
- package/dist/packem_shared/utils-CthVdBPS.js +40 -0
- package/dist/packem_shared/xxh3-Ck8mXNg1.js +239 -0
- package/index.js +773 -0
- package/package.json +82 -21
- package/schemas/project.schema.json +420 -0
- package/schemas/vis-config.schema.json +501 -0
- package/skills/vis/SKILL.md +96 -0
- package/templates/buildkite-ci/.buildkite/pipeline.yml.tera +85 -0
- package/templates/buildkite-ci/template.yml +20 -0
- package/dist/ai-analysis.d.ts +0 -40
- package/dist/ai-cache.d.ts +0 -21
- package/dist/bin.d.ts +0 -1
- package/dist/catalog.d.ts +0 -110
- package/dist/commands/affected.d.ts +0 -3
- package/dist/commands/ai.d.ts +0 -3
- package/dist/commands/analyze.d.ts +0 -3
- package/dist/commands/check.d.ts +0 -3
- package/dist/commands/graph.d.ts +0 -3
- package/dist/commands/hook/constants.d.ts +0 -8
- package/dist/commands/hook/index.d.ts +0 -3
- package/dist/commands/hook/install.d.ts +0 -7
- package/dist/commands/hook/migrate.d.ts +0 -27
- package/dist/commands/hook/uninstall.d.ts +0 -3
- package/dist/commands/migrate/constants.d.ts +0 -12
- package/dist/commands/migrate/deps.d.ts +0 -32
- package/dist/commands/migrate/index.d.ts +0 -3
- package/dist/commands/migrate/json.d.ts +0 -20
- package/dist/commands/migrate/lint-staged.d.ts +0 -62
- package/dist/commands/migrate/types.d.ts +0 -20
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/staged.d.ts +0 -3
- package/dist/commands/update.d.ts +0 -3
- package/dist/config.d.ts +0 -40
- package/dist/config.js +0 -1
- package/dist/package-manager.d.ts +0 -23
- package/dist/workspace.d.ts +0 -58
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
import { createRequire as __cjs_createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
const __cjs_require = __cjs_createRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
|
|
6
|
+
|
|
7
|
+
const __cjs_getBuiltinModule = (module) => {
|
|
8
|
+
// Check if we're in Node.js and version supports getBuiltinModule
|
|
9
|
+
if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
|
|
10
|
+
const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
|
|
11
|
+
// Node.js 20.16.0+ and 22.3.0+
|
|
12
|
+
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
|
|
13
|
+
return __cjs_getProcess.getBuiltinModule(module);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// Fallback to createRequire
|
|
17
|
+
return __cjs_require(module);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
import { dim, cyan, yellow, red, green } from '@visulima/colorize';
|
|
21
|
+
import { writeFileSync, isAccessibleSync, readJsonSync, readFileSync } from '@visulima/fs';
|
|
22
|
+
import { isAbsolute, relative, resolve, join } from '@visulima/path';
|
|
23
|
+
import { redact } from '@visulima/redact';
|
|
24
|
+
import { fingerprint, scan, scanFiles, inspectRuleset, listRules, listRequiredValidators } from '@visulima/secret-scanner';
|
|
25
|
+
import { p as pail } from './bin.js';
|
|
26
|
+
import { InteractiveManager, InteractiveStreamHook } from '@visulima/interactive-manager';
|
|
27
|
+
import { Spinner } from '@visulima/spinner';
|
|
28
|
+
const {
|
|
29
|
+
pathToFileURL
|
|
30
|
+
} = __cjs_getBuiltinModule("node:url");
|
|
31
|
+
const {
|
|
32
|
+
execFileSync
|
|
33
|
+
} = __cjs_getBuiltinModule("node:child_process");
|
|
34
|
+
|
|
35
|
+
const createSpinner = (options = {}) => {
|
|
36
|
+
const manager = new InteractiveManager(new InteractiveStreamHook(process.stdout), new InteractiveStreamHook(process.stderr));
|
|
37
|
+
return new Spinner(options, manager);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const toRelative = (file, root) => {
|
|
41
|
+
if (!isAbsolute(file)) {
|
|
42
|
+
return file;
|
|
43
|
+
}
|
|
44
|
+
const rel = relative(root, file);
|
|
45
|
+
return rel === "" || rel.startsWith("..") ? file : rel;
|
|
46
|
+
};
|
|
47
|
+
const toRelativeFinding = (f, root) => {
|
|
48
|
+
const relativeFile = toRelative(f.file, root);
|
|
49
|
+
return relativeFile === f.file ? f : { ...f, file: relativeFile };
|
|
50
|
+
};
|
|
51
|
+
const readBaseline = (baselinePath) => {
|
|
52
|
+
if (!isAccessibleSync(baselinePath)) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const parsed = readJsonSync(baselinePath);
|
|
57
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const diffBaseline = (findings, baselinePath, root) => {
|
|
63
|
+
const existing = readBaseline(baselinePath).map((f) => toRelativeFinding(f, root));
|
|
64
|
+
const existingKeys = new Set(existing.map((f) => fingerprint(f)));
|
|
65
|
+
const currentRelative = findings.map((f) => toRelativeFinding(f, root));
|
|
66
|
+
const currentKeys = new Set(currentRelative.map((f) => fingerprint(f)));
|
|
67
|
+
return {
|
|
68
|
+
fresh: currentRelative.filter((f) => !existingKeys.has(fingerprint(f))),
|
|
69
|
+
resolved: existing.filter((f) => !currentKeys.has(fingerprint(f))),
|
|
70
|
+
surviving: currentRelative.filter((f) => existingKeys.has(fingerprint(f)))
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const writeBaseline = (findings, baselinePath, root, options = {}) => {
|
|
74
|
+
const incoming = findings.map((f) => toRelativeFinding(f, root));
|
|
75
|
+
let final;
|
|
76
|
+
if (options.replace) {
|
|
77
|
+
final = incoming;
|
|
78
|
+
} else {
|
|
79
|
+
const existing = readBaseline(baselinePath).map((f) => toRelativeFinding(f, root));
|
|
80
|
+
const seen = /* @__PURE__ */ new Set();
|
|
81
|
+
final = [];
|
|
82
|
+
for (const f of [...existing, ...incoming]) {
|
|
83
|
+
const key = fingerprint(f);
|
|
84
|
+
if (seen.has(key)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
seen.add(key);
|
|
88
|
+
final.push(f);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
writeFileSync(baselinePath, `${JSON.stringify(final, null, 4)}
|
|
92
|
+
`);
|
|
93
|
+
return final.length;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const CONTEXT_RADIUS = 1;
|
|
97
|
+
const groupByFile = (findings) => {
|
|
98
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
99
|
+
for (const f of findings) {
|
|
100
|
+
const list = byFile.get(f.file);
|
|
101
|
+
if (list) {
|
|
102
|
+
list.push(f);
|
|
103
|
+
} else {
|
|
104
|
+
byFile.set(f.file, [f]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return byFile;
|
|
108
|
+
};
|
|
109
|
+
const loadLines = (file) => {
|
|
110
|
+
try {
|
|
111
|
+
return readFileSync(file).split(/\r?\n/);
|
|
112
|
+
} catch {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const caretFor = (line, col, len) => {
|
|
117
|
+
const clampedCol = Math.max(1, col);
|
|
118
|
+
const prefix = line.slice(0, clampedCol - 1).replaceAll(/[^\t]/g, " ");
|
|
119
|
+
return `${prefix}${"^".repeat(Math.max(1, len))}`;
|
|
120
|
+
};
|
|
121
|
+
const formatText = (findings, root, useColor, options = {}) => {
|
|
122
|
+
if (findings.length === 0) {
|
|
123
|
+
return useColor ? dim("No secrets detected.") : "No secrets detected.";
|
|
124
|
+
}
|
|
125
|
+
const color = useColor ? { cyan, dim, green, red, yellow } : {
|
|
126
|
+
cyan: (s) => s,
|
|
127
|
+
dim: (s) => s,
|
|
128
|
+
green: (s) => s,
|
|
129
|
+
red: (s) => s,
|
|
130
|
+
yellow: (s) => s
|
|
131
|
+
};
|
|
132
|
+
const lines = [];
|
|
133
|
+
const byFile = groupByFile(findings);
|
|
134
|
+
for (const [file, items] of byFile) {
|
|
135
|
+
const relativeFile = relative(root, file) || file;
|
|
136
|
+
lines.push(color.cyan(relativeFile));
|
|
137
|
+
const sourceLines = options.redact ? void 0 : loadLines(file);
|
|
138
|
+
for (const f of items) {
|
|
139
|
+
const provenance = [f.source, f.confidence].filter(Boolean).join(", ");
|
|
140
|
+
const provenanceSuffix = provenance ? ` ${color.dim(`(${provenance})`)}` : "";
|
|
141
|
+
const alternates = f.alternateMatches && f.alternateMatches.length > 0 ? ` ${color.dim(`also: ${f.alternateMatches.join(", ")}`)}` : "";
|
|
142
|
+
let validationBadge = "";
|
|
143
|
+
switch (f.validation) {
|
|
144
|
+
case "error": {
|
|
145
|
+
validationBadge = ` ${color.yellow("! error")}`;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "rejected": {
|
|
149
|
+
validationBadge = ` ${color.red("✗ rejected")}`;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "skipped": {
|
|
153
|
+
validationBadge = ` ${color.dim("— unverifiable")}`;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "verified": {
|
|
157
|
+
validationBadge = ` ${color.green("✓ verified")}`;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
lines.push(
|
|
162
|
+
` ${color.red("✖")} ${color.yellow(`[${f.ruleId}]`)}${provenanceSuffix}${validationBadge} ${color.dim(`line ${String(f.startLine)}:${String(f.startColumn)}`)}${alternates}`
|
|
163
|
+
);
|
|
164
|
+
if (sourceLines) {
|
|
165
|
+
const start = Math.max(0, f.startLine - 1 - CONTEXT_RADIUS);
|
|
166
|
+
const end = Math.min(sourceLines.length, f.startLine + CONTEXT_RADIUS);
|
|
167
|
+
for (let n = start; n < end; n += 1) {
|
|
168
|
+
const lineNumber = String(n + 1).padStart(4, " ");
|
|
169
|
+
const isMatchLine = n + 1 === f.startLine;
|
|
170
|
+
const marker = isMatchLine ? color.red("▶") : " ";
|
|
171
|
+
lines.push(` ${marker} ${color.dim(lineNumber)} │ ${sourceLines[n] ?? ""}`);
|
|
172
|
+
if (isMatchLine) {
|
|
173
|
+
const matchLen = Math.max(1, (f.endColumn ?? f.startColumn + 1) - f.startColumn);
|
|
174
|
+
const caret = caretFor(sourceLines[n] ?? "", f.startColumn, matchLen);
|
|
175
|
+
lines.push(` ${color.dim(" │ ")}${color.red(caret)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
lines.push("");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return lines.join("\n").trimEnd();
|
|
183
|
+
};
|
|
184
|
+
const toSarifUri = (path, root) => {
|
|
185
|
+
if (!isAbsolute(path)) {
|
|
186
|
+
return path.replaceAll("\\", "/");
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
return pathToFileURL(path).toString();
|
|
190
|
+
} catch {
|
|
191
|
+
return `file://${resolve(root, path).replaceAll("\\", "/")}`;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const shortText = (text, limit = 100) => {
|
|
195
|
+
if (text.length <= limit) {
|
|
196
|
+
return text;
|
|
197
|
+
}
|
|
198
|
+
return `${text.slice(0, limit - 1).trimEnd()}…`;
|
|
199
|
+
};
|
|
200
|
+
const formatSarif = (findings, toolVersion, root = process.cwd(), ruleMetadata = []) => {
|
|
201
|
+
const metaById = new Map(ruleMetadata.map((r) => [r.id, r]));
|
|
202
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
203
|
+
for (const f of findings) {
|
|
204
|
+
seenIds.add(f.ruleId);
|
|
205
|
+
}
|
|
206
|
+
const ruleIds = [.../* @__PURE__ */ new Set([...metaById.keys(), ...seenIds])].sort((a, b) => a.localeCompare(b));
|
|
207
|
+
const ruleIndex = new Map(ruleIds.map((id, i) => [id, i]));
|
|
208
|
+
return JSON.stringify(
|
|
209
|
+
{
|
|
210
|
+
$schema: "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json",
|
|
211
|
+
runs: [
|
|
212
|
+
{
|
|
213
|
+
originalUriBaseIds: {
|
|
214
|
+
SRCROOT: { uri: pathToFileURL(root).toString() }
|
|
215
|
+
},
|
|
216
|
+
results: findings.map((f) => {
|
|
217
|
+
const properties = {};
|
|
218
|
+
if (f.source) {
|
|
219
|
+
properties["source"] = f.source;
|
|
220
|
+
}
|
|
221
|
+
if (f.confidence) {
|
|
222
|
+
properties["confidence"] = f.confidence;
|
|
223
|
+
}
|
|
224
|
+
if (f.alternateMatches && f.alternateMatches.length > 0) {
|
|
225
|
+
properties["alternateRules"] = f.alternateMatches;
|
|
226
|
+
}
|
|
227
|
+
if (f.validation) {
|
|
228
|
+
properties["validation"] = f.validation;
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
level: "error",
|
|
232
|
+
locations: [
|
|
233
|
+
{
|
|
234
|
+
physicalLocation: {
|
|
235
|
+
artifactLocation: {
|
|
236
|
+
uri: toSarifUri(f.file, root),
|
|
237
|
+
uriBaseId: isAbsolute(f.file) ? void 0 : "SRCROOT"
|
|
238
|
+
},
|
|
239
|
+
region: {
|
|
240
|
+
endColumn: f.endColumn,
|
|
241
|
+
endLine: f.endLine,
|
|
242
|
+
snippet: { text: f.match },
|
|
243
|
+
startColumn: f.startColumn,
|
|
244
|
+
startLine: f.startLine
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
],
|
|
249
|
+
message: { text: f.description || f.ruleId },
|
|
250
|
+
properties: Object.keys(properties).length > 0 ? properties : void 0,
|
|
251
|
+
ruleId: f.ruleId,
|
|
252
|
+
ruleIndex: ruleIndex.get(f.ruleId) ?? -1
|
|
253
|
+
};
|
|
254
|
+
}),
|
|
255
|
+
tool: {
|
|
256
|
+
driver: {
|
|
257
|
+
informationUri: "https://visulima.com/packages/secret-scanner",
|
|
258
|
+
name: "visulima-secret-scanner",
|
|
259
|
+
rules: ruleIds.map((id) => {
|
|
260
|
+
const meta = metaById.get(id);
|
|
261
|
+
const description = meta?.description ?? `Detected by rule \`${id}\``;
|
|
262
|
+
const ruleProperties = {};
|
|
263
|
+
if (meta?.tags && meta.tags.length > 0) {
|
|
264
|
+
ruleProperties["tags"] = meta.tags;
|
|
265
|
+
}
|
|
266
|
+
if (meta?.source) {
|
|
267
|
+
ruleProperties["source"] = meta.source;
|
|
268
|
+
}
|
|
269
|
+
if (meta?.confidence) {
|
|
270
|
+
ruleProperties["confidence"] = meta.confidence;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
defaultConfiguration: { level: "error" },
|
|
274
|
+
fullDescription: { text: description },
|
|
275
|
+
helpUri: "https://visulima.com/packages/secret-scanner",
|
|
276
|
+
id,
|
|
277
|
+
name: id,
|
|
278
|
+
properties: Object.keys(ruleProperties).length > 0 ? ruleProperties : void 0,
|
|
279
|
+
shortDescription: { text: shortText(description) }
|
|
280
|
+
};
|
|
281
|
+
}),
|
|
282
|
+
version: toolVersion
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
],
|
|
287
|
+
version: "2.1.0"
|
|
288
|
+
},
|
|
289
|
+
void 0,
|
|
290
|
+
2
|
|
291
|
+
);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const runGit = (root, args) => {
|
|
295
|
+
try {
|
|
296
|
+
return execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
297
|
+
} catch {
|
|
298
|
+
return "";
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
const splitFiles = (stdout) => stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
302
|
+
const stagedFiles = (root) => splitFiles(runGit(root, ["diff", "--cached", "--name-only", "--diff-filter=ACMR"])).map((p) => isAbsolute(p) ? p : join(root, p));
|
|
303
|
+
const filesSince = (root, ref) => {
|
|
304
|
+
const stdout = runGit(root, ["diff", "--name-only", "--diff-filter=ACMR", `${ref}...HEAD`]);
|
|
305
|
+
const list = splitFiles(stdout);
|
|
306
|
+
if (list.length === 0) {
|
|
307
|
+
const fallback = runGit(root, ["diff", "--name-only", "--diff-filter=ACMR", ref]);
|
|
308
|
+
return splitFiles(fallback).map((p) => isAbsolute(p) ? p : join(root, p));
|
|
309
|
+
}
|
|
310
|
+
return list.map((p) => isAbsolute(p) ? p : join(root, p));
|
|
311
|
+
};
|
|
312
|
+
const hasGit = (root) => runGit(root, ["rev-parse", "--show-toplevel"]).length > 0;
|
|
313
|
+
|
|
314
|
+
const DEFAULT_BASELINE = ".secrets-baseline.json";
|
|
315
|
+
const toArray = (value) => {
|
|
316
|
+
if (!value) {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
return Array.isArray(value) ? value : [value];
|
|
320
|
+
};
|
|
321
|
+
const validateFormat = (raw) => {
|
|
322
|
+
const allowed = /* @__PURE__ */ new Set(["json", "sarif", "text"]);
|
|
323
|
+
if (raw && !allowed.has(raw)) {
|
|
324
|
+
pail.error(`--format must be one of: ${[...allowed].join(", ")} (got "${raw}")`);
|
|
325
|
+
process.exit(2);
|
|
326
|
+
}
|
|
327
|
+
return raw ?? "text";
|
|
328
|
+
};
|
|
329
|
+
const validateConfidence = (raw) => {
|
|
330
|
+
if (raw === void 0) {
|
|
331
|
+
return void 0;
|
|
332
|
+
}
|
|
333
|
+
const allowed = /* @__PURE__ */ new Set(["high", "low", "medium"]);
|
|
334
|
+
if (!allowed.has(raw)) {
|
|
335
|
+
pail.error(`--min-confidence must be one of: ${[...allowed].join(", ")} (got "${raw}")`);
|
|
336
|
+
process.exit(2);
|
|
337
|
+
}
|
|
338
|
+
return raw;
|
|
339
|
+
};
|
|
340
|
+
const printListRules = async (scanOptions, useColor) => {
|
|
341
|
+
const rules = await listRules(scanOptions);
|
|
342
|
+
process.stdout.write(`${String(rules.length)} rules loaded
|
|
343
|
+
|
|
344
|
+
`);
|
|
345
|
+
for (const rule of rules) {
|
|
346
|
+
const id = useColor ? yellow(rule.id) : rule.id;
|
|
347
|
+
const tags = rule.tags.length > 0 ? ` ${useColor ? dim(`[${rule.tags.join(", ")}]`) : `[${rule.tags.join(", ")}]`}` : "";
|
|
348
|
+
process.stdout.write(`${id}${tags}
|
|
349
|
+
${rule.description}
|
|
350
|
+
`);
|
|
351
|
+
if (rule.keywords.length > 0) {
|
|
352
|
+
const kw = `keywords: ${rule.keywords.slice(0, 6).join(", ")}${rule.keywords.length > 6 ? ", ..." : ""}`;
|
|
353
|
+
process.stdout.write(` ${useColor ? dim(kw) : kw}
|
|
354
|
+
`);
|
|
355
|
+
}
|
|
356
|
+
process.stdout.write("\n");
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
const printListValidators = async (scanOptions, useColor) => {
|
|
360
|
+
const report = await listRequiredValidators(scanOptions);
|
|
361
|
+
if (report.length === 0) {
|
|
362
|
+
process.stdout.write(
|
|
363
|
+
useColor ? `${dim("No non-HTTP validators required by the current ruleset.")}
|
|
364
|
+
` : "No non-HTTP validators required by the current ruleset.\n"
|
|
365
|
+
);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
process.stdout.write(`${String(report.length)} non-HTTP validator type(s) referenced by the current ruleset:
|
|
369
|
+
|
|
370
|
+
`);
|
|
371
|
+
for (const entry of report) {
|
|
372
|
+
const title = useColor ? yellow(entry.displayName) : entry.displayName;
|
|
373
|
+
const typeLabel = `(${entry.type}, ${String(entry.ruleCount)} rule${entry.ruleCount === 1 ? "" : "s"})`;
|
|
374
|
+
process.stdout.write(`${title} ${useColor ? dim(typeLabel) : typeLabel}
|
|
375
|
+
`);
|
|
376
|
+
process.stdout.write(` ${entry.summary}
|
|
377
|
+
`);
|
|
378
|
+
const hint = entry.packageName ? `install: npm add ${entry.packageName}` : "no driver — bespoke implementation required";
|
|
379
|
+
process.stdout.write(` ${useColor ? dim(hint) : hint}
|
|
380
|
+
|
|
381
|
+
`);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
const runInit = async (root, scanOptions, dryRun) => {
|
|
385
|
+
const baselinePath = join(root, DEFAULT_BASELINE);
|
|
386
|
+
if (!dryRun && isAccessibleSync(baselinePath)) {
|
|
387
|
+
pail.warn(`Detected existing ${DEFAULT_BASELINE} — refusing to overwrite. Delete it first to re-init.`);
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
pail.info(dryRun ? "[dry-run] Previewing init — no files will be written." : "Scanning workspace to seed baseline…");
|
|
391
|
+
const spinner = createSpinner();
|
|
392
|
+
spinner.start("scanning");
|
|
393
|
+
let findings;
|
|
394
|
+
try {
|
|
395
|
+
findings = await scan([root], scanOptions);
|
|
396
|
+
spinner.succeed();
|
|
397
|
+
} catch (error) {
|
|
398
|
+
spinner.failed();
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
if (dryRun) {
|
|
402
|
+
pail.info(`[dry-run] Would create ${DEFAULT_BASELINE} with ${String(findings.length)} finding(s).`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const count = writeBaseline(findings, baselinePath, root, { replace: true });
|
|
406
|
+
pail.success(`Wrote ${DEFAULT_BASELINE} (${String(count)} findings).`);
|
|
407
|
+
pail.notice("Commit it. Use `vis secrets --baseline .secrets-baseline.json` in CI. Add path patterns to .gitignore to exclude directories from scanning.");
|
|
408
|
+
};
|
|
409
|
+
const resolveScanOptions = (flags, visSecrets, root) => {
|
|
410
|
+
const cfg = visSecrets ?? {};
|
|
411
|
+
const resolvePath = (p) => p ? resolve(root, p) : void 0;
|
|
412
|
+
const pickList = (flag, fallback) => {
|
|
413
|
+
const fromFlag = toArray(flag);
|
|
414
|
+
return fromFlag.length > 0 ? fromFlag : fallback;
|
|
415
|
+
};
|
|
416
|
+
const enableRules = pickList(flags.enableRule, cfg.rules?.enable);
|
|
417
|
+
const excludeRules = pickList(flags.excludeRule, cfg.rules?.exclude);
|
|
418
|
+
const includeRules = pickList(flags.includeRule, cfg.rules?.include);
|
|
419
|
+
const excludePatterns = pickList(flags.exclude, cfg.walk?.excludePatterns);
|
|
420
|
+
const excludeFromFlag = toArray(flags.excludeFrom).map((p) => resolve(root, p));
|
|
421
|
+
const excludeFromFiles = excludeFromFlag.length > 0 ? excludeFromFlag : cfg.walk?.excludeFromFiles?.map((p) => resolve(root, p));
|
|
422
|
+
const baselinePath = resolvePath(flags.baseline) ?? resolvePath(cfg.baseline);
|
|
423
|
+
const configPath = resolvePath(flags.config) ?? resolvePath(cfg.config?.path);
|
|
424
|
+
const minConfidence = validateConfidence(flags.minConfidence ?? cfg.config?.minConfidence);
|
|
425
|
+
return {
|
|
426
|
+
baseline: baselinePath,
|
|
427
|
+
concurrency: flags.concurrency,
|
|
428
|
+
config: {
|
|
429
|
+
extendBundled: flags.noExtendBundled ? false : cfg.config?.extendBundled,
|
|
430
|
+
inline: cfg.config?.inline,
|
|
431
|
+
minConfidence,
|
|
432
|
+
onlyVerified: flags.onlyVerified ?? cfg.config?.onlyVerified ?? false,
|
|
433
|
+
path: configPath,
|
|
434
|
+
validate: flags.validate ?? cfg.config?.validate ?? false
|
|
435
|
+
},
|
|
436
|
+
redact: flags.redact ?? cfg.redact,
|
|
437
|
+
rules: { enable: enableRules, exclude: excludeRules, include: includeRules },
|
|
438
|
+
verbose: flags.verbose ?? false,
|
|
439
|
+
walk: {
|
|
440
|
+
excludeFromFiles,
|
|
441
|
+
excludePatterns,
|
|
442
|
+
gitignore: flags.noGitignore ? false : cfg.walk?.gitignore ?? true,
|
|
443
|
+
includeHidden: flags.includeHidden ?? cfg.walk?.includeHidden,
|
|
444
|
+
maxFileSize: flags.maxSize ?? cfg.walk?.maxFileSize
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
};
|
|
448
|
+
const printDiff = (diff) => {
|
|
449
|
+
process.stderr.write(
|
|
450
|
+
`${dim("baseline diff: ")}${green(`+${String(diff.fresh.length)} new`)} · ${yellow(`${String(diff.surviving.length)} unchanged`)} · ${dim(`-${String(diff.resolved.length)} resolved`)}
|
|
451
|
+
`
|
|
452
|
+
);
|
|
453
|
+
};
|
|
454
|
+
const chooseScanPaths = async (flags, args, root) => {
|
|
455
|
+
if (flags.staged) {
|
|
456
|
+
if (!hasGit(root)) {
|
|
457
|
+
pail.error("--staged requires a git working tree, and none was detected.");
|
|
458
|
+
process.exit(2);
|
|
459
|
+
}
|
|
460
|
+
return { files: stagedFiles(root) };
|
|
461
|
+
}
|
|
462
|
+
if (flags.since) {
|
|
463
|
+
if (!hasGit(root)) {
|
|
464
|
+
pail.error("--since requires a git working tree, and none was detected.");
|
|
465
|
+
process.exit(2);
|
|
466
|
+
}
|
|
467
|
+
return { files: filesSince(root, flags.since) };
|
|
468
|
+
}
|
|
469
|
+
if (flags.affected) {
|
|
470
|
+
if (!hasGit(root)) {
|
|
471
|
+
pail.warn("--affected requires git; falling back to full scan");
|
|
472
|
+
return { paths: args && args.length > 0 ? args.map((p) => resolve(root, p)) : [root] };
|
|
473
|
+
}
|
|
474
|
+
const baseRef = process.env["VIS_BASE"] ?? "HEAD~1";
|
|
475
|
+
return { files: filesSince(root, baseRef) };
|
|
476
|
+
}
|
|
477
|
+
return { paths: args && args.length > 0 ? args.map((p) => resolve(root, p)) : [root] };
|
|
478
|
+
};
|
|
479
|
+
const emitFindings = (findings, format, root, useColor, toolVersion, ruleMetadata, redactFindings) => {
|
|
480
|
+
switch (format) {
|
|
481
|
+
case "json": {
|
|
482
|
+
const view = findings.map((f) => toRelativeFinding(f, root));
|
|
483
|
+
process.stdout.write(`${JSON.stringify(view, null, 2)}
|
|
484
|
+
`);
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
case "sarif": {
|
|
488
|
+
process.stdout.write(`${formatSarif(findings, toolVersion, root, ruleMetadata)}
|
|
489
|
+
`);
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
default: {
|
|
493
|
+
process.stdout.write(`${formatText(findings, root, useColor, { redact: redactFindings })}
|
|
494
|
+
`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const execute = async ({ argument, options, visConfig, workspaceRoot }) => {
|
|
499
|
+
const flags = options;
|
|
500
|
+
const args = argument;
|
|
501
|
+
const root = workspaceRoot ?? process.cwd();
|
|
502
|
+
const useColor = !flags.quiet && process.stdout.isTTY;
|
|
503
|
+
const visSecrets = visConfig?.secrets;
|
|
504
|
+
const scanOptions = resolveScanOptions(flags, visSecrets, root);
|
|
505
|
+
const toolVersion = "0.0.0-alpha";
|
|
506
|
+
if (flags.listRules) {
|
|
507
|
+
await printListRules(scanOptions, useColor);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (flags.listValidators) {
|
|
511
|
+
await printListValidators(scanOptions, useColor);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (flags.init) {
|
|
515
|
+
await runInit(root, scanOptions, flags.dryRun ?? false);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const target = await chooseScanPaths(flags, args ?? [], root);
|
|
519
|
+
if (target.files?.length === 0) {
|
|
520
|
+
if (!flags.quiet) {
|
|
521
|
+
pail.success("No files to scan.");
|
|
522
|
+
}
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const isInteractive = !flags.quiet && !["json", "sarif"].includes(flags.format ?? "text");
|
|
526
|
+
const spinner = createSpinner({ verbose: isInteractive });
|
|
527
|
+
spinner.start("scanning for secrets");
|
|
528
|
+
let findings;
|
|
529
|
+
try {
|
|
530
|
+
findings = target.files === void 0 ? await scan(target.paths ?? [root], scanOptions) : await scanFiles(target.files, scanOptions);
|
|
531
|
+
spinner.succeed();
|
|
532
|
+
} catch (error) {
|
|
533
|
+
spinner.failed();
|
|
534
|
+
pail.error(`secret scan failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
535
|
+
process.exit(2);
|
|
536
|
+
}
|
|
537
|
+
if (flags.verbose) {
|
|
538
|
+
const skipped = await inspectRuleset(scanOptions);
|
|
539
|
+
if (skipped.length > 0) {
|
|
540
|
+
pail.warn(`${String(skipped.length)} rule(s) skipped due to invalid regex. First few:`);
|
|
541
|
+
for (const s of skipped.slice(0, 5)) {
|
|
542
|
+
process.stderr.write(` - ${s.ruleId}: ${s.reason}
|
|
543
|
+
`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const baselineFullPath = scanOptions.baseline ?? join(root, DEFAULT_BASELINE);
|
|
548
|
+
const showDiff = !flags.quiet && isAccessibleSync(baselineFullPath);
|
|
549
|
+
if (flags.updateBaseline) {
|
|
550
|
+
const count = writeBaseline(findings, baselineFullPath, root, { replace: flags.replaceBaseline });
|
|
551
|
+
pail.success(`Baseline updated: ${relative(root, baselineFullPath) || baselineFullPath} now contains ${String(count)} entries.`);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const format = validateFormat(flags.format);
|
|
555
|
+
const ruleMetadata = format === "sarif" ? await listRules(scanOptions).catch(() => []) : [];
|
|
556
|
+
const shouldRedact = scanOptions.redact === true;
|
|
557
|
+
const reportFindings = shouldRedact ? redact(findings, ["match", "secret"]) : findings;
|
|
558
|
+
emitFindings(reportFindings, format, root, useColor, toolVersion, ruleMetadata, shouldRedact);
|
|
559
|
+
if (format === "text" && showDiff) {
|
|
560
|
+
printDiff(diffBaseline(findings, baselineFullPath, root));
|
|
561
|
+
}
|
|
562
|
+
if (findings.length > 0) {
|
|
563
|
+
if (!flags.quiet) {
|
|
564
|
+
pail.warn(`${String(findings.length)} potential secret(s) found`);
|
|
565
|
+
pail.notice("Suppress individual lines with `gitleaks:allow` / `secret-scanner:allow`, or run `vis secrets --update-baseline`.");
|
|
566
|
+
}
|
|
567
|
+
process.exit(1);
|
|
568
|
+
}
|
|
569
|
+
if (!flags.quiet) {
|
|
570
|
+
pail.success("No secrets detected.");
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
export { execute as default };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createRequire as __cjs_createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
const __cjs_require = __cjs_createRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
|
|
6
|
+
|
|
7
|
+
const __cjs_getBuiltinModule = (module) => {
|
|
8
|
+
// Check if we're in Node.js and version supports getBuiltinModule
|
|
9
|
+
if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
|
|
10
|
+
const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
|
|
11
|
+
// Node.js 20.16.0+ and 22.3.0+
|
|
12
|
+
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
|
|
13
|
+
return __cjs_getProcess.getBuiltinModule(module);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// Fallback to createRequire
|
|
17
|
+
return __cjs_require(module);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
spawnSync
|
|
22
|
+
} = __cjs_getBuiltinModule("node:child_process");
|
|
23
|
+
import { c as detectPm, p as pail, s as scanUnapprovedBuildScripts, e as syncAllowBuildsToNativeConfig } from './bin.js';
|
|
24
|
+
|
|
25
|
+
const execute = async ({ options, visConfig, workspaceRoot: wsRoot }) => {
|
|
26
|
+
const cwd = wsRoot ?? process.cwd();
|
|
27
|
+
const pm = detectPm(cwd);
|
|
28
|
+
if (pm.name === "pnpm" && !options.scan) {
|
|
29
|
+
pail.info("Delegating to pnpm approve-builds...");
|
|
30
|
+
const pnpmArgs = ["approve-builds"];
|
|
31
|
+
if (options.all) {
|
|
32
|
+
pnpmArgs.push("--all");
|
|
33
|
+
}
|
|
34
|
+
const result = spawnSync("pnpm", pnpmArgs, { cwd, stdio: "inherit" });
|
|
35
|
+
if (result.error) {
|
|
36
|
+
throw new Error(`Failed to run pnpm approve-builds: ${result.error.message}`);
|
|
37
|
+
}
|
|
38
|
+
if (result.status !== 0 && result.status !== null) {
|
|
39
|
+
pail.error(`pnpm approve-builds exited with code ${result.status}`);
|
|
40
|
+
process.exitCode = result.status;
|
|
41
|
+
}
|
|
42
|
+
if (!options.syncNative) {
|
|
43
|
+
pail.notice("");
|
|
44
|
+
pail.notice("Tip: vis.config.ts security.allowBuilds may now be out of sync with pnpm-workspace.yaml.");
|
|
45
|
+
pail.notice("Run 'vis check --security-config' to compare, or copy the new entries into vis.config.ts.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
const allowBuilds = visConfig?.security?.allowBuilds ?? {};
|
|
50
|
+
const unapproved = scanUnapprovedBuildScripts(cwd, allowBuilds);
|
|
51
|
+
if (unapproved.length === 0) {
|
|
52
|
+
pail.success("No unapproved build scripts found.");
|
|
53
|
+
} else {
|
|
54
|
+
pail.warn(`Found ${unapproved.length} package${unapproved.length === 1 ? "" : "s"} with unapproved build scripts:
|
|
55
|
+
`);
|
|
56
|
+
for (const pkg of unapproved) {
|
|
57
|
+
pail.info(` ${pkg}`);
|
|
58
|
+
}
|
|
59
|
+
pail.notice("");
|
|
60
|
+
pail.notice("To approve these packages, add them to vis.config.ts:");
|
|
61
|
+
pail.notice("");
|
|
62
|
+
pail.notice(" security: {");
|
|
63
|
+
pail.notice(" allowBuilds: {");
|
|
64
|
+
for (const pkg of unapproved) {
|
|
65
|
+
const name = pkg.split(" (")[0];
|
|
66
|
+
pail.notice(` "${name}": true,`);
|
|
67
|
+
}
|
|
68
|
+
pail.notice(" },");
|
|
69
|
+
pail.notice(" },");
|
|
70
|
+
if (pm.name === "pnpm") {
|
|
71
|
+
pail.notice("");
|
|
72
|
+
pail.notice("Or run 'pnpm approve-builds' to update pnpm-workspace.yaml directly.");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (options.syncNative) {
|
|
77
|
+
const allowBuilds = visConfig?.security?.allowBuilds ?? {};
|
|
78
|
+
if (Object.keys(allowBuilds).length === 0) {
|
|
79
|
+
pail.warn("No security.allowBuilds configured in vis.config.ts. Nothing to sync.");
|
|
80
|
+
} else {
|
|
81
|
+
const actions = syncAllowBuildsToNativeConfig(pm.name, cwd, allowBuilds);
|
|
82
|
+
pail.info("");
|
|
83
|
+
for (const action of actions) {
|
|
84
|
+
pail.success(action);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export { execute as default };
|