sertivibed 0.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,222 @@
1
+ "use strict";
2
+ /**
3
+ * Ripgrep Wrapper
4
+ *
5
+ * Kjører ripgrep (rg) med JSON output for stabil parsing.
6
+ * Fallback til grep hvis rg ikke er installert.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.isRipgrepAvailable = isRipgrepAvailable;
10
+ exports.search = search;
11
+ exports.searchAbsence = searchAbsence;
12
+ exports.getExpandedContext = getExpandedContext;
13
+ const child_process_1 = require("child_process");
14
+ // ============================================
15
+ // CHECK AVAILABILITY
16
+ // ============================================
17
+ function isRipgrepAvailable() {
18
+ try {
19
+ // På Windows må vi bruke cmd.exe som shell for å finne rg i PATH
20
+ const result = (0, child_process_1.execSync)("rg --version", {
21
+ stdio: "pipe",
22
+ shell: process.platform === "win32" ? "cmd.exe" : "/bin/sh"
23
+ });
24
+ return true;
25
+ }
26
+ catch {
27
+ // rg not available, will fall back to grep
28
+ return false;
29
+ }
30
+ }
31
+ /**
32
+ * Søk etter pattern i filer med ripgrep
33
+ */
34
+ function search(options) {
35
+ const { pattern, paths, cwd, ignoreCase = true, contextLines = 3 } = options;
36
+ if (!isRipgrepAvailable()) {
37
+ return searchWithGrep(options);
38
+ }
39
+ const args = [
40
+ "--json",
41
+ "--no-heading",
42
+ "--line-number",
43
+ `-C${contextLines}`,
44
+ ignoreCase ? "--ignore-case" : "",
45
+ "--glob", "!node_modules/**",
46
+ "--glob", "!dist/**",
47
+ "--glob", "!.next/**",
48
+ "--glob", "!.git/**",
49
+ "--glob", "!*.min.js",
50
+ "-e", pattern,
51
+ ...paths.flatMap(p => ["--glob", p])
52
+ ].filter(Boolean);
53
+ try {
54
+ const result = (0, child_process_1.spawnSync)("rg", args, {
55
+ cwd,
56
+ encoding: "utf-8",
57
+ maxBuffer: 10 * 1024 * 1024, // 10MB
58
+ });
59
+ // rg returnerer exit code 1 hvis ingen matches (ikke en feil)
60
+ if (result.status !== 0 && result.status !== 1) {
61
+ return { found: false, matches: [], matchCount: 0 };
62
+ }
63
+ const matches = parseRipgrepOutput(result.stdout || "", cwd);
64
+ return {
65
+ found: matches.length > 0,
66
+ matches,
67
+ matchCount: matches.length
68
+ };
69
+ }
70
+ catch {
71
+ return { found: false, matches: [], matchCount: 0 };
72
+ }
73
+ }
74
+ /**
75
+ * Sjekk om pattern IKKE finnes (for absence-regler)
76
+ */
77
+ function searchAbsence(options) {
78
+ const result = search(options);
79
+ return {
80
+ found: !result.found, // Invertert: "funnet" betyr at pattern MANGLER
81
+ matches: [], // Ingen matches for absence
82
+ matchCount: 0
83
+ };
84
+ }
85
+ // ============================================
86
+ // PARSING
87
+ // ============================================
88
+ function parseRipgrepOutput(output, cwd) {
89
+ const lines = output.trim().split("\n").filter(Boolean);
90
+ const matches = [];
91
+ const seenLocations = new Set();
92
+ for (const line of lines) {
93
+ try {
94
+ const parsed = JSON.parse(line);
95
+ if (parsed.type === "match") {
96
+ const match = parsed;
97
+ const path = match.data.path.text;
98
+ const lineNum = match.data.line_number;
99
+ const locationKey = `${path}:${lineNum}`;
100
+ // Dedupliser
101
+ if (seenLocations.has(locationKey))
102
+ continue;
103
+ seenLocations.add(locationKey);
104
+ const matchText = match.data.submatches?.[0]?.match?.text || "";
105
+ matches.push({
106
+ path: path.replace(cwd + "/", ""), // Relativ path
107
+ lineStart: Math.max(1, lineNum - 2),
108
+ lineEnd: lineNum + 2,
109
+ snippet: match.data.lines.text.trim(),
110
+ matchText
111
+ });
112
+ }
113
+ }
114
+ catch {
115
+ // Ignorer linjer som ikke er valid JSON
116
+ }
117
+ }
118
+ return matches;
119
+ }
120
+ // ============================================
121
+ // FALLBACK: GREP
122
+ // ============================================
123
+ function searchWithGrep(options) {
124
+ const { pattern, paths, cwd, ignoreCase = true } = options;
125
+ // Build include patterns from paths
126
+ const includeArgs = [];
127
+ for (const p of paths) {
128
+ // Convert glob to grep include pattern
129
+ // e.g., "**/*.sql" -> "--include=*.sql"
130
+ const match = p.match(/\*\*\/\*\.(\w+)$/) || p.match(/\*\.(\w+)$/);
131
+ if (match) {
132
+ includeArgs.push("--include", `*.${match[1]}`);
133
+ }
134
+ }
135
+ // Fallback: include common file types if no specific includes
136
+ if (includeArgs.length === 0) {
137
+ includeArgs.push("--include", "*.ts", "--include", "*.tsx", "--include", "*.js", "--include", "*.jsx", "--include", "*.sql");
138
+ }
139
+ const args = [
140
+ "-r",
141
+ "-n",
142
+ ignoreCase ? "-i" : "",
143
+ "-E", // Extended regex
144
+ ...includeArgs,
145
+ "--exclude-dir=node_modules",
146
+ "--exclude-dir=dist",
147
+ "--exclude-dir=.next",
148
+ "--exclude-dir=.git",
149
+ pattern,
150
+ "."
151
+ ].filter(Boolean);
152
+ try {
153
+ // Don't use shell on Windows - it breaks argument handling
154
+ const result = (0, child_process_1.spawnSync)("grep", args, {
155
+ cwd,
156
+ encoding: "utf-8",
157
+ });
158
+ if (result.status !== 0 && result.status !== 1) {
159
+ return { found: false, matches: [], matchCount: 0 };
160
+ }
161
+ const matches = parseGrepOutput(result.stdout || "");
162
+ return {
163
+ found: matches.length > 0,
164
+ matches,
165
+ matchCount: matches.length
166
+ };
167
+ }
168
+ catch {
169
+ return { found: false, matches: [], matchCount: 0 };
170
+ }
171
+ }
172
+ function parseGrepOutput(output) {
173
+ const lines = output.trim().split("\n").filter(Boolean);
174
+ const matches = [];
175
+ for (const line of lines) {
176
+ // Format: ./path/to/file.ts:123:matched line content
177
+ const match = line.match(/^\.?\/?([^:]+):(\d+):(.*)$/);
178
+ if (match) {
179
+ const [, path, lineStr, content] = match;
180
+ const lineNum = parseInt(lineStr, 10);
181
+ matches.push({
182
+ path,
183
+ lineStart: lineNum,
184
+ lineEnd: lineNum,
185
+ snippet: content.trim(),
186
+ matchText: content.trim()
187
+ });
188
+ }
189
+ }
190
+ return matches;
191
+ }
192
+ // ============================================
193
+ // CONTEXT FETCHING
194
+ // ============================================
195
+ const fs_1 = require("fs");
196
+ const path_1 = require("path");
197
+ /**
198
+ * Hent flere linjer rundt en match for bedre kontekst
199
+ */
200
+ function getExpandedContext(cwd, filePath, lineNumber, contextBefore = 3, contextAfter = 3) {
201
+ const fullPath = (0, path_1.join)(cwd, filePath);
202
+ if (!(0, fs_1.existsSync)(fullPath)) {
203
+ return "[File not found]";
204
+ }
205
+ try {
206
+ const content = (0, fs_1.readFileSync)(fullPath, "utf-8");
207
+ const lines = content.split("\n");
208
+ const start = Math.max(0, lineNumber - 1 - contextBefore);
209
+ const end = Math.min(lines.length, lineNumber + contextAfter);
210
+ return lines
211
+ .slice(start, end)
212
+ .map((line, i) => {
213
+ const num = start + i + 1;
214
+ const marker = num === lineNumber ? "→" : " ";
215
+ return `${marker} ${num.toString().padStart(4)}: ${line}`;
216
+ })
217
+ .join("\n");
218
+ }
219
+ catch {
220
+ return "[Could not read file]";
221
+ }
222
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "sertivibed",
3
+ "version": "0.1.0",
4
+ "description": "Evidence-first security scanner for Supabase/React apps",
5
+ "bin": {
6
+ "sertivibed": "./dist/cli.js"
7
+ },
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "dev": "ts-node src/cli.ts",
11
+ "scan": "ts-node src/cli.ts scan",
12
+ "test": "node --test"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/sertivibed/sertivibed-cli"
17
+ },
18
+ "keywords": [
19
+ "security",
20
+ "supabase",
21
+ "rls",
22
+ "scanner",
23
+ "audit",
24
+ "react",
25
+ "nextjs",
26
+ "prisma"
27
+ ],
28
+ "author": "Håkon Haugen",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "commander": "^12.0.0",
32
+ "chalk": "^5.3.0",
33
+ "handlebars": "^4.7.8"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.0.0",
37
+ "typescript": "^5.0.0",
38
+ "ts-node": "^10.9.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "templates"
46
+ ]
47
+ }