safeey-cli 0.2.1 → 0.6.1
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/bin/safeey.js +174 -20
- package/package.json +2 -2
- package/src/auth.js +232 -0
- package/src/baseline.js +42 -0
- package/src/config.js +1 -0
- package/src/engine/githistory.js +84 -0
- package/src/engine/infra.js +46 -0
- package/src/engine/osv.js +173 -0
- package/src/engine/patterns.js +2 -1
- package/src/engine/secrets.js +37 -1
- package/src/engine/suppress.js +50 -0
- package/src/engine/taint.js +182 -0
- package/src/fix.js +116 -0
- package/src/report-sarif.js +67 -0
- package/src/rules/framework.js +102 -0
- package/src/rules/infra.js +120 -0
- package/src/rules/js-rules.js +114 -0
- package/src/scan.js +88 -29
- package/src/spinner.js +97 -0
package/src/spinner.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/spinner.js — lightweight progress UX for the scan.
|
|
2
|
+
//
|
|
3
|
+
// Writes to stderr (so --json/--sarif on stdout stays clean), animates in place
|
|
4
|
+
// on a TTY, and degrades to plain one-line-per-phase output when not a TTY
|
|
5
|
+
// (e.g. CI logs). The goal is simply to carry the user along: show that files
|
|
6
|
+
// are being scanned, then dependencies, then history, then grading.
|
|
7
|
+
|
|
8
|
+
const FRAMES = ["\u280b", "\u2819", "\u2839", "\u2838", "\u283c", "\u2834", "\u2826", "\u2827", "\u2807", "\u280f"];
|
|
9
|
+
const GREEN = "\x1b[32m", DIM = "\x1b[2m", RESET = "\x1b[0m";
|
|
10
|
+
|
|
11
|
+
class Spinner {
|
|
12
|
+
constructor(stream = process.stderr) {
|
|
13
|
+
this.stream = stream;
|
|
14
|
+
this.tty = !!stream.isTTY;
|
|
15
|
+
this.text = "";
|
|
16
|
+
this.frame = 0;
|
|
17
|
+
this.timer = null;
|
|
18
|
+
this.start = Date.now();
|
|
19
|
+
this.lastPlain = "";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_render() {
|
|
23
|
+
if (!this.tty) return;
|
|
24
|
+
const f = FRAMES[(this.frame = (this.frame + 1) % FRAMES.length)];
|
|
25
|
+
this.stream.write(`\r\x1b[2K ${GREEN}${f}${RESET} ${this.text}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
begin(text) {
|
|
29
|
+
this.text = text;
|
|
30
|
+
if (this.tty) {
|
|
31
|
+
if (!this.timer) this.timer = setInterval(() => this._render(), 80);
|
|
32
|
+
this._render();
|
|
33
|
+
} else {
|
|
34
|
+
this.stream.write(` ${text}\n`);
|
|
35
|
+
this.lastPlain = text;
|
|
36
|
+
}
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Update the current line's text (in place on a TTY; new line otherwise,
|
|
41
|
+
// but only when the headline phase changes, to avoid log spam).
|
|
42
|
+
update(text, { phaseChanged = false } = {}) {
|
|
43
|
+
this.text = text;
|
|
44
|
+
if (this.tty) this._render();
|
|
45
|
+
else if (phaseChanged && text !== this.lastPlain) { this.stream.write(` ${text}\n`); this.lastPlain = text; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Stop and erase the line (TTY), so the report/output prints cleanly after.
|
|
49
|
+
stop() {
|
|
50
|
+
if (this.timer) { clearInterval(this.timer); this.timer = null; }
|
|
51
|
+
if (this.tty) this.stream.write("\r\x1b[2K");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Stop and leave a final check-marked line in place.
|
|
55
|
+
done(text) {
|
|
56
|
+
if (this.timer) { clearInterval(this.timer); this.timer = null; }
|
|
57
|
+
const secs = ((Date.now() - this.start) / 1000).toFixed(1);
|
|
58
|
+
if (this.tty) {
|
|
59
|
+
this.stream.write(`\r\x1b[2K ${GREEN}\u2714${RESET} ${text} ${DIM}(${secs}s)${RESET}\n`);
|
|
60
|
+
} else {
|
|
61
|
+
this.stream.write(` \u2714 ${text} (${secs}s)\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Build an onProgress handler for scan() that drives a spinner. Throttled so we
|
|
67
|
+
// don't repaint on every single file.
|
|
68
|
+
function attachProgress(spinner) {
|
|
69
|
+
let lastPaint = 0;
|
|
70
|
+
let curPhase = null;
|
|
71
|
+
return (ev) => {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
if (ev.phase !== curPhase) {
|
|
74
|
+
curPhase = ev.phase;
|
|
75
|
+
const headline = {
|
|
76
|
+
discover: "Discovering files\u2026",
|
|
77
|
+
scan: "Scanning source\u2026",
|
|
78
|
+
osv: "Checking dependencies against OSV.dev\u2026",
|
|
79
|
+
history: "Scanning git history for secrets\u2026",
|
|
80
|
+
grade: "Grading results\u2026",
|
|
81
|
+
}[ev.phase] || ev.phase;
|
|
82
|
+
spinner.update(headline, { phaseChanged: true });
|
|
83
|
+
lastPaint = now;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (ev.phase === "scan" && ev.total) {
|
|
87
|
+
if (now - lastPaint < 60) return; // throttle
|
|
88
|
+
lastPaint = now;
|
|
89
|
+
const name = ev.file ? ` ${DIM}${truncate(ev.file, 40)}${RESET}` : "";
|
|
90
|
+
spinner.update(`Scanning source ${ev.current}/${ev.total}\u2026${name}`);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function truncate(s, n) { return s.length <= n ? s : "\u2026" + s.slice(-(n - 1)); }
|
|
96
|
+
|
|
97
|
+
module.exports = { Spinner, attachProgress };
|