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.
@@ -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, "&lt;")
48
+ .replace(/>/g, "&gt;")
49
+ .replace(/"/g, "&quot;")
50
+ .replace(/'/g, "&#39;");
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;IACJ,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;IAArD,YAAY,OAAO,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,OAAO,EAGpD;CACF;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"}
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 'child_process';
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 = 'GitError';
37
+ this.name = "GitError";
38
38
  }
39
39
  }
40
40
  function git(args) {
41
- return execFileSync('git', args, {
42
- encoding: 'utf-8',
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(['rev-parse', '--is-inside-work-tree']).trim() === 'true';
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(['diff', '--cached', '--name-only', '-z', '--diff-filter=ACMR']);
71
+ output = git(["diff", "--cached", "--name-only", "-z", "--diff-filter=ACMR"]);
72
72
  }
73
73
  catch (error) {
74
74
  throw new GitError(isGitRepository()
75
- ? 'Could not list staged files. `git diff --cached` failed.'
76
- : 'Not a git repository (or git is not on PATH).', error);
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('\u0000').filter((entry) => entry.length > 0);
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(['show', `:${path}`]);
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;IACK,KAAK;IAA3C,YAAY,OAAe,EAAW,KAAe;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;qBADqB,KAAK;QAEzC,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"}
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 'fs';
3
- import { GitError, getStagedFiles, readStagedContent } from './git.js';
4
- import { scanFile, formatScanResults } from './scanner.js';
5
- const VERBOSE = process.argv.includes('--verbose');
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 === '--format');
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 === 'sarif' || val === 'json' || val === 'text') {
33
+ if (val === "sarif" || val === "json" || val === "text" || val === "csv" || val === "html") {
13
34
  return val;
14
35
  }
15
36
  }
16
37
  }
17
- return 'text';
38
+ return "text";
18
39
  }
19
40
  function parseOutputArg() {
20
- const outIndex = process.argv.findIndex((arg) => arg === '-o' || arg === '--output');
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 main() {
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 === 'text') {
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
- if (format === 'sarif' || format === 'json') {
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, 'utf-8');
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, 'text');
83
- fs.writeFileSync(outputPath, textOutput, 'utf-8');
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 === 'text') {
87
- console.warn(`⚠️ [SecureFlow] Could not read ${unreadable.length} staged entr${unreadable.length === 1 ? 'y' : 'ies'} (submodule, symlink or conflicted): ${unreadable.join(', ')}`);
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 === 'text') {
91
- console.error(`\n❌ SecureFlow blocked this commit: ${violationCount} secret-logging violation${violationCount === 1 ? '' : 's'}. Remove the exposed secrets/env variables, then re-stage.`);
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 === 'text') {
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(main());
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":"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"}
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"}