secureflow-cli 1.2.0 → 2.1.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/exporters.d.ts +49 -0
- package/dist/exporters.d.ts.map +1 -0
- package/dist/exporters.js +187 -0
- package/dist/exporters.js.map +1 -0
- package/dist/git.d.ts +2 -2
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +10 -10
- package/dist/git.js.map +1 -1
- package/dist/index.js +84 -21
- package/dist/index.js.map +1 -1
- package/dist/lib/api-client.d.ts +41 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +62 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/sarif.d.ts +4 -4
- package/dist/sarif.d.ts.map +1 -1
- package/dist/sarif.js +31 -31
- package/dist/sarif.js.map +1 -1
- package/dist/scanner.d.ts +2 -2
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +98 -46
- package/dist/scanner.js.map +1 -1
- package/package.json +35 -35
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alternative Export Formatters for SecureFlow CLI (#811)
|
|
3
|
+
*
|
|
4
|
+
* Provides CSV and HTML report generation for stakeholders who do not use
|
|
5
|
+
* SARIF viewers. Both formatters are zero-dependency, using pure string
|
|
6
|
+
* templates to keep the CLI lightweight.
|
|
7
|
+
*
|
|
8
|
+
* PDF generation was intentionally not added because it would introduce a
|
|
9
|
+
* heavyweight PDF-generation dependency (e.g. pdfkit, puppeteer). HTML
|
|
10
|
+
* provides a portable report that users can directly print/save as PDF
|
|
11
|
+
* from any browser.
|
|
12
|
+
*/
|
|
13
|
+
import type { FileScanResult } from "./scanner.js";
|
|
14
|
+
/**
|
|
15
|
+
* Escapes a value for safe embedding in a CSV cell per RFC 4180.
|
|
16
|
+
*
|
|
17
|
+
* - A value starting with a formula trigger is prefixed with `'`, so Excel, LibreOffice and
|
|
18
|
+
* Google Sheets show it as text instead of running it (CWE-1236). The cells hold file paths
|
|
19
|
+
* and source lines from the scanned commit, which anyone who can open a PR controls.
|
|
20
|
+
* - If the value contains a comma, double-quote, or newline, the entire value
|
|
21
|
+
* is wrapped in double-quotes.
|
|
22
|
+
* - Any internal double-quotes are doubled (`"` → `""`).
|
|
23
|
+
*/
|
|
24
|
+
export declare function escapeCsv(value: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Escapes special HTML characters to prevent XSS when embedding untrusted
|
|
27
|
+
* content (e.g. source code snippets) in generated HTML reports.
|
|
28
|
+
*/
|
|
29
|
+
export declare function escapeHtml(value: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Formats scan results as a CSV string.
|
|
32
|
+
*
|
|
33
|
+
* Columns: `File`, `Line`, `Violation`, `Reason`
|
|
34
|
+
*
|
|
35
|
+
* All cell values are escaped per RFC 4180 so embedded commas, quotes, and
|
|
36
|
+
* newlines do not corrupt the output.
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatCsv(results: FileScanResult[]): string;
|
|
39
|
+
/**
|
|
40
|
+
* Generates a self-contained, styled HTML report of scan results.
|
|
41
|
+
*
|
|
42
|
+
* The document includes inline CSS so it can be opened directly in a browser
|
|
43
|
+
* or printed / saved as PDF without any external assets.
|
|
44
|
+
*
|
|
45
|
+
* All untrusted content (file paths, source snippets, reasons) is
|
|
46
|
+
* HTML-escaped to prevent XSS.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatHtml(results: FileScanResult[]): string;
|
|
49
|
+
//# sourceMappingURL=exporters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exporters.d.ts","sourceRoot":"","sources":["../src/exporters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAcnD;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhD;AAQD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,MAAM,CAY3D;AAMD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,MAAM,CA4G5D"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alternative Export Formatters for SecureFlow CLI (#811)
|
|
3
|
+
*
|
|
4
|
+
* Provides CSV and HTML report generation for stakeholders who do not use
|
|
5
|
+
* SARIF viewers. Both formatters are zero-dependency, using pure string
|
|
6
|
+
* templates to keep the CLI lightweight.
|
|
7
|
+
*
|
|
8
|
+
* PDF generation was intentionally not added because it would introduce a
|
|
9
|
+
* heavyweight PDF-generation dependency (e.g. pdfkit, puppeteer). HTML
|
|
10
|
+
* provides a portable report that users can directly print/save as PDF
|
|
11
|
+
* from any browser.
|
|
12
|
+
*/
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Escape helpers
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* Leading characters that make a spreadsheet evaluate a cell as a formula.
|
|
18
|
+
*
|
|
19
|
+
* Same set as the server-side exporter (`src/lib/utils/csv.ts`). A leading TAB or CR is
|
|
20
|
+
* stripped by some importers, exposing the character after it.
|
|
21
|
+
*/
|
|
22
|
+
const FORMULA_TRIGGERS = ["=", "+", "-", "@", "\t", "\r"];
|
|
23
|
+
/**
|
|
24
|
+
* Escapes a value for safe embedding in a CSV cell per RFC 4180.
|
|
25
|
+
*
|
|
26
|
+
* - A value starting with a formula trigger is prefixed with `'`, so Excel, LibreOffice and
|
|
27
|
+
* Google Sheets show it as text instead of running it (CWE-1236). The cells hold file paths
|
|
28
|
+
* and source lines from the scanned commit, which anyone who can open a PR controls.
|
|
29
|
+
* - If the value contains a comma, double-quote, or newline, the entire value
|
|
30
|
+
* is wrapped in double-quotes.
|
|
31
|
+
* - Any internal double-quotes are doubled (`"` → `""`).
|
|
32
|
+
*/
|
|
33
|
+
export function escapeCsv(value) {
|
|
34
|
+
const text = FORMULA_TRIGGERS.some((trigger) => value.startsWith(trigger)) ? `'${value}` : value;
|
|
35
|
+
if (text.includes('"') || text.includes(",") || text.includes("\n") || text.includes("\r")) {
|
|
36
|
+
return `"${text.replace(/"/g, '""')}"`;
|
|
37
|
+
}
|
|
38
|
+
return text;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Escapes special HTML characters to prevent XSS when embedding untrusted
|
|
42
|
+
* content (e.g. source code snippets) in generated HTML reports.
|
|
43
|
+
*/
|
|
44
|
+
export function escapeHtml(value) {
|
|
45
|
+
return value
|
|
46
|
+
.replace(/&/g, "&")
|
|
47
|
+
.replace(/</g, "<")
|
|
48
|
+
.replace(/>/g, ">")
|
|
49
|
+
.replace(/"/g, """)
|
|
50
|
+
.replace(/'/g, "'");
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// CSV formatter
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
const CSV_HEADERS = "File,Line,Violation,Reason";
|
|
56
|
+
/**
|
|
57
|
+
* Formats scan results as a CSV string.
|
|
58
|
+
*
|
|
59
|
+
* Columns: `File`, `Line`, `Violation`, `Reason`
|
|
60
|
+
*
|
|
61
|
+
* All cell values are escaped per RFC 4180 so embedded commas, quotes, and
|
|
62
|
+
* newlines do not corrupt the output.
|
|
63
|
+
*/
|
|
64
|
+
export function formatCsv(results) {
|
|
65
|
+
const rows = [CSV_HEADERS];
|
|
66
|
+
for (const file of results) {
|
|
67
|
+
for (const v of file.violations) {
|
|
68
|
+
rows.push([escapeCsv(file.path), String(v.line), escapeCsv(v.text), escapeCsv(v.reason)].join(","));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return rows.join("\n") + "\n";
|
|
72
|
+
}
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// HTML formatter
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
/**
|
|
77
|
+
* Generates a self-contained, styled HTML report of scan results.
|
|
78
|
+
*
|
|
79
|
+
* The document includes inline CSS so it can be opened directly in a browser
|
|
80
|
+
* or printed / saved as PDF without any external assets.
|
|
81
|
+
*
|
|
82
|
+
* All untrusted content (file paths, source snippets, reasons) is
|
|
83
|
+
* HTML-escaped to prevent XSS.
|
|
84
|
+
*/
|
|
85
|
+
export function formatHtml(results) {
|
|
86
|
+
const violations = [];
|
|
87
|
+
for (const file of results) {
|
|
88
|
+
for (const v of file.violations) {
|
|
89
|
+
violations.push({ path: file.path, line: v.line, text: v.text, reason: v.reason });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const tableRows = violations.length > 0
|
|
93
|
+
? violations
|
|
94
|
+
.map((v) => ` <tr>
|
|
95
|
+
<td>${escapeHtml(v.path)}</td>
|
|
96
|
+
<td>${v.line}</td>
|
|
97
|
+
<td><code>${escapeHtml(v.text)}</code></td>
|
|
98
|
+
<td>${escapeHtml(v.reason)}</td>
|
|
99
|
+
</tr>`)
|
|
100
|
+
.join("\n")
|
|
101
|
+
: ` <tr><td colspan="4" class="empty">No violations detected.</td></tr>`;
|
|
102
|
+
return `<!DOCTYPE html>
|
|
103
|
+
<html lang="en">
|
|
104
|
+
<head>
|
|
105
|
+
<meta charset="UTF-8">
|
|
106
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
107
|
+
<title>SecureFlow Scan Report</title>
|
|
108
|
+
<style>
|
|
109
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
110
|
+
body {
|
|
111
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
112
|
+
background: #f6f8fa;
|
|
113
|
+
color: #24292f;
|
|
114
|
+
padding: 2rem;
|
|
115
|
+
line-height: 1.5;
|
|
116
|
+
}
|
|
117
|
+
h1 {
|
|
118
|
+
font-size: 1.5rem;
|
|
119
|
+
margin-bottom: 0.25rem;
|
|
120
|
+
}
|
|
121
|
+
.meta {
|
|
122
|
+
color: #57606a;
|
|
123
|
+
font-size: 0.875rem;
|
|
124
|
+
margin-bottom: 1.5rem;
|
|
125
|
+
}
|
|
126
|
+
table {
|
|
127
|
+
width: 100%;
|
|
128
|
+
border-collapse: collapse;
|
|
129
|
+
background: #ffffff;
|
|
130
|
+
border: 1px solid #d0d7de;
|
|
131
|
+
border-radius: 6px;
|
|
132
|
+
overflow: hidden;
|
|
133
|
+
}
|
|
134
|
+
th, td {
|
|
135
|
+
text-align: left;
|
|
136
|
+
padding: 0.5rem 0.75rem;
|
|
137
|
+
border-bottom: 1px solid #d0d7de;
|
|
138
|
+
}
|
|
139
|
+
th {
|
|
140
|
+
background: #f6f8fa;
|
|
141
|
+
font-weight: 600;
|
|
142
|
+
font-size: 0.875rem;
|
|
143
|
+
}
|
|
144
|
+
td code {
|
|
145
|
+
background: #f0f3f6;
|
|
146
|
+
padding: 0.125rem 0.375rem;
|
|
147
|
+
border-radius: 3px;
|
|
148
|
+
font-size: 0.8125rem;
|
|
149
|
+
}
|
|
150
|
+
tr:last-child td { border-bottom: none; }
|
|
151
|
+
.empty {
|
|
152
|
+
text-align: center;
|
|
153
|
+
color: #57606a;
|
|
154
|
+
padding: 1.5rem;
|
|
155
|
+
}
|
|
156
|
+
.summary {
|
|
157
|
+
margin-top: 1rem;
|
|
158
|
+
font-size: 0.875rem;
|
|
159
|
+
color: #57606a;
|
|
160
|
+
}
|
|
161
|
+
@media print {
|
|
162
|
+
body { background: #fff; padding: 0; }
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
165
|
+
</head>
|
|
166
|
+
<body>
|
|
167
|
+
<h1>SecureFlow Scan Report</h1>
|
|
168
|
+
<p class="meta">Generated by SecureFlow CLI</p>
|
|
169
|
+
<table>
|
|
170
|
+
<thead>
|
|
171
|
+
<tr>
|
|
172
|
+
<th>File</th>
|
|
173
|
+
<th>Line</th>
|
|
174
|
+
<th>Violation</th>
|
|
175
|
+
<th>Reason</th>
|
|
176
|
+
</tr>
|
|
177
|
+
</thead>
|
|
178
|
+
<tbody>
|
|
179
|
+
${tableRows}
|
|
180
|
+
</tbody>
|
|
181
|
+
</table>
|
|
182
|
+
<p class="summary">${violations.length} violation${violations.length === 1 ? "" : "s"} found.</p>
|
|
183
|
+
</body>
|
|
184
|
+
</html>
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=exporters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exporters.js","sourceRoot":"","sources":["../src/exporters.ts"],"names":[],"mappings":"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"}
|
package/dist/git.d.ts
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
*/
|
|
22
22
|
/** A problem with the repository or the git invocation, not with the content. */
|
|
23
23
|
export declare class GitError extends Error {
|
|
24
|
-
readonly cause?: unknown;
|
|
25
|
-
constructor(message: string, cause?: unknown);
|
|
24
|
+
readonly cause?: unknown | undefined;
|
|
25
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
26
26
|
}
|
|
27
27
|
/** Whether the current directory is inside a work tree. */
|
|
28
28
|
export declare function isGitRepository(): boolean;
|
package/dist/git.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAaH,iFAAiF;AACjF,qBAAa,QAAS,SAAQ,KAAK;
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAaH,iFAAiF;AACjF,qBAAa,QAAS,SAAQ,KAAK;IAG/B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;gBADxB,OAAO,EAAE,MAAM,EACN,KAAK,CAAC,EAAE,OAAO,YAAA;CAK3B;AASD,2DAA2D;AAC3D,wBAAgB,eAAe,IAAI,OAAO,CAMzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAgBzC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM7D"}
|
package/dist/git.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* The inverse is just as bad: unstaged debug code blocks a commit that does not
|
|
20
20
|
* contain it, which teaches people to reach for `--no-verify`.
|
|
21
21
|
*/
|
|
22
|
-
import { execFileSync } from
|
|
22
|
+
import { execFileSync } from "child_process";
|
|
23
23
|
/**
|
|
24
24
|
* Buffer ceiling for git output.
|
|
25
25
|
*
|
|
@@ -34,19 +34,19 @@ export class GitError extends Error {
|
|
|
34
34
|
constructor(message, cause) {
|
|
35
35
|
super(message);
|
|
36
36
|
this.cause = cause;
|
|
37
|
-
this.name =
|
|
37
|
+
this.name = "GitError";
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
function git(args) {
|
|
41
|
-
return execFileSync(
|
|
42
|
-
encoding:
|
|
41
|
+
return execFileSync("git", args, {
|
|
42
|
+
encoding: "utf-8",
|
|
43
43
|
maxBuffer: MAX_GIT_BUFFER,
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
/** Whether the current directory is inside a work tree. */
|
|
47
47
|
export function isGitRepository() {
|
|
48
48
|
try {
|
|
49
|
-
return git([
|
|
49
|
+
return git(["rev-parse", "--is-inside-work-tree"]).trim() === "true";
|
|
50
50
|
}
|
|
51
51
|
catch {
|
|
52
52
|
return false;
|
|
@@ -68,15 +68,15 @@ export function isGitRepository() {
|
|
|
68
68
|
export function getStagedFiles() {
|
|
69
69
|
let output;
|
|
70
70
|
try {
|
|
71
|
-
output = git([
|
|
71
|
+
output = git(["diff", "--cached", "--name-only", "-z", "--diff-filter=ACMR"]);
|
|
72
72
|
}
|
|
73
73
|
catch (error) {
|
|
74
74
|
throw new GitError(isGitRepository()
|
|
75
|
-
?
|
|
76
|
-
:
|
|
75
|
+
? "Could not list staged files. `git diff --cached` failed."
|
|
76
|
+
: "Not a git repository (or git is not on PATH).", error);
|
|
77
77
|
}
|
|
78
78
|
// NUL-separated, with a trailing separator on a non-empty list.
|
|
79
|
-
return output.split(
|
|
79
|
+
return output.split("\u0000").filter((entry) => entry.length > 0);
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
82
|
* The staged content of `path` — the bytes that will actually be committed.
|
|
@@ -87,7 +87,7 @@ export function getStagedFiles() {
|
|
|
87
87
|
*/
|
|
88
88
|
export function readStagedContent(path) {
|
|
89
89
|
try {
|
|
90
|
-
return git([
|
|
90
|
+
return git(["show", `:${path}`]);
|
|
91
91
|
}
|
|
92
92
|
catch {
|
|
93
93
|
return null;
|
package/dist/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAExC,iFAAiF;AACjF,MAAM,OAAO,QAAS,SAAQ,KAAK;
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAExC,iFAAiF;AACjF,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGtB;IAFX,YACE,OAAe,EACN,KAAe;QAExB,KAAK,CAAC,OAAO,CAAC,CAAC;QAFN,UAAK,GAAL,KAAK,CAAU;QAGxB,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAED,SAAS,GAAG,CAAC,IAAc;IACzB,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;QAC/B,QAAQ,EAAE,OAAO;QACjB,SAAS,EAAE,cAAc;KAC1B,CAAC,CAAC;AACL,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,MAAc,CAAC;IAEnB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,IAAI,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAChF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,QAAQ,CAChB,eAAe,EAAE;YACf,CAAC,CAAC,0DAA0D;YAC5D,CAAC,CAAC,+CAA+C,EACnD,KAAK,CACN,CAAC;IACJ,CAAC;IAED,gEAAgE;IAChE,OAAO,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import fs from
|
|
3
|
-
import { GitError, getStagedFiles, readStagedContent } from
|
|
4
|
-
import { scanFile, formatScanResults } from
|
|
5
|
-
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { GitError, getStagedFiles, readStagedContent } from "./git.js";
|
|
4
|
+
import { scanFile, formatScanResults } from "./scanner.js";
|
|
5
|
+
import { NetworkUnavailableError, requestAiFileScan, } from "./lib/api-client.js";
|
|
6
|
+
const VERBOSE = process.argv.includes("--verbose");
|
|
7
|
+
const NO_AI = process.argv.includes("--no-ai");
|
|
8
|
+
/**
|
|
9
|
+
* --local flag: route AI calls to a local Ollama instance instead of Groq.
|
|
10
|
+
*
|
|
11
|
+
* Sets LOCAL_AI_URL before any AI module is imported so the env var is
|
|
12
|
+
* visible to getAiInstance() / resolveLocalModelConfig() in genkit.ts.
|
|
13
|
+
* The model can be overridden with --local-model <tag> (default: llama3).
|
|
14
|
+
*/
|
|
15
|
+
const LOCAL_FLAG = process.argv.includes("--local");
|
|
16
|
+
if (LOCAL_FLAG) {
|
|
17
|
+
const localFlagIndex = process.argv.findIndex((a) => a === "--local");
|
|
18
|
+
const nextArg = process.argv[localFlagIndex + 1];
|
|
19
|
+
// Check if the argument after --local is a URL (starts with http:// or https://)
|
|
20
|
+
const customUrl = nextArg && (nextArg.startsWith("http://") || nextArg.startsWith("https://")) ? nextArg : undefined;
|
|
21
|
+
const modelIdx = process.argv.findIndex((a) => a === "--local-model");
|
|
22
|
+
const localModel = modelIdx !== -1 ? process.argv[modelIdx + 1] : undefined;
|
|
23
|
+
process.env.LOCAL_AI_URL = customUrl || process.env.LOCAL_AI_URL || "http://localhost:11434/v1";
|
|
24
|
+
if (localModel)
|
|
25
|
+
process.env.LOCAL_AI_MODEL = localModel;
|
|
26
|
+
}
|
|
6
27
|
function parseFormatArg() {
|
|
7
|
-
const formatIndex = process.argv.findIndex((arg) => arg ===
|
|
28
|
+
const formatIndex = process.argv.findIndex((arg) => arg === "--format");
|
|
8
29
|
if (formatIndex !== -1) {
|
|
9
30
|
const valStr = process.argv[formatIndex + 1];
|
|
10
31
|
if (valStr) {
|
|
11
32
|
const val = valStr.toLowerCase();
|
|
12
|
-
if (val ===
|
|
33
|
+
if (val === "sarif" || val === "json" || val === "text" || val === "csv" || val === "html") {
|
|
13
34
|
return val;
|
|
14
35
|
}
|
|
15
36
|
}
|
|
16
37
|
}
|
|
17
|
-
return
|
|
38
|
+
return "text";
|
|
18
39
|
}
|
|
19
40
|
function parseOutputArg() {
|
|
20
|
-
const outIndex = process.argv.findIndex((arg) => arg ===
|
|
41
|
+
const outIndex = process.argv.findIndex((arg) => arg === "-o" || arg === "--output");
|
|
21
42
|
if (outIndex !== -1) {
|
|
22
43
|
const valStr = process.argv[outIndex + 1];
|
|
23
44
|
if (valStr) {
|
|
@@ -38,7 +59,34 @@ function reportViolations(result) {
|
|
|
38
59
|
console.error(` why: ${violation.reason} passed to a console call`);
|
|
39
60
|
}
|
|
40
61
|
}
|
|
41
|
-
function
|
|
62
|
+
function reportAiFinding(finding) {
|
|
63
|
+
console.error(`🤖 [SecureFlow AI] ${finding.severity} ${finding.type} in ${finding.fileLocation}${finding.lineStart ? `:${finding.lineStart}` : ""}`);
|
|
64
|
+
console.error(` -> ${finding.description}`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Best-effort AI-powered scan on top of the always-on local scan above.
|
|
68
|
+
* Never throws and never delays the commit beyond its own short internal
|
|
69
|
+
* timeout -- if the network is down, this is a silent (or
|
|
70
|
+
* --verbose-logged) no-op and the local scan result stands on its own,
|
|
71
|
+
* unchanged.
|
|
72
|
+
*/
|
|
73
|
+
async function runAiScanIfAvailable(stagedForAi) {
|
|
74
|
+
if (NO_AI || stagedForAi.length === 0)
|
|
75
|
+
return [];
|
|
76
|
+
try {
|
|
77
|
+
return await requestAiFileScan(stagedForAi);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof NetworkUnavailableError) {
|
|
81
|
+
if (VERBOSE) {
|
|
82
|
+
console.warn("⚠️ [SecureFlow] AI scan unreachable -- continuing with local scan only.");
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function main() {
|
|
42
90
|
const format = parseFormatArg();
|
|
43
91
|
const outputPath = parseOutputArg();
|
|
44
92
|
let staged;
|
|
@@ -51,6 +99,7 @@ function main() {
|
|
|
51
99
|
}
|
|
52
100
|
const fileResults = [];
|
|
53
101
|
const unreadable = [];
|
|
102
|
+
const stagedForAi = [];
|
|
54
103
|
let violationCount = 0;
|
|
55
104
|
if (staged.length > 0) {
|
|
56
105
|
for (const path of staged) {
|
|
@@ -59,19 +108,31 @@ function main() {
|
|
|
59
108
|
unreadable.push(path);
|
|
60
109
|
continue;
|
|
61
110
|
}
|
|
111
|
+
stagedForAi.push({ path, content });
|
|
62
112
|
const result = scanFile(path, content);
|
|
63
113
|
fileResults.push(result);
|
|
64
|
-
if (format ===
|
|
114
|
+
if (format === "text") {
|
|
65
115
|
reportSkipped(result);
|
|
66
116
|
reportViolations(result);
|
|
67
117
|
}
|
|
68
118
|
violationCount += result.violations.length;
|
|
69
119
|
}
|
|
70
120
|
}
|
|
71
|
-
|
|
121
|
+
// AI-powered pass, additive on top of the local scan above. Only
|
|
122
|
+
// affects the text output/exit code today -- JSON/SARIF export stays
|
|
123
|
+
// local-scan-only for now so existing automated consumers of those
|
|
124
|
+
// formats aren't changed by this PR.
|
|
125
|
+
const aiFindings = await runAiScanIfAvailable(stagedForAi);
|
|
126
|
+
if (format === "text") {
|
|
127
|
+
for (const finding of aiFindings) {
|
|
128
|
+
reportAiFinding(finding);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const aiViolationCount = aiFindings.filter((f) => f.severity === "HIGH" || f.severity === "CRITICAL").length;
|
|
132
|
+
if (format === "sarif" || format === "json" || format === "csv" || format === "html") {
|
|
72
133
|
const outputString = formatScanResults(fileResults, format);
|
|
73
134
|
if (outputPath) {
|
|
74
|
-
fs.writeFileSync(outputPath, outputString,
|
|
135
|
+
fs.writeFileSync(outputPath, outputString, "utf-8");
|
|
75
136
|
console.log(`📄 [SecureFlow] Scan report exported in ${format.toUpperCase()} format to ${outputPath}`);
|
|
76
137
|
}
|
|
77
138
|
else {
|
|
@@ -79,23 +140,25 @@ function main() {
|
|
|
79
140
|
}
|
|
80
141
|
}
|
|
81
142
|
else if (outputPath) {
|
|
82
|
-
const textOutput = formatScanResults(fileResults,
|
|
83
|
-
fs.writeFileSync(outputPath, textOutput,
|
|
143
|
+
const textOutput = formatScanResults(fileResults, "text");
|
|
144
|
+
fs.writeFileSync(outputPath, textOutput, "utf-8");
|
|
84
145
|
console.log(`📄 [SecureFlow] Scan report written to ${outputPath}`);
|
|
85
146
|
}
|
|
86
|
-
if (unreadable.length > 0 && format ===
|
|
87
|
-
console.warn(`⚠️ [SecureFlow] Could not read ${unreadable.length} staged entr${unreadable.length === 1 ?
|
|
147
|
+
if (unreadable.length > 0 && format === "text") {
|
|
148
|
+
console.warn(`⚠️ [SecureFlow] Could not read ${unreadable.length} staged entr${unreadable.length === 1 ? "y" : "ies"} (submodule, symlink or conflicted): ${unreadable.join(", ")}`);
|
|
88
149
|
}
|
|
89
|
-
if (violationCount > 0) {
|
|
90
|
-
if (format ===
|
|
91
|
-
console.error(`\n❌ SecureFlow blocked this commit: ${violationCount} secret-logging violation${violationCount === 1 ?
|
|
150
|
+
if (violationCount > 0 || aiViolationCount > 0) {
|
|
151
|
+
if (format === "text") {
|
|
152
|
+
console.error(`\n❌ SecureFlow blocked this commit: ${violationCount} secret-logging violation${violationCount === 1 ? "" : "s"}${aiViolationCount > 0
|
|
153
|
+
? ` and ${aiViolationCount} AI-detected HIGH/CRITICAL finding${aiViolationCount === 1 ? "" : "s"}`
|
|
154
|
+
: ""}. Remove the exposed secrets/env variables, then re-stage.`);
|
|
92
155
|
}
|
|
93
156
|
return 1;
|
|
94
157
|
}
|
|
95
|
-
if (format ===
|
|
158
|
+
if (format === "text") {
|
|
96
159
|
console.log(`✅ SecureFlow scan passed (${staged.length} staged file(s)).`);
|
|
97
160
|
}
|
|
98
161
|
return 0;
|
|
99
162
|
}
|
|
100
|
-
process.exit(
|
|
163
|
+
main().then((code) => process.exit(code));
|
|
101
164
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAA0C,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin client for the AI-powered scanning endpoint the SecureFlow GitHub
|
|
3
|
+
* App uses (src/lib/armor/scanner.ts, exposed at POST /api/cli/scan).
|
|
4
|
+
* Gives the CLI parity with the App's AI review, additive on top of the
|
|
5
|
+
* always-available local scan in `scanner.ts`.
|
|
6
|
+
*/
|
|
7
|
+
export interface StagedFileForAiScan {
|
|
8
|
+
path: string;
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
11
|
+
/** Mirrors `ScanFinding` from src/lib/armor/scanner.ts. */
|
|
12
|
+
export interface AiFinding {
|
|
13
|
+
type: string;
|
|
14
|
+
severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
15
|
+
description: string;
|
|
16
|
+
fileLocation: string;
|
|
17
|
+
codeSnippet: string;
|
|
18
|
+
lineStart?: number;
|
|
19
|
+
lineEnd?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare class NetworkUnavailableError extends Error {
|
|
22
|
+
constructor(cause?: unknown);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Whether a staged file may be uploaded to the hosted AI scanner.
|
|
26
|
+
*
|
|
27
|
+
* The same test the local scan applies before it reads a file: lockfiles,
|
|
28
|
+
* binary extensions, NUL-containing content and blobs over
|
|
29
|
+
* `MAX_SCANNED_BYTES` are skipped locally, and there is no reason to send
|
|
30
|
+
* them off the machine either. Without this every readable staged path was
|
|
31
|
+
* uploaded in full, whatever its type or size.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isEligibleForAiScan(file: StagedFileForAiScan): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Sends staged file contents to the hosted AI scanner. Fails fast (short
|
|
36
|
+
* timeout) and always throws `NetworkUnavailableError` on any failure so
|
|
37
|
+
* callers can swallow it and fall back to the local-only scan -- never
|
|
38
|
+
* block a commit on connectivity.
|
|
39
|
+
*/
|
|
40
|
+
export declare function requestAiFileScan(files: StagedFileForAiScan[], baseUrl?: string): Promise<AiFinding[]>;
|
|
41
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,KAAK,CAAC,EAAE,OAAO;CAK5B;AAKD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAKtE;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,mBAAmB,EAAE,EAC5B,OAAO,GAAE,MAAyB,GACjC,OAAO,CAAC,SAAS,EAAE,CAAC,CA0BtB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin client for the AI-powered scanning endpoint the SecureFlow GitHub
|
|
3
|
+
* App uses (src/lib/armor/scanner.ts, exposed at POST /api/cli/scan).
|
|
4
|
+
* Gives the CLI parity with the App's AI review, additive on top of the
|
|
5
|
+
* always-available local scan in `scanner.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import { looksBinary, shouldScanFile } from "../scanner.js";
|
|
8
|
+
export class NetworkUnavailableError extends Error {
|
|
9
|
+
constructor(cause) {
|
|
10
|
+
super("SecureFlow API unreachable");
|
|
11
|
+
this.name = "NetworkUnavailableError";
|
|
12
|
+
this.cause = cause;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
16
|
+
const DEFAULT_BASE_URL = "https://secure-flow-six.vercel.app";
|
|
17
|
+
/**
|
|
18
|
+
* Whether a staged file may be uploaded to the hosted AI scanner.
|
|
19
|
+
*
|
|
20
|
+
* The same test the local scan applies before it reads a file: lockfiles,
|
|
21
|
+
* binary extensions, NUL-containing content and blobs over
|
|
22
|
+
* `MAX_SCANNED_BYTES` are skipped locally, and there is no reason to send
|
|
23
|
+
* them off the machine either. Without this every readable staged path was
|
|
24
|
+
* uploaded in full, whatever its type or size.
|
|
25
|
+
*/
|
|
26
|
+
export function isEligibleForAiScan(file) {
|
|
27
|
+
return (shouldScanFile(file.path, Buffer.byteLength(file.content, "utf-8")) &&
|
|
28
|
+
!looksBinary(file.content));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Sends staged file contents to the hosted AI scanner. Fails fast (short
|
|
32
|
+
* timeout) and always throws `NetworkUnavailableError` on any failure so
|
|
33
|
+
* callers can swallow it and fall back to the local-only scan -- never
|
|
34
|
+
* block a commit on connectivity.
|
|
35
|
+
*/
|
|
36
|
+
export async function requestAiFileScan(files, baseUrl = DEFAULT_BASE_URL) {
|
|
37
|
+
const eligible = files.filter(isEligibleForAiScan);
|
|
38
|
+
if (eligible.length === 0)
|
|
39
|
+
return [];
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch(`${baseUrl}/api/cli/scan`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { "Content-Type": "application/json" },
|
|
46
|
+
body: JSON.stringify({ files: eligible }),
|
|
47
|
+
signal: controller.signal,
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
throw new Error(`SecureFlow API responded with ${res.status}`);
|
|
51
|
+
}
|
|
52
|
+
const data = (await res.json());
|
|
53
|
+
return data.findings ?? [];
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
throw new NetworkUnavailableError(err);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
clearTimeout(timeout);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAkB5D,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChD,YAAY,KAAe;QACzB,KAAK,CAAC,4BAA4B,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACjC,MAAM,gBAAgB,GAAG,oCAAoC,CAAC;AAE9D;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAyB;IAC3D,OAAO,CACL,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACnE,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAC3B,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAA4B,EAC5B,UAAkB,gBAAgB;IAElC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACnD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAErC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,kBAAkB,CAAC,CAAC;IAEzE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,eAAe,EAAE;YACjD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;YACzC,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACjE,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA8B,CAAC;QAC7D,OAAO,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,uBAAuB,CAAC,GAAG,CAAC,CAAC;IACzC,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;AACH,CAAC"}
|