vibe-checking 1.0.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.
Files changed (41) hide show
  1. package/README.md +53 -0
  2. package/dist/claude/correlator.d.ts +2 -0
  3. package/dist/claude/correlator.js +179 -0
  4. package/dist/claude/correlator.js.map +1 -0
  5. package/dist/claude/reader.d.ts +5 -0
  6. package/dist/claude/reader.js +191 -0
  7. package/dist/claude/reader.js.map +1 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +102 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/repl/display.d.ts +16 -0
  12. package/dist/repl/display.js +153 -0
  13. package/dist/repl/display.js.map +1 -0
  14. package/dist/repl/repl.d.ts +9 -0
  15. package/dist/repl/repl.js +110 -0
  16. package/dist/repl/repl.js.map +1 -0
  17. package/dist/report/html.d.ts +9 -0
  18. package/dist/report/html.js +174 -0
  19. package/dist/report/html.js.map +1 -0
  20. package/dist/scanners/aggregator.d.ts +12 -0
  21. package/dist/scanners/aggregator.js +126 -0
  22. package/dist/scanners/aggregator.js.map +1 -0
  23. package/dist/scanners/deps.d.ts +6 -0
  24. package/dist/scanners/deps.js +73 -0
  25. package/dist/scanners/deps.js.map +1 -0
  26. package/dist/scanners/gitleaks.d.ts +7 -0
  27. package/dist/scanners/gitleaks.js +103 -0
  28. package/dist/scanners/gitleaks.js.map +1 -0
  29. package/dist/scanners/installer.d.ts +3 -0
  30. package/dist/scanners/installer.js +121 -0
  31. package/dist/scanners/installer.js.map +1 -0
  32. package/dist/scanners/rls.d.ts +6 -0
  33. package/dist/scanners/rls.js +177 -0
  34. package/dist/scanners/rls.js.map +1 -0
  35. package/dist/scanners/semgrep.d.ts +7 -0
  36. package/dist/scanners/semgrep.js +121 -0
  37. package/dist/scanners/semgrep.js.map +1 -0
  38. package/dist/types.d.ts +45 -0
  39. package/dist/types.js +2 -0
  40. package/dist/types.js.map +1 -0
  41. package/package.json +29 -0
@@ -0,0 +1,73 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ const execFileAsync = promisify(execFile);
6
+ export async function scanDeps(repoPath) {
7
+ const packageJson = join(repoPath, "package.json");
8
+ if (!existsSync(packageJson)) {
9
+ return {
10
+ findings: [],
11
+ available: false,
12
+ error: "no package.json found — skipping dependency audit",
13
+ };
14
+ }
15
+ try {
16
+ const { stdout } = await execFileAsync("npm", ["audit", "--json", "--omit=dev"], { cwd: repoPath, maxBuffer: 20 * 1024 * 1024, timeout: 60_000 });
17
+ return { findings: parseAudit(stdout), available: true };
18
+ }
19
+ catch (err) {
20
+ const e = err;
21
+ // npm audit exits non-zero when vulnerabilities are found
22
+ if (e.stdout) {
23
+ try {
24
+ return { findings: parseAudit(e.stdout), available: true };
25
+ }
26
+ catch {
27
+ /* fall through */
28
+ }
29
+ }
30
+ return {
31
+ findings: [],
32
+ available: true,
33
+ error: `npm audit error: ${e.stderr?.slice(0, 200) || String(err)}`,
34
+ };
35
+ }
36
+ }
37
+ function parseAudit(json) {
38
+ let result;
39
+ try {
40
+ result = JSON.parse(json);
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ if (!result.vulnerabilities)
46
+ return [];
47
+ const findings = [];
48
+ for (const [, vuln] of Object.entries(result.vulnerabilities)) {
49
+ const sev = vuln.severity?.toLowerCase();
50
+ if (sev === "low" || sev === "info")
51
+ continue;
52
+ let title = `Known vulnerability in ${vuln.name}`;
53
+ for (const v of vuln.via) {
54
+ if (typeof v === "object" && v.title) {
55
+ title = v.title;
56
+ break;
57
+ }
58
+ }
59
+ findings.push({
60
+ id: 0,
61
+ severity: sev === "critical" || sev === "high" ? "critical" : "medium",
62
+ path: `package.json · ${vuln.name}`,
63
+ title: title.length > 120 ? title.slice(0, 117) + "…" : title,
64
+ meta: `npm audit · ${sev} severity${vuln.fixAvailable ? " · fix available" : ""}`,
65
+ source: "deps",
66
+ trace: null,
67
+ fix: null,
68
+ manual: "Not a generation issue — a vulnerable dependency. Update or replace the package. No prompt rewrite applies.",
69
+ });
70
+ }
71
+ return findings;
72
+ }
73
+ //# sourceMappingURL=deps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/scanners/deps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAuB1C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAK7C,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IACnD,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,OAAO;YACL,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,KAAK;YAChB,KAAK,EAAE,mDAAmD;SAC3D,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CACpC,KAAK,EACL,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC,EACjC,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAChE,CAAC;QAEF,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC3D,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,GAA2C,CAAC;QACtD,0DAA0D;QAC1D,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;YACb,IAAI,CAAC;gBACH,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;YAC7D,CAAC;YAAC,MAAM,CAAC;gBACP,kBAAkB;YACpB,CAAC;QACH,CAAC;QACD,OAAO;YACL,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,oBAAoB,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE;SACpE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,MAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,eAAe;QAAE,OAAO,EAAE,CAAC;IAEvC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;QACzC,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM;YAAE,SAAS;QAE9C,IAAI,KAAK,GAAG,0BAA0B,IAAI,CAAC,IAAI,EAAE,CAAC;QAClD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACzB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;gBACrC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;gBAChB,MAAM;YACR,CAAC;QACH,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC;YACZ,EAAE,EAAE,CAAC;YACL,QAAQ,EAAE,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ;YACtE,IAAI,EAAE,kBAAkB,IAAI,CAAC,IAAI,EAAE;YACnC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK;YAC7D,IAAI,EAAE,eAAe,GAAG,YAAY,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,EAAE;YACjF,MAAM,EAAE,MAAM;YACd,KAAK,EAAE,IAAI;YACX,GAAG,EAAE,IAAI;YACT,MAAM,EACJ,6GAA6G;SAChH,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Finding } from "../types.js";
2
+ import { type OnProgress } from "./installer.js";
3
+ export declare function scanSecrets(repoPath: string, onProgress?: OnProgress): Promise<{
4
+ findings: Finding[];
5
+ available: boolean;
6
+ error?: string;
7
+ }>;
@@ -0,0 +1,103 @@
1
+ import { execFile, exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { autoInstallGitleaks } from "./installer.js";
6
+ const execFileAsync = promisify(execFile);
7
+ const execAsync = promisify(exec);
8
+ async function findGitleaks() {
9
+ try {
10
+ const { stdout } = await execFileAsync("which", ["gitleaks"]);
11
+ return stdout.trim();
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ export async function scanSecrets(repoPath, onProgress) {
18
+ let bin = await findGitleaks();
19
+ if (!bin && onProgress) {
20
+ bin = await autoInstallGitleaks(onProgress);
21
+ }
22
+ if (!bin) {
23
+ return {
24
+ findings: [],
25
+ available: false,
26
+ error: "gitleaks not found — install it (brew install gitleaks) to scan for secrets in git history",
27
+ };
28
+ }
29
+ const isGitRepo = existsSync(join(repoPath, ".git"));
30
+ if (!isGitRepo) {
31
+ return {
32
+ findings: [],
33
+ available: true,
34
+ error: "not a git repository — skipping git history secret scan",
35
+ };
36
+ }
37
+ try {
38
+ let stdout;
39
+ const gitleaksArgs = ["detect", "--source", repoPath, "--report-format", "json", "--no-banner"];
40
+ if (bin === "npx-gitleaks") {
41
+ const result = await execAsync(`npx --yes @gitleaks/gitleaks ${gitleaksArgs.join(" ")}`, { maxBuffer: 50 * 1024 * 1024, timeout: 120_000 });
42
+ stdout = result.stdout;
43
+ }
44
+ else {
45
+ const result = await execFileAsync(bin, gitleaksArgs, {
46
+ maxBuffer: 50 * 1024 * 1024,
47
+ timeout: 120_000,
48
+ });
49
+ stdout = result.stdout;
50
+ }
51
+ const matches = JSON.parse(stdout || "[]");
52
+ return { findings: matchesToFindings(matches), available: true };
53
+ }
54
+ catch (err) {
55
+ const e = err;
56
+ // gitleaks exits 1 when findings are present
57
+ if (e.code === 1 && e.stdout) {
58
+ try {
59
+ const matches = JSON.parse(e.stdout);
60
+ return { findings: matchesToFindings(matches), available: true };
61
+ }
62
+ catch {
63
+ /* fall through */
64
+ }
65
+ }
66
+ // Exit code 0 means no findings
67
+ if (e.code === 0) {
68
+ return { findings: [], available: true };
69
+ }
70
+ return {
71
+ findings: [],
72
+ available: true,
73
+ error: `gitleaks error: ${e.stderr || String(err)}`,
74
+ };
75
+ }
76
+ }
77
+ function matchesToFindings(matches) {
78
+ const seen = new Set();
79
+ const findings = [];
80
+ for (const m of matches) {
81
+ const key = `${m.Rule}:${m.File}:${m.Commit}`;
82
+ if (seen.has(key))
83
+ continue;
84
+ seen.add(key);
85
+ const isServiceRole = m.Description?.toLowerCase().includes("service_role") ||
86
+ m.Rule?.toLowerCase().includes("supabase") ||
87
+ m.Match?.includes("service_role");
88
+ const shortCommit = m.Commit?.slice(0, 7) || "unknown";
89
+ findings.push({
90
+ id: 0,
91
+ severity: isServiceRole ? "critical" : "critical",
92
+ path: `git history · commit ${shortCommit}`,
93
+ title: `${m.Description || m.Rule} committed${m.File ? ` in ${m.File}` : ""} — still live in history`,
94
+ meta: `gitleaks · ${isServiceRole ? "key bypasses RLS entirely · rotate immediately" : "rotate this credential immediately"}`,
95
+ source: "gitleaks",
96
+ trace: null,
97
+ fix: null,
98
+ manual: "Not a generation issue — a leaked credential. Rotate the key in the relevant service, then purge it from git history. No prompt rewrite applies.",
99
+ });
100
+ }
101
+ return findings;
102
+ }
103
+ //# sourceMappingURL=gitleaks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitleaks.js","sourceRoot":"","sources":["../../src/scanners/gitleaks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,mBAAmB,EAAmB,MAAM,gBAAgB,CAAC;AAEtE,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC1C,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAWlC,KAAK,UAAU,YAAY;IACzB,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;QAC9D,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAAgB,EAChB,UAAuB;IAMvB,IAAI,GAAG,GAAG,MAAM,YAAY,EAAE,CAAC;IAC/B,IAAI,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC;QACvB,GAAG,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO;YACL,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,KAAK;YAChB,KAAK,EACH,4FAA4F;SAC/F,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,yDAAyD;SACjE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,IAAI,MAAc,CAAC;QACnB,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;QAEhG,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,MAAM,SAAS,CAC5B,gCAAgC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EACxD,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAClD,CAAC;YACF,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,YAAY,EAAE;gBACpD,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;gBAC3B,OAAO,EAAE,OAAO;aACjB,CAAC,CAAC;YACH,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACzB,CAAC;QAED,MAAM,OAAO,GAAoB,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;QAC5D,OAAO,EAAE,QAAQ,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACnE,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,GAA0D,CAAC;QACrE,6CAA6C;QAC7C,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAoB,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;gBACtD,OAAO,EAAE,QAAQ,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;YACnE,CAAC;YAAC,MAAM,CAAC;gBACP,kBAAkB;YACpB,CAAC;QACH,CAAC;QACD,gCAAgC;QAChC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACjB,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC3C,CAAC;QACD,OAAO;YACL,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,mBAAmB,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE;SACpD,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAwB;IACjD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;QAC9C,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEd,MAAM,aAAa,GACjB,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YACrD,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC;YAC1C,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;QAEpC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,SAAS,CAAC;QAEvD,QAAQ,CAAC,IAAI,CAAC;YACZ,EAAE,EAAE,CAAC;YACL,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;YACjD,IAAI,EAAE,wBAAwB,WAAW,EAAE;YAC3C,KAAK,EAAE,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,0BAA0B;YACrG,IAAI,EAAE,cAAc,aAAa,CAAC,CAAC,CAAC,gDAAgD,CAAC,CAAC,CAAC,oCAAoC,EAAE;YAC7H,MAAM,EAAE,UAAU;YAClB,KAAK,EAAE,IAAI;YACX,GAAG,EAAE,IAAI;YACT,MAAM,EACJ,kJAAkJ;SACrJ,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export type OnProgress = (msg: string) => void;
2
+ export declare function autoInstallGitleaks(onProgress: OnProgress): Promise<string | null>;
3
+ export declare function autoInstallSemgrep(onProgress: OnProgress): Promise<string | null>;
@@ -0,0 +1,121 @@
1
+ import { execFile, exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { platform } from "node:os";
4
+ const execFileAsync = promisify(execFile);
5
+ const execAsync = promisify(exec);
6
+ async function hasBrew() {
7
+ try {
8
+ await execFileAsync("which", ["brew"]);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ async function hasPip() {
16
+ for (const bin of ["pip3", "pip", "python3 -m pip", "python -m pip"]) {
17
+ try {
18
+ if (bin.includes(" ")) {
19
+ await execAsync(`${bin} --version`);
20
+ }
21
+ else {
22
+ await execFileAsync("which", [bin]);
23
+ }
24
+ return bin;
25
+ }
26
+ catch {
27
+ continue;
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+ async function hasNpx() {
33
+ try {
34
+ await execFileAsync("which", ["npx"]);
35
+ return true;
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ export async function autoInstallGitleaks(onProgress) {
42
+ onProgress("gitleaks not found — attempting auto-install…");
43
+ // macOS: try brew
44
+ if (platform() === "darwin" && (await hasBrew())) {
45
+ try {
46
+ onProgress(" → brew install gitleaks");
47
+ await execAsync("brew install gitleaks", { timeout: 120_000 });
48
+ const { stdout } = await execFileAsync("which", ["gitleaks"]);
49
+ onProgress(" ✓ gitleaks installed");
50
+ return stdout.trim();
51
+ }
52
+ catch (err) {
53
+ onProgress(` ✗ brew install failed: ${err.message?.slice(0, 100)}`);
54
+ }
55
+ }
56
+ // Linux: try brew if available, otherwise try downloading the binary
57
+ if (platform() === "linux" && (await hasBrew())) {
58
+ try {
59
+ onProgress(" → brew install gitleaks");
60
+ await execAsync("brew install gitleaks", { timeout: 120_000 });
61
+ const { stdout } = await execFileAsync("which", ["gitleaks"]);
62
+ onProgress(" ✓ gitleaks installed");
63
+ return stdout.trim();
64
+ }
65
+ catch {
66
+ /* fall through */
67
+ }
68
+ }
69
+ // Try npx as a last resort (gitleaks has an npm wrapper)
70
+ if (await hasNpx()) {
71
+ try {
72
+ onProgress(" → checking npx @gitleaks/gitleaks");
73
+ await execAsync("npx --yes @gitleaks/gitleaks version", {
74
+ timeout: 60_000,
75
+ });
76
+ onProgress(" ✓ gitleaks available via npx");
77
+ return "npx-gitleaks";
78
+ }
79
+ catch {
80
+ /* fall through */
81
+ }
82
+ }
83
+ onProgress(" ✗ could not auto-install gitleaks — install manually: brew install gitleaks");
84
+ return null;
85
+ }
86
+ export async function autoInstallSemgrep(onProgress) {
87
+ onProgress("semgrep not found — attempting auto-install…");
88
+ // Try pip/pip3
89
+ const pip = await hasPip();
90
+ if (pip) {
91
+ try {
92
+ const cmd = pip.includes(" ")
93
+ ? `${pip} install semgrep`
94
+ : `${pip} install semgrep`;
95
+ onProgress(` → ${cmd}`);
96
+ await execAsync(cmd, { timeout: 180_000 });
97
+ const { stdout } = await execFileAsync("which", ["semgrep"]);
98
+ onProgress(" ✓ semgrep installed");
99
+ return stdout.trim();
100
+ }
101
+ catch (err) {
102
+ onProgress(` ✗ pip install failed: ${err.message?.slice(0, 100)}`);
103
+ }
104
+ }
105
+ // macOS: try brew
106
+ if (platform() === "darwin" && (await hasBrew())) {
107
+ try {
108
+ onProgress(" → brew install semgrep");
109
+ await execAsync("brew install semgrep", { timeout: 180_000 });
110
+ const { stdout } = await execFileAsync("which", ["semgrep"]);
111
+ onProgress(" ✓ semgrep installed");
112
+ return stdout.trim();
113
+ }
114
+ catch (err) {
115
+ onProgress(` ✗ brew install failed: ${err.message?.slice(0, 100)}`);
116
+ }
117
+ }
118
+ onProgress(" ✗ could not auto-install semgrep — install manually: pip install semgrep");
119
+ return null;
120
+ }
121
+ //# sourceMappingURL=installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/scanners/installer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { Finding } from "../types.js";
2
+ export declare function scanRLS(repoPath: string, dbUrl?: string): Promise<{
3
+ findings: Finding[];
4
+ available: boolean;
5
+ error?: string;
6
+ }>;
@@ -0,0 +1,177 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { execFile } from "node:child_process";
5
+ import { promisify } from "node:util";
6
+ const execFileAsync = promisify(execFile);
7
+ export async function scanRLS(repoPath, dbUrl) {
8
+ if (dbUrl) {
9
+ return scanRLSLive(dbUrl);
10
+ }
11
+ return scanRLSStatic(repoPath);
12
+ }
13
+ async function scanRLSStatic(repoPath) {
14
+ const migrationsDir = join(repoPath, "supabase", "migrations");
15
+ if (!existsSync(migrationsDir)) {
16
+ return {
17
+ findings: [],
18
+ available: false,
19
+ error: "no supabase/migrations directory found — skipping RLS scan",
20
+ };
21
+ }
22
+ let files;
23
+ try {
24
+ files = (await readdir(migrationsDir)).filter((f) => f.endsWith(".sql"));
25
+ }
26
+ catch {
27
+ return {
28
+ findings: [],
29
+ available: false,
30
+ error: "could not read supabase/migrations — skipping RLS scan",
31
+ };
32
+ }
33
+ if (files.length === 0) {
34
+ return {
35
+ findings: [],
36
+ available: true,
37
+ error: "no .sql files in supabase/migrations",
38
+ };
39
+ }
40
+ const tables = new Map();
41
+ for (const file of files) {
42
+ const filePath = join(migrationsDir, file);
43
+ const sql = await readFile(filePath, "utf-8");
44
+ const relPath = `supabase/migrations/${file}`;
45
+ parseMigration(sql, relPath, tables);
46
+ }
47
+ const findings = [];
48
+ for (const [, table] of tables) {
49
+ if (!table.hasRLS) {
50
+ findings.push({
51
+ id: 0,
52
+ severity: "critical",
53
+ path: table.file,
54
+ title: `Table ${table.name} has no RLS policy`,
55
+ meta: `anon key can read/write this table via the public API`,
56
+ source: "rls",
57
+ trace: null,
58
+ fix: null,
59
+ manual: null,
60
+ });
61
+ }
62
+ else if (table.isPermissive) {
63
+ findings.push({
64
+ id: 0,
65
+ severity: "medium",
66
+ path: table.file,
67
+ title: `Table ${table.name} has an overly permissive RLS policy`,
68
+ meta: `policy uses USING (true) or allows anon full access`,
69
+ source: "rls",
70
+ trace: null,
71
+ fix: null,
72
+ manual: null,
73
+ });
74
+ }
75
+ }
76
+ return { findings, available: true };
77
+ }
78
+ function parseMigration(sql, file, tables) {
79
+ const lines = sql.split("\n");
80
+ const lineCount = lines.length;
81
+ // Match CREATE TABLE statements
82
+ const createTableRe = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:public\.)?["']?(\w+)["']?/gi;
83
+ let match;
84
+ while ((match = createTableRe.exec(sql)) !== null) {
85
+ const name = match[1].toLowerCase();
86
+ if (name.startsWith("_") ||
87
+ name === "schema_migrations" ||
88
+ name === "extensions") {
89
+ continue;
90
+ }
91
+ if (!tables.has(name)) {
92
+ tables.set(name, {
93
+ name,
94
+ file,
95
+ hasRLS: false,
96
+ policies: [],
97
+ isPermissive: false,
98
+ lineCount,
99
+ });
100
+ }
101
+ }
102
+ // Match ALTER TABLE ... ENABLE ROW LEVEL SECURITY
103
+ const enableRLSRe = /ALTER\s+TABLE\s+(?:public\.)?["']?(\w+)["']?\s+ENABLE\s+ROW\s+LEVEL\s+SECURITY/gi;
104
+ while ((match = enableRLSRe.exec(sql)) !== null) {
105
+ const name = match[1].toLowerCase();
106
+ const info = tables.get(name);
107
+ if (info)
108
+ info.hasRLS = true;
109
+ }
110
+ // Match CREATE POLICY
111
+ const policyRe = /CREATE\s+POLICY\s+["']?(\w+)["']?\s+ON\s+(?:public\.)?["']?(\w+)["']?/gi;
112
+ while ((match = policyRe.exec(sql)) !== null) {
113
+ const tableName = match[2].toLowerCase();
114
+ const info = tables.get(tableName);
115
+ if (info)
116
+ info.policies.push(match[1]);
117
+ }
118
+ // Check for overly permissive policies: USING (true) on tables with RLS
119
+ const permissiveRe = /CREATE\s+POLICY\s+\S+\s+ON\s+(?:public\.)?["']?(\w+)["']?[\s\S]*?USING\s*\(\s*true\s*\)/gi;
120
+ while ((match = permissiveRe.exec(sql)) !== null) {
121
+ const name = match[1].toLowerCase();
122
+ const info = tables.get(name);
123
+ if (info)
124
+ info.isPermissive = true;
125
+ }
126
+ }
127
+ async function scanRLSLive(dbUrl) {
128
+ let psqlBin;
129
+ try {
130
+ const { stdout } = await execFileAsync("which", ["psql"]);
131
+ psqlBin = stdout.trim();
132
+ }
133
+ catch {
134
+ return {
135
+ findings: [],
136
+ available: false,
137
+ error: "psql not found — cannot run live RLS check",
138
+ };
139
+ }
140
+ try {
141
+ const { stdout } = await execFileAsync(psqlBin, [
142
+ dbUrl,
143
+ "-t",
144
+ "-A",
145
+ "-c",
146
+ `SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';`,
147
+ ], { timeout: 15_000 });
148
+ const findings = [];
149
+ for (const line of stdout.trim().split("\n")) {
150
+ if (!line)
151
+ continue;
152
+ const [table, rls] = line.split("|");
153
+ if (rls === "f" || rls === "false") {
154
+ findings.push({
155
+ id: 0,
156
+ severity: "critical",
157
+ path: `database · public.${table}`,
158
+ title: `Table ${table} has no RLS policy (live check)`,
159
+ meta: `pg_tables rowsecurity=false · anon key can access this table`,
160
+ source: "rls",
161
+ trace: null,
162
+ fix: null,
163
+ manual: null,
164
+ });
165
+ }
166
+ }
167
+ return { findings, available: true };
168
+ }
169
+ catch (err) {
170
+ return {
171
+ findings: [],
172
+ available: true,
173
+ error: `psql error: ${String(err)}`,
174
+ };
175
+ }
176
+ }
177
+ //# sourceMappingURL=rls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls.js","sourceRoot":"","sources":["../../src/scanners/rls.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Finding } from "../types.js";
2
+ import { type OnProgress } from "./installer.js";
3
+ export declare function scanSAST(repoPath: string, onProgress?: OnProgress): Promise<{
4
+ findings: Finding[];
5
+ available: boolean;
6
+ error?: string;
7
+ }>;
@@ -0,0 +1,121 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { autoInstallSemgrep } from "./installer.js";
4
+ const execFileAsync = promisify(execFile);
5
+ async function findSemgrep() {
6
+ try {
7
+ const { stdout } = await execFileAsync("which", ["semgrep"]);
8
+ return stdout.trim();
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ }
14
+ const RELEVANT_CATEGORIES = new Set([
15
+ "security",
16
+ "correctness",
17
+ "owasp",
18
+ ]);
19
+ function isRelevant(m) {
20
+ const sev = m.extra.severity?.toLowerCase();
21
+ if (sev === "info")
22
+ return false;
23
+ const cat = m.extra.metadata?.category?.toLowerCase() || "";
24
+ if (RELEVANT_CATEGORIES.has(cat))
25
+ return true;
26
+ const id = m.check_id.toLowerCase();
27
+ if (id.includes("injection") ||
28
+ id.includes("xss") ||
29
+ id.includes("auth") ||
30
+ id.includes("validation") ||
31
+ id.includes("webhook") ||
32
+ id.includes("upload") ||
33
+ id.includes("traversal") ||
34
+ id.includes("ssrf") ||
35
+ id.includes("csrf") ||
36
+ id.includes("rce") ||
37
+ id.includes("sql") ||
38
+ id.includes("crypto") ||
39
+ id.includes("secret") ||
40
+ id.includes("hardcoded") ||
41
+ id.includes("insecure")) {
42
+ return true;
43
+ }
44
+ if (sev === "error" || sev === "warning")
45
+ return true;
46
+ return false;
47
+ }
48
+ function mapSeverity(sev) {
49
+ return sev.toLowerCase() === "error" ? "critical" : "medium";
50
+ }
51
+ export async function scanSAST(repoPath, onProgress) {
52
+ let bin = await findSemgrep();
53
+ if (!bin && onProgress) {
54
+ bin = await autoInstallSemgrep(onProgress);
55
+ }
56
+ if (!bin) {
57
+ return {
58
+ findings: [],
59
+ available: false,
60
+ error: "semgrep not found — install it (pip install semgrep) to run SAST analysis",
61
+ };
62
+ }
63
+ try {
64
+ const { stdout } = await execFileAsync(bin, [
65
+ "scan",
66
+ "--config",
67
+ "auto",
68
+ "--json",
69
+ "--quiet",
70
+ "--no-git-ignore",
71
+ "--timeout",
72
+ "60",
73
+ repoPath,
74
+ ], { maxBuffer: 50 * 1024 * 1024, timeout: 300_000 });
75
+ const result = JSON.parse(stdout || '{"results":[]}');
76
+ return { findings: resultsToFindings(result.results), available: true };
77
+ }
78
+ catch (err) {
79
+ const e = err;
80
+ if (e.stdout) {
81
+ try {
82
+ const result = JSON.parse(e.stdout);
83
+ return { findings: resultsToFindings(result.results), available: true };
84
+ }
85
+ catch {
86
+ /* fall through */
87
+ }
88
+ }
89
+ return {
90
+ findings: [],
91
+ available: true,
92
+ error: `semgrep error: ${e.stderr?.slice(0, 200) || String(err)}`,
93
+ };
94
+ }
95
+ }
96
+ function resultsToFindings(results) {
97
+ const seen = new Set();
98
+ const findings = [];
99
+ const relevant = results.filter(isRelevant);
100
+ for (const m of relevant) {
101
+ const key = `${m.check_id}:${m.path}`;
102
+ if (seen.has(key))
103
+ continue;
104
+ seen.add(key);
105
+ const shortId = m.check_id.split(".").pop() || m.check_id;
106
+ const message = m.extra.message || shortId;
107
+ findings.push({
108
+ id: 0,
109
+ severity: mapSeverity(m.extra.severity),
110
+ path: m.path,
111
+ title: message.length > 120 ? message.slice(0, 117) + "…" : message,
112
+ meta: `semgrep · ${shortId}`,
113
+ source: "semgrep",
114
+ trace: null,
115
+ fix: null,
116
+ manual: null,
117
+ });
118
+ }
119
+ return findings;
120
+ }
121
+ //# sourceMappingURL=semgrep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semgrep.js","sourceRoot":"","sources":["../../src/scanners/semgrep.ts"],"names":[],"mappings":"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"}