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.
- package/README.md +53 -0
- package/dist/claude/correlator.d.ts +2 -0
- package/dist/claude/correlator.js +179 -0
- package/dist/claude/correlator.js.map +1 -0
- package/dist/claude/reader.d.ts +5 -0
- package/dist/claude/reader.js +191 -0
- package/dist/claude/reader.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/repl/display.d.ts +16 -0
- package/dist/repl/display.js +153 -0
- package/dist/repl/display.js.map +1 -0
- package/dist/repl/repl.d.ts +9 -0
- package/dist/repl/repl.js +110 -0
- package/dist/repl/repl.js.map +1 -0
- package/dist/report/html.d.ts +9 -0
- package/dist/report/html.js +174 -0
- package/dist/report/html.js.map +1 -0
- package/dist/scanners/aggregator.d.ts +12 -0
- package/dist/scanners/aggregator.js +126 -0
- package/dist/scanners/aggregator.js.map +1 -0
- package/dist/scanners/deps.d.ts +6 -0
- package/dist/scanners/deps.js +73 -0
- package/dist/scanners/deps.js.map +1 -0
- package/dist/scanners/gitleaks.d.ts +7 -0
- package/dist/scanners/gitleaks.js +103 -0
- package/dist/scanners/gitleaks.js.map +1 -0
- package/dist/scanners/installer.d.ts +3 -0
- package/dist/scanners/installer.js +121 -0
- package/dist/scanners/installer.js.map +1 -0
- package/dist/scanners/rls.d.ts +6 -0
- package/dist/scanners/rls.js +177 -0
- package/dist/scanners/rls.js.map +1 -0
- package/dist/scanners/semgrep.d.ts +7 -0
- package/dist/scanners/semgrep.js +121 -0
- package/dist/scanners/semgrep.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- 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":"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"}
|
|
@@ -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":"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"}
|
|
@@ -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,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,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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"}
|