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
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# vibecheck
|
|
2
|
+
|
|
3
|
+
Audit AI-generated codebases — trace security findings back to the prompts that caused them.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx vibecheck --with-claude-history
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
No account. No upload. Everything stays local.
|
|
12
|
+
|
|
13
|
+
## What it does
|
|
14
|
+
|
|
15
|
+
1. **Scans** your repo for secrets (gitleaks), SAST issues (semgrep), missing Supabase RLS, and vulnerable dependencies (npm audit)
|
|
16
|
+
2. **Reads** your Claude Code session history and **correlates** each finding to the prompt that generated it
|
|
17
|
+
3. **Shows** rewritten prompts that would have produced secure code the first time
|
|
18
|
+
4. **Generates** a shareable HTML report
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Node.js ≥ 18
|
|
23
|
+
- For full scanning: [gitleaks](https://github.com/gitleaks/gitleaks) and [semgrep](https://semgrep.dev) installed
|
|
24
|
+
- Degrades gracefully if scanners are missing
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx vibecheck # scan current directory
|
|
30
|
+
npx vibecheck --with-claude-history # scan + trace prompt origins
|
|
31
|
+
npx vibecheck --db-url postgres://... # include live RLS check
|
|
32
|
+
npx vibecheck ~/projects/my-app # scan a specific directory
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Interactive commands
|
|
36
|
+
|
|
37
|
+
| Command | Action |
|
|
38
|
+
|---------|--------|
|
|
39
|
+
| `1-N` | Inspect a finding |
|
|
40
|
+
| `fix` / `f` | Show the secure prompt rewrite |
|
|
41
|
+
| `ignore` / `i` | Dismiss the current finding |
|
|
42
|
+
| `next` / `n` | Jump to the next open finding |
|
|
43
|
+
| `list` / `l` | Reprint findings |
|
|
44
|
+
| `help` / `?` | Show commands |
|
|
45
|
+
| `q` | Finish and write report |
|
|
46
|
+
|
|
47
|
+
## Privacy
|
|
48
|
+
|
|
49
|
+
No code, prompts, or secrets leave this machine. Ever.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
*vibecheck looked back at what happened. It can't stop the next insecure prompt — [Symbiotic](https://www.symbioticsec.ai) does that at generation time, continuously.*
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { basename, resolve } from "node:path";
|
|
2
|
+
export function correlateFindings(findings, sessions, repoPath) {
|
|
3
|
+
for (const finding of findings) {
|
|
4
|
+
if (finding.trace || finding.manual)
|
|
5
|
+
continue;
|
|
6
|
+
const correlation = findCorrelation(finding, sessions, repoPath);
|
|
7
|
+
if (correlation) {
|
|
8
|
+
finding.trace = correlation.trace;
|
|
9
|
+
finding.fix = correlation.fix;
|
|
10
|
+
}
|
|
11
|
+
else if (!finding.manual) {
|
|
12
|
+
finding.manual = guessManualNote(finding);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function findCorrelation(finding, sessions, repoPath) {
|
|
17
|
+
const findingPath = extractFilePath(finding.path);
|
|
18
|
+
if (!findingPath)
|
|
19
|
+
return null;
|
|
20
|
+
for (const session of sessions) {
|
|
21
|
+
for (const prompt of session.prompts) {
|
|
22
|
+
const matchedFile = prompt.filesGenerated.find((f) => {
|
|
23
|
+
const resolved = f.startsWith("/") ? f : resolve(repoPath, f);
|
|
24
|
+
return (resolved.endsWith(findingPath) ||
|
|
25
|
+
basename(resolved) === basename(findingPath) ||
|
|
26
|
+
f === findingPath ||
|
|
27
|
+
f.endsWith(findingPath));
|
|
28
|
+
});
|
|
29
|
+
if (matchedFile) {
|
|
30
|
+
const trace = buildTrace(finding, prompt, session, matchedFile);
|
|
31
|
+
const fix = generateFix(finding, prompt);
|
|
32
|
+
return { trace, fix };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function extractFilePath(path) {
|
|
39
|
+
// "git history · commit abc1234" → no file path for correlation
|
|
40
|
+
if (path.startsWith("git history"))
|
|
41
|
+
return null;
|
|
42
|
+
if (path.startsWith("database ·"))
|
|
43
|
+
return null;
|
|
44
|
+
if (path.startsWith("package.json"))
|
|
45
|
+
return null;
|
|
46
|
+
// "supabase/migrations/0007_orgs.sql" or "app/api/upload/route.ts"
|
|
47
|
+
return path;
|
|
48
|
+
}
|
|
49
|
+
function buildTrace(finding, prompt, session, matchedFile) {
|
|
50
|
+
const ts = formatTimestamp(prompt.timestamp || session.timestamp);
|
|
51
|
+
const lineCount = estimateLineCount(prompt, matchedFile);
|
|
52
|
+
return {
|
|
53
|
+
prompt: `"${truncate(prompt.text, 120)}"`,
|
|
54
|
+
session: `${ts} · claude code`,
|
|
55
|
+
file: `${matchedFile}${lineCount ? ` (+${lineCount} lines)` : ""}`,
|
|
56
|
+
result: inferResult(finding),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function inferResult(finding) {
|
|
60
|
+
const title = finding.title.toLowerCase();
|
|
61
|
+
const meta = finding.meta.toLowerCase();
|
|
62
|
+
if (title.includes("rls") || title.includes("row level security")) {
|
|
63
|
+
return "RLS was never enabled — the prompt asked for the schema, not the access rules.";
|
|
64
|
+
}
|
|
65
|
+
if (title.includes("validation") || title.includes("upload")) {
|
|
66
|
+
return "No validation generated. The prompt never mentioned constraints, so none were added.";
|
|
67
|
+
}
|
|
68
|
+
if (title.includes("injection") || title.includes("interpolat")) {
|
|
69
|
+
return "Raw query params concatenated into the query. No parameterization requested.";
|
|
70
|
+
}
|
|
71
|
+
if (title.includes("webhook") || title.includes("signature")) {
|
|
72
|
+
return "Handler trusts the payload directly. Signature verification was never requested.";
|
|
73
|
+
}
|
|
74
|
+
if (title.includes("xss") || title.includes("cross-site")) {
|
|
75
|
+
return "User input rendered without sanitization. The prompt didn't mention output encoding.";
|
|
76
|
+
}
|
|
77
|
+
if (meta.includes("auth") || title.includes("auth")) {
|
|
78
|
+
return "No authentication check generated. The prompt didn't specify access control.";
|
|
79
|
+
}
|
|
80
|
+
if (title.includes("service-role") || title.includes("service_role")) {
|
|
81
|
+
return "The service-role client was placed in a client-accessible module. The prompt didn't specify server-only.";
|
|
82
|
+
}
|
|
83
|
+
return `The prompt produced this code without the security constraint. The omission led to ${finding.title.toLowerCase()}.`;
|
|
84
|
+
}
|
|
85
|
+
function generateFix(finding, prompt) {
|
|
86
|
+
const originalPrompt = prompt.text.toLowerCase();
|
|
87
|
+
const title = finding.title.toLowerCase();
|
|
88
|
+
if (title.includes("rls") || title.includes("row level security")) {
|
|
89
|
+
return [
|
|
90
|
+
`"${truncate(prompt.text, 80)}.`,
|
|
91
|
+
`Enable row level security and add a policy so a user can only select rows`,
|
|
92
|
+
`for their own records. Deny anon access by default."`,
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
if (title.includes("upload") || title.includes("validation")) {
|
|
96
|
+
return [
|
|
97
|
+
`"${truncate(prompt.text, 80)}.`,
|
|
98
|
+
`Validate MIME type (images only), cap size at 5MB, sanitize the filename`,
|
|
99
|
+
`against path traversal, require an authenticated session, and enforce an`,
|
|
100
|
+
`RLS policy so a user can only write to their own folder."`,
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
if (title.includes("injection") || title.includes("interpolat")) {
|
|
104
|
+
return [
|
|
105
|
+
`"${truncate(prompt.text, 80)}.`,
|
|
106
|
+
`Validate the params as ISO dates and use parameterized Supabase filters —`,
|
|
107
|
+
`never string-concatenate user input into the query."`,
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
if (title.includes("webhook") || title.includes("signature")) {
|
|
111
|
+
return [
|
|
112
|
+
`"${truncate(prompt.text, 80)}.`,
|
|
113
|
+
`Verify the Stripe-Signature header against the webhook secret and reject`,
|
|
114
|
+
`any event that fails verification."`,
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
if (title.includes("xss") || title.includes("cross-site")) {
|
|
118
|
+
return [
|
|
119
|
+
`"${truncate(prompt.text, 80)}.`,
|
|
120
|
+
`Sanitize all user-provided content before rendering. Use proper encoding`,
|
|
121
|
+
`and never set innerHTML with unescaped user data."`,
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
if (title.includes("auth") && !title.includes("webhook")) {
|
|
125
|
+
return [
|
|
126
|
+
`"${truncate(prompt.text, 80)}.`,
|
|
127
|
+
`Add authentication middleware that verifies the session token before`,
|
|
128
|
+
`processing the request. Reject unauthenticated requests with 401."`,
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
// Generic fallback fix
|
|
132
|
+
return [
|
|
133
|
+
`"${truncate(prompt.text, 80)}.`,
|
|
134
|
+
`Add security constraints: validate inputs, enforce authentication,`,
|
|
135
|
+
`and follow least-privilege defaults."`,
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
function formatTimestamp(ts) {
|
|
139
|
+
if (!ts)
|
|
140
|
+
return "unknown date";
|
|
141
|
+
try {
|
|
142
|
+
const d = new Date(ts);
|
|
143
|
+
if (isNaN(d.getTime()))
|
|
144
|
+
return ts;
|
|
145
|
+
return d.toISOString().replace("T", " ").slice(0, 16);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return ts;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function truncate(s, max) {
|
|
152
|
+
const clean = s.replace(/\n/g, " ").trim();
|
|
153
|
+
if (clean.length <= max)
|
|
154
|
+
return clean;
|
|
155
|
+
return clean.slice(0, max - 1) + "…";
|
|
156
|
+
}
|
|
157
|
+
function estimateLineCount(prompt, matchedFile) {
|
|
158
|
+
for (const tc of prompt.toolCalls) {
|
|
159
|
+
if (tc.filePath && tc.filePath.endsWith(basename(matchedFile))) {
|
|
160
|
+
if (tc.content) {
|
|
161
|
+
return tc.content.split("\n").length;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
function guessManualNote(finding) {
|
|
168
|
+
if (finding.source === "gitleaks") {
|
|
169
|
+
return "Not a generation issue — a leaked credential. Rotate the key in the relevant service, then purge it from git history. No prompt rewrite applies.";
|
|
170
|
+
}
|
|
171
|
+
if (finding.source === "deps") {
|
|
172
|
+
return "Not a generation issue — a vulnerable dependency. Update or replace the package. No prompt rewrite applies.";
|
|
173
|
+
}
|
|
174
|
+
if (finding.title.toLowerCase().includes("service-role")) {
|
|
175
|
+
return "Move the service-role client to a server-only module. Architectural fix, not a prompt rewrite.";
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=correlator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlator.js","sourceRoot":"","sources":["../../src/claude/correlator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { join, resolve, basename } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
const CLAUDE_DIRS = [
|
|
6
|
+
join(homedir(), ".claude", "projects"),
|
|
7
|
+
join(homedir(), ".claude"),
|
|
8
|
+
];
|
|
9
|
+
export async function readClaudeHistory(repoPath) {
|
|
10
|
+
const resolvedRepo = resolve(repoPath);
|
|
11
|
+
const allSessions = [];
|
|
12
|
+
for (const dir of CLAUDE_DIRS) {
|
|
13
|
+
if (!existsSync(dir))
|
|
14
|
+
continue;
|
|
15
|
+
const sessions = await findSessionFiles(dir, resolvedRepo);
|
|
16
|
+
allSessions.push(...sessions);
|
|
17
|
+
}
|
|
18
|
+
// Sort by timestamp, newest first
|
|
19
|
+
allSessions.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
20
|
+
return { sessions: allSessions, sessionCount: allSessions.length };
|
|
21
|
+
}
|
|
22
|
+
async function findSessionFiles(baseDir, repoPath) {
|
|
23
|
+
const sessions = [];
|
|
24
|
+
async function walk(dir) {
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const fullPath = join(dir, entry.name);
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
// Look inside project-specific directories
|
|
36
|
+
await walk(fullPath);
|
|
37
|
+
}
|
|
38
|
+
else if (entry.name.endsWith(".jsonl") || entry.name.endsWith(".json")) {
|
|
39
|
+
try {
|
|
40
|
+
const session = await parseSessionFile(fullPath, repoPath);
|
|
41
|
+
if (session && session.prompts.length > 0) {
|
|
42
|
+
sessions.push(session);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Skip unparseable files
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await walk(baseDir);
|
|
52
|
+
return sessions;
|
|
53
|
+
}
|
|
54
|
+
async function parseSessionFile(filePath, repoPath) {
|
|
55
|
+
const content = await readFile(filePath, "utf-8");
|
|
56
|
+
const lines = content.split("\n").filter(Boolean);
|
|
57
|
+
let sessionTimestamp = "";
|
|
58
|
+
const prompts = [];
|
|
59
|
+
// Check if this session is related to the repo
|
|
60
|
+
let isRelevant = false;
|
|
61
|
+
for (const line of lines) {
|
|
62
|
+
let entry;
|
|
63
|
+
try {
|
|
64
|
+
entry = JSON.parse(line);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Detect relevance from the session's working directory or file paths
|
|
70
|
+
if (!isRelevant) {
|
|
71
|
+
const cwd = (entry.cwd || entry.workingDirectory || "");
|
|
72
|
+
if (cwd && resolve(cwd).startsWith(repoPath)) {
|
|
73
|
+
isRelevant = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Extract user messages as prompts
|
|
77
|
+
const role = entry.role || entry.type;
|
|
78
|
+
if (role === "human" || role === "user") {
|
|
79
|
+
const text = extractText(entry);
|
|
80
|
+
if (text) {
|
|
81
|
+
const ts = entry.timestamp ||
|
|
82
|
+
entry.createdAt ||
|
|
83
|
+
sessionTimestamp;
|
|
84
|
+
if (!sessionTimestamp)
|
|
85
|
+
sessionTimestamp = ts;
|
|
86
|
+
prompts.push({
|
|
87
|
+
text,
|
|
88
|
+
timestamp: ts,
|
|
89
|
+
filesGenerated: [],
|
|
90
|
+
toolCalls: [],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Extract assistant responses with tool usage to find generated files
|
|
95
|
+
if (role === "assistant") {
|
|
96
|
+
const toolCalls = extractToolCalls(entry);
|
|
97
|
+
if (toolCalls.length > 0 && prompts.length > 0) {
|
|
98
|
+
const lastPrompt = prompts[prompts.length - 1];
|
|
99
|
+
lastPrompt.toolCalls.push(...toolCalls);
|
|
100
|
+
for (const tc of toolCalls) {
|
|
101
|
+
if (tc.filePath) {
|
|
102
|
+
lastPrompt.filesGenerated.push(tc.filePath);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// If file path hints at the project
|
|
109
|
+
if (!isRelevant) {
|
|
110
|
+
const repoName = basename(repoPath).toLowerCase();
|
|
111
|
+
if (filePath.toLowerCase().includes(repoName)) {
|
|
112
|
+
isRelevant = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!isRelevant && prompts.length > 0) {
|
|
116
|
+
// Check if any generated files match repo files
|
|
117
|
+
for (const p of prompts) {
|
|
118
|
+
for (const f of p.filesGenerated) {
|
|
119
|
+
if (resolve(f).startsWith(repoPath) || !f.startsWith("/")) {
|
|
120
|
+
isRelevant = true;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (isRelevant)
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!isRelevant)
|
|
129
|
+
return null;
|
|
130
|
+
// Try to get session timestamp from file stat if not found in content
|
|
131
|
+
if (!sessionTimestamp) {
|
|
132
|
+
try {
|
|
133
|
+
const s = await stat(filePath);
|
|
134
|
+
sessionTimestamp = s.mtime.toISOString();
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
sessionTimestamp = new Date().toISOString();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { timestamp: sessionTimestamp, prompts };
|
|
141
|
+
}
|
|
142
|
+
function extractText(entry) {
|
|
143
|
+
if (typeof entry.content === "string")
|
|
144
|
+
return entry.content;
|
|
145
|
+
if (typeof entry.message === "string")
|
|
146
|
+
return entry.message;
|
|
147
|
+
if (Array.isArray(entry.content)) {
|
|
148
|
+
const texts = entry.content
|
|
149
|
+
.filter((c) => c.type === "text" && typeof c.text === "string")
|
|
150
|
+
.map((c) => c.text);
|
|
151
|
+
return texts.join("\n");
|
|
152
|
+
}
|
|
153
|
+
if (entry.message &&
|
|
154
|
+
typeof entry.message === "object" &&
|
|
155
|
+
entry.message.content) {
|
|
156
|
+
return extractText(entry.message);
|
|
157
|
+
}
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
160
|
+
function extractToolCalls(entry) {
|
|
161
|
+
const calls = [];
|
|
162
|
+
const content = entry.content;
|
|
163
|
+
if (Array.isArray(content)) {
|
|
164
|
+
for (const block of content) {
|
|
165
|
+
if (block.type === "tool_use" || block.type === "tool_call") {
|
|
166
|
+
const tc = {
|
|
167
|
+
tool: (block.name || block.function?.name || "unknown"),
|
|
168
|
+
args: (block.input || block.function?.arguments || {}),
|
|
169
|
+
};
|
|
170
|
+
// Extract file paths from write/create tool calls
|
|
171
|
+
const toolName = tc.tool.toLowerCase();
|
|
172
|
+
if (toolName.includes("write") ||
|
|
173
|
+
toolName.includes("create") ||
|
|
174
|
+
toolName.includes("edit") ||
|
|
175
|
+
toolName.includes("file")) {
|
|
176
|
+
const path = tc.args.file_path ||
|
|
177
|
+
tc.args.path ||
|
|
178
|
+
tc.args.filePath ||
|
|
179
|
+
tc.args.file;
|
|
180
|
+
if (path) {
|
|
181
|
+
tc.filePath = path;
|
|
182
|
+
tc.content = tc.args.content || undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
calls.push(tc);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return calls;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.js","sourceRoot":"","sources":["../../src/claude/reader.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import { runAllScanners } from "./scanners/aggregator.js";
|
|
5
|
+
import { readClaudeHistory } from "./claude/reader.js";
|
|
6
|
+
import { correlateFindings } from "./claude/correlator.js";
|
|
7
|
+
import { printBoot, printNoFindings } from "./repl/display.js";
|
|
8
|
+
import { startRepl } from "./repl/repl.js";
|
|
9
|
+
async function main() {
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const withClaudeHistory = args.includes("--with-claude-history");
|
|
12
|
+
const dbUrlIdx = args.indexOf("--db-url");
|
|
13
|
+
const dbUrl = dbUrlIdx !== -1 ? args[dbUrlIdx + 1] : undefined;
|
|
14
|
+
const helpFlag = args.includes("--help") || args.includes("-h");
|
|
15
|
+
if (helpFlag) {
|
|
16
|
+
printUsage();
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
// Determine repo path: first non-flag arg, or cwd
|
|
20
|
+
let repoPath = process.cwd();
|
|
21
|
+
for (const arg of args) {
|
|
22
|
+
if (!arg.startsWith("-") && arg !== dbUrl) {
|
|
23
|
+
repoPath = resolve(arg);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Banner
|
|
28
|
+
console.log(`$ npx vibecheck ${withClaudeHistory ? pc.magenta("--with-claude-history") : ""}`);
|
|
29
|
+
console.log();
|
|
30
|
+
// Run scanners
|
|
31
|
+
const result = await runAllScanners({ repoPath, dbUrl, withClaudeHistory }, (msg) => console.log(pc.dim(msg)));
|
|
32
|
+
// Read Claude history if requested
|
|
33
|
+
if (withClaudeHistory) {
|
|
34
|
+
try {
|
|
35
|
+
const { sessions, sessionCount } = await readClaudeHistory(repoPath);
|
|
36
|
+
result.stats.claudeSessions = sessionCount;
|
|
37
|
+
if (sessions.length > 0 && result.findings.length > 0) {
|
|
38
|
+
correlateFindings(result.findings, sessions, repoPath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.log(pc.dim(pc.yellow(` ⚠ could not read claude history: ${String(err)}`)));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Fill in manual notes for findings that weren't correlated
|
|
46
|
+
for (const f of result.findings) {
|
|
47
|
+
if (!f.trace && !f.manual) {
|
|
48
|
+
if (f.source === "rls") {
|
|
49
|
+
f.manual =
|
|
50
|
+
"Enable row level security on this table and add appropriate policies. Without RLS, any client with the anon key can read and write all rows.";
|
|
51
|
+
}
|
|
52
|
+
else if (f.source === "semgrep") {
|
|
53
|
+
f.manual =
|
|
54
|
+
"This is a code-level issue found by static analysis. Review the flagged code and apply the recommended fix.";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Boot line
|
|
59
|
+
printBoot(result.stats, withClaudeHistory);
|
|
60
|
+
if (result.findings.length === 0) {
|
|
61
|
+
printNoFindings();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Start interactive REPL
|
|
65
|
+
await startRepl(result.findings, result.stats, repoPath);
|
|
66
|
+
}
|
|
67
|
+
function printUsage() {
|
|
68
|
+
console.log(`
|
|
69
|
+
${pc.bold("vibecheck")} — audit AI-generated codebases
|
|
70
|
+
|
|
71
|
+
${pc.dim("USAGE")}
|
|
72
|
+
npx vibecheck [options] [path]
|
|
73
|
+
|
|
74
|
+
${pc.dim("OPTIONS")}
|
|
75
|
+
--with-claude-history Read Claude Code session history and correlate
|
|
76
|
+
findings to the prompts that generated them
|
|
77
|
+
--db-url <url> Live Supabase RLS check via a postgres connection
|
|
78
|
+
-h, --help Show this help
|
|
79
|
+
|
|
80
|
+
${pc.dim("EXAMPLES")}
|
|
81
|
+
npx vibecheck scan current directory
|
|
82
|
+
npx vibecheck --with-claude-history scan + trace prompt origins
|
|
83
|
+
npx vibecheck --db-url postgres://... include live RLS check
|
|
84
|
+
npx vibecheck ~/projects/my-app scan a specific directory
|
|
85
|
+
|
|
86
|
+
${pc.dim("COMMANDS (interactive)")}
|
|
87
|
+
1-N inspect a finding
|
|
88
|
+
fix / f show the secure prompt rewrite
|
|
89
|
+
ignore / i dismiss the current finding
|
|
90
|
+
next / n jump to the next open finding
|
|
91
|
+
list / l reprint findings
|
|
92
|
+
help / ? show commands
|
|
93
|
+
q finish and write report
|
|
94
|
+
|
|
95
|
+
${pc.dim(pc.gray("local only — no code, prompts, or secrets leave this machine."))}
|
|
96
|
+
`);
|
|
97
|
+
}
|
|
98
|
+
main().catch((err) => {
|
|
99
|
+
console.error(pc.red(`fatal: ${err.message || err}`));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Finding, FindingStatus } from "../types.js";
|
|
2
|
+
export declare function printBoot(stats: {
|
|
3
|
+
gitHistory: boolean;
|
|
4
|
+
sourceScanned: boolean;
|
|
5
|
+
supabaseMigrations: boolean;
|
|
6
|
+
claudeSessions: number;
|
|
7
|
+
stack: string[];
|
|
8
|
+
contributors: number;
|
|
9
|
+
}, withClaude: boolean): void;
|
|
10
|
+
export declare function printList(findings: Finding[], statuses: FindingStatus[]): void;
|
|
11
|
+
export declare function printInspect(finding: Finding, index: number): void;
|
|
12
|
+
export declare function printFix(finding: Finding): void;
|
|
13
|
+
export declare function printIgnore(index: number, ignored: boolean): void;
|
|
14
|
+
export declare function printHelp(findingCount: number): void;
|
|
15
|
+
export declare function printFinish(findings: Finding[], statuses: FindingStatus[], reportPath: string): void;
|
|
16
|
+
export declare function printNoFindings(): void;
|