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.
- package/README.md +209 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +167 -0
- package/dist/reporters/json.d.ts +12 -0
- package/dist/reporters/json.js +39 -0
- package/dist/reporters/md.d.ts +8 -0
- package/dist/reporters/md.js +278 -0
- package/dist/rules/index.d.ts +19 -0
- package/dist/rules/index.js +263 -0
- package/dist/scan.d.ts +8 -0
- package/dist/scan.js +278 -0
- package/dist/types.d.ts +120 -0
- package/dist/types.js +15 -0
- package/dist/utils/fs.d.ts +35 -0
- package/dist/utils/fs.js +195 -0
- package/dist/utils/rg.d.ts +32 -0
- package/dist/utils/rg.js +222 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Sertivibed
|
|
2
|
+
|
|
3
|
+
**Evidence-first security scanner for Supabase/React apps.**
|
|
4
|
+
|
|
5
|
+
Sertivibed scans your codebase for common security issues and generates detailed reports with code evidence. No AI guessing — every finding is backed by actual code snippets.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Scan current directory
|
|
11
|
+
npx sertivibed scan .
|
|
12
|
+
|
|
13
|
+
# Scan a specific project
|
|
14
|
+
npx sertivibed scan ./my-app
|
|
15
|
+
|
|
16
|
+
# Show detailed progress
|
|
17
|
+
npx sertivibed scan . --verbose
|
|
18
|
+
|
|
19
|
+
# Fail CI if HIGH+ issues found
|
|
20
|
+
npx sertivibed scan . --fail-on high
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Use with npx (no install needed)
|
|
27
|
+
npx sertivibed scan .
|
|
28
|
+
|
|
29
|
+
# Or install globally
|
|
30
|
+
npm install -g sertivibed
|
|
31
|
+
sertivibed scan .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## CLI Options
|
|
35
|
+
|
|
36
|
+
| Flag | Description |
|
|
37
|
+
|------|-------------|
|
|
38
|
+
| `--verbose`, `-v` | Show detailed progress |
|
|
39
|
+
| `--out <dir>` | Custom output directory (default: scanned project) |
|
|
40
|
+
| `--fail-on <severity>` | Exit code 1 if issues >= threshold (`critical`, `high`, `medium`, `low`) |
|
|
41
|
+
| `--json-only` | Only output JSON, skip markdown report |
|
|
42
|
+
| `--quiet` | Minimal terminal output |
|
|
43
|
+
| `--output <file>`, `-o` | Markdown report filename (default: `sertivibed-report.md`) |
|
|
44
|
+
| `--json <file>`, `-j` | JSON output filename (default: `sertivibed-results.json`) |
|
|
45
|
+
|
|
46
|
+
## Security Rules (12)
|
|
47
|
+
|
|
48
|
+
### RLS (Row Level Security)
|
|
49
|
+
|
|
50
|
+
| Rule | Severity | Description |
|
|
51
|
+
|------|----------|-------------|
|
|
52
|
+
| RLS001 | CRITICAL | RLS not enabled on table |
|
|
53
|
+
| RLS002 | MEDIUM | Missing DELETE policy when `.delete()` is used |
|
|
54
|
+
| RLS003 | HIGH | Policy missing ownership check (`auth.uid() = user_id`) |
|
|
55
|
+
|
|
56
|
+
### Authentication & Authorization
|
|
57
|
+
|
|
58
|
+
| Rule | Severity | Description |
|
|
59
|
+
|------|----------|-------------|
|
|
60
|
+
| AUTH001 | CRITICAL | Service role key in client code |
|
|
61
|
+
|
|
62
|
+
### Privacy
|
|
63
|
+
|
|
64
|
+
| Rule | Severity | Description |
|
|
65
|
+
|------|----------|-------------|
|
|
66
|
+
| PRIV001 | MEDIUM | Auth token in localStorage |
|
|
67
|
+
| PRIV002 | MEDIUM | PII in console.log |
|
|
68
|
+
|
|
69
|
+
### Secrets
|
|
70
|
+
|
|
71
|
+
| Rule | Severity | Description |
|
|
72
|
+
|------|----------|-------------|
|
|
73
|
+
| SEC001 | HIGH | Hardcoded API key |
|
|
74
|
+
|
|
75
|
+
### XSS (Cross-Site Scripting)
|
|
76
|
+
|
|
77
|
+
| Rule | Severity | Description |
|
|
78
|
+
|------|----------|-------------|
|
|
79
|
+
| XSS001 | HIGH | `dangerouslySetInnerHTML` usage |
|
|
80
|
+
| XSS002 | HIGH | Direct `innerHTML` assignment |
|
|
81
|
+
|
|
82
|
+
### Storage
|
|
83
|
+
|
|
84
|
+
| Rule | Severity | Description |
|
|
85
|
+
|------|----------|-------------|
|
|
86
|
+
| STOR001 | MEDIUM | File upload without validation |
|
|
87
|
+
| STOR002 | MEDIUM | Public storage URL without signing |
|
|
88
|
+
|
|
89
|
+
### Configuration
|
|
90
|
+
|
|
91
|
+
| Rule | Severity | Description |
|
|
92
|
+
|------|----------|-------------|
|
|
93
|
+
| CONF001 | LOW | Missing Content Security Policy |
|
|
94
|
+
|
|
95
|
+
## Output
|
|
96
|
+
|
|
97
|
+
Sertivibed generates two files:
|
|
98
|
+
|
|
99
|
+
- **`sertivibed-report.md`** — Human-readable report with code evidence
|
|
100
|
+
- **`sertivibed-results.json`** — Machine-readable for CI/CD integration
|
|
101
|
+
|
|
102
|
+
### Example Report
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
SERTIVIBED SIKKERHETSRAPPORT
|
|
106
|
+
my-app
|
|
107
|
+
10.1.2026
|
|
108
|
+
|
|
109
|
+
Totalvurdering: BESTATT
|
|
110
|
+
|
|
111
|
+
## Coverage
|
|
112
|
+
|
|
113
|
+
Regler kjort:
|
|
114
|
+
RLS001, RLS002, RLS003, AUTH001, PRIV001, PRIV002, SEC001, XSS001, XSS002
|
|
115
|
+
|
|
116
|
+
## Sammendrag
|
|
117
|
+
|
|
118
|
+
| Severity | Antall |
|
|
119
|
+
|-----------|--------|
|
|
120
|
+
| CRITICAL | 0 |
|
|
121
|
+
| HIGH | 0 |
|
|
122
|
+
| MEDIUM | 0 |
|
|
123
|
+
| LOW | 1 |
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## CI/CD Integration
|
|
127
|
+
|
|
128
|
+
### GitHub Actions
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
name: Security Scan
|
|
132
|
+
|
|
133
|
+
on: [push, pull_request]
|
|
134
|
+
|
|
135
|
+
jobs:
|
|
136
|
+
security:
|
|
137
|
+
runs-on: ubuntu-latest
|
|
138
|
+
steps:
|
|
139
|
+
- uses: actions/checkout@v4
|
|
140
|
+
|
|
141
|
+
- name: Run Sertivibed
|
|
142
|
+
run: npx sertivibed scan . --fail-on high --quiet
|
|
143
|
+
|
|
144
|
+
- name: Upload Report
|
|
145
|
+
if: always()
|
|
146
|
+
uses: actions/upload-artifact@v4
|
|
147
|
+
with:
|
|
148
|
+
name: security-report
|
|
149
|
+
path: sertivibed-report.md
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### GitLab CI
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
security-scan:
|
|
156
|
+
image: node:20
|
|
157
|
+
script:
|
|
158
|
+
- npx sertivibed scan . --fail-on high
|
|
159
|
+
artifacts:
|
|
160
|
+
paths:
|
|
161
|
+
- sertivibed-report.md
|
|
162
|
+
when: always
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Philosophy
|
|
166
|
+
|
|
167
|
+
Sertivibed follows an **evidence-first** approach:
|
|
168
|
+
|
|
169
|
+
1. **Every finding has code evidence** — No vague warnings. You see the exact file and line.
|
|
170
|
+
2. **Verification steps included** — Each finding tells you how to manually verify it's real.
|
|
171
|
+
3. **Smart detection** — Skips RLS checks for Prisma projects (they use different auth patterns).
|
|
172
|
+
4. **No false confidence** — We only check what we can verify. Unknown stacks get honest "N/A".
|
|
173
|
+
|
|
174
|
+
## Supported Stacks
|
|
175
|
+
|
|
176
|
+
| Stack | Detection | RLS Rules |
|
|
177
|
+
|-------|-----------|-----------|
|
|
178
|
+
| Supabase | Auto-detected | Full support |
|
|
179
|
+
| Prisma | Auto-detected | Skipped (check API routes instead) |
|
|
180
|
+
| Next.js | Auto-detected | Full support |
|
|
181
|
+
| Vite/React | Auto-detected | Full support |
|
|
182
|
+
|
|
183
|
+
## Requirements
|
|
184
|
+
|
|
185
|
+
- Node.js >= 18
|
|
186
|
+
- ripgrep (rg) recommended for performance, falls back to grep
|
|
187
|
+
|
|
188
|
+
### Install ripgrep
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# macOS
|
|
192
|
+
brew install ripgrep
|
|
193
|
+
|
|
194
|
+
# Ubuntu/Debian
|
|
195
|
+
sudo apt install ripgrep
|
|
196
|
+
|
|
197
|
+
# Windows
|
|
198
|
+
choco install ripgrep
|
|
199
|
+
# or
|
|
200
|
+
winget install BurntSushi.ripgrep.MSVC
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Contributing
|
|
204
|
+
|
|
205
|
+
Issues and PRs welcome at [github.com/sertivibed/sertivibed-cli](https://github.com/sertivibed/sertivibed-cli).
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT - Hakon Haugen
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sertivibed CLI
|
|
4
|
+
*
|
|
5
|
+
* Evidence-first security screening for Supabase/React apps.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx sertivibed scan # Scan current directory
|
|
9
|
+
* npx sertivibed scan ./my-app # Scan specific directory
|
|
10
|
+
* npx sertivibed scan --verbose # Show progress
|
|
11
|
+
* npx sertivibed scan --fail-on=high # Exit 1 if HIGH+ found
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Sertivibed CLI
|
|
5
|
+
*
|
|
6
|
+
* Evidence-first security screening for Supabase/React apps.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx sertivibed scan # Scan current directory
|
|
10
|
+
* npx sertivibed scan ./my-app # Scan specific directory
|
|
11
|
+
* npx sertivibed scan --verbose # Show progress
|
|
12
|
+
* npx sertivibed scan --fail-on=high # Exit 1 if HIGH+ found
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const commander_1 = require("commander");
|
|
16
|
+
const fs_1 = require("fs");
|
|
17
|
+
const path_1 = require("path");
|
|
18
|
+
const scan_1 = require("./scan");
|
|
19
|
+
const md_1 = require("./reporters/md");
|
|
20
|
+
const json_1 = require("./reporters/json");
|
|
21
|
+
const types_1 = require("./types");
|
|
22
|
+
// ============================================
|
|
23
|
+
// CLI SETUP
|
|
24
|
+
// ============================================
|
|
25
|
+
const program = new commander_1.Command();
|
|
26
|
+
program
|
|
27
|
+
.name("sertivibed")
|
|
28
|
+
.description("Evidence-first security screening for Supabase/React apps")
|
|
29
|
+
.version("0.1.0");
|
|
30
|
+
// ============================================
|
|
31
|
+
// SCAN COMMAND
|
|
32
|
+
// ============================================
|
|
33
|
+
program
|
|
34
|
+
.command("scan")
|
|
35
|
+
.description("Scan a project for security issues")
|
|
36
|
+
.argument("[path]", "Path to project (default: current directory)", ".")
|
|
37
|
+
.option("-v, --verbose", "Show detailed progress")
|
|
38
|
+
.option("-o, --output <file>", "Output markdown report to file", "sertivibed-report.md")
|
|
39
|
+
.option("-j, --json <file>", "Output JSON results to file", "sertivibed-results.json")
|
|
40
|
+
.option("--out <dir>", "Custom output directory (default: scanned project dir)")
|
|
41
|
+
.option("--fail-on <severity>", "Exit with code 1 if severity >= threshold (critical|high|medium|low)")
|
|
42
|
+
.option("--json-only", "Only output JSON, skip markdown report")
|
|
43
|
+
.option("--quiet", "Minimal terminal output")
|
|
44
|
+
.option("--no-report", "Skip generating markdown report")
|
|
45
|
+
.option("--no-json", "Skip generating JSON output")
|
|
46
|
+
.action(async (path, options) => {
|
|
47
|
+
const targetPath = (0, path_1.resolve)(path);
|
|
48
|
+
const quiet = options.quiet || false;
|
|
49
|
+
// Validate path exists
|
|
50
|
+
if (!(0, fs_1.existsSync)(targetPath)) {
|
|
51
|
+
console.error(`❌ Path not found: ${targetPath}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
// Determine output directory
|
|
55
|
+
const outputDir = options.out ? (0, path_1.resolve)(options.out) : targetPath;
|
|
56
|
+
if (options.out && !(0, fs_1.existsSync)(outputDir)) {
|
|
57
|
+
(0, fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
if (!quiet) {
|
|
60
|
+
console.log(`
|
|
61
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
62
|
+
║ SERTIVIBED SCANNER v0.1 ║
|
|
63
|
+
║ Evidence-first Security Screening ║
|
|
64
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
// Determine what to output
|
|
68
|
+
const skipMarkdown = options.jsonOnly || !options.report;
|
|
69
|
+
const skipJson = !options.json;
|
|
70
|
+
const config = {
|
|
71
|
+
targetPath,
|
|
72
|
+
verbose: options.verbose && !quiet,
|
|
73
|
+
outputMd: skipMarkdown ? undefined : options.output,
|
|
74
|
+
outputJson: skipJson ? undefined : options.json,
|
|
75
|
+
failOn: options.failOn?.toUpperCase(),
|
|
76
|
+
quiet,
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
// Run scan
|
|
80
|
+
if (!quiet) {
|
|
81
|
+
console.log(`📁 Scanning: ${targetPath}`);
|
|
82
|
+
console.log("");
|
|
83
|
+
}
|
|
84
|
+
const result = await (0, scan_1.scan)(config);
|
|
85
|
+
// Display summary
|
|
86
|
+
if (!quiet) {
|
|
87
|
+
console.log("═══════════════════════════════════════════════════════════════");
|
|
88
|
+
console.log(" SUMMARY ");
|
|
89
|
+
console.log("═══════════════════════════════════════════════════════════════");
|
|
90
|
+
console.log("");
|
|
91
|
+
console.log(` 🔴 Critical: ${result.summary.bySeverity.CRITICAL}`);
|
|
92
|
+
console.log(` 🟠 High: ${result.summary.bySeverity.HIGH}`);
|
|
93
|
+
console.log(` 🟡 Medium: ${result.summary.bySeverity.MEDIUM}`);
|
|
94
|
+
console.log(` 🟢 Low: ${result.summary.bySeverity.LOW}`);
|
|
95
|
+
console.log(` ⚪ Info: ${result.summary.bySeverity.INFO}`);
|
|
96
|
+
console.log("");
|
|
97
|
+
console.log(` Total issues: ${result.summary.total}`);
|
|
98
|
+
console.log(` Rules checked: ${result.rulesChecked.length}`);
|
|
99
|
+
console.log(` Scan time: ${result.meta.scanDurationMs}ms`);
|
|
100
|
+
console.log("");
|
|
101
|
+
}
|
|
102
|
+
// Generate outputs
|
|
103
|
+
if (config.outputJson) {
|
|
104
|
+
const jsonPath = (0, path_1.join)(outputDir, config.outputJson);
|
|
105
|
+
(0, fs_1.writeFileSync)(jsonPath, (0, json_1.generateJsonReport)(result));
|
|
106
|
+
if (!quiet)
|
|
107
|
+
console.log(`📄 JSON saved: ${config.outputJson}`);
|
|
108
|
+
}
|
|
109
|
+
if (config.outputMd) {
|
|
110
|
+
const mdPath = (0, path_1.join)(outputDir, config.outputMd);
|
|
111
|
+
(0, fs_1.writeFileSync)(mdPath, (0, md_1.generateMarkdownReport)(result));
|
|
112
|
+
if (!quiet)
|
|
113
|
+
console.log(`📋 Report saved: ${config.outputMd}`);
|
|
114
|
+
}
|
|
115
|
+
if (!quiet)
|
|
116
|
+
console.log("");
|
|
117
|
+
// Check fail threshold
|
|
118
|
+
if (config.failOn) {
|
|
119
|
+
const threshold = types_1.SEVERITY_ORDER[config.failOn];
|
|
120
|
+
const hasFailure = result.claims.some(c => types_1.SEVERITY_ORDER[c.severity] >= threshold);
|
|
121
|
+
if (hasFailure) {
|
|
122
|
+
if (!quiet)
|
|
123
|
+
console.log(`❌ Found issues at or above ${config.failOn} severity`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Final verdict
|
|
128
|
+
if (!quiet) {
|
|
129
|
+
const hasCritical = result.summary.bySeverity.CRITICAL > 0;
|
|
130
|
+
const hasHigh = result.summary.bySeverity.HIGH > 0;
|
|
131
|
+
if (hasCritical || hasHigh) {
|
|
132
|
+
console.log("⚠️ Issues found that should be addressed before production.");
|
|
133
|
+
}
|
|
134
|
+
else if (result.summary.total > 0) {
|
|
135
|
+
console.log("ℹ️ Some issues found. Review the report for details.");
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
console.log("✅ No security issues found!");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error(`❌ Scan failed: ${error}`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
// ============================================
|
|
148
|
+
// LIST RULES COMMAND
|
|
149
|
+
// ============================================
|
|
150
|
+
program
|
|
151
|
+
.command("rules")
|
|
152
|
+
.description("List all security rules")
|
|
153
|
+
.action(() => {
|
|
154
|
+
const { RULES } = require("./rules/index");
|
|
155
|
+
console.log("\nSertivibed Security Rules v0.1\n");
|
|
156
|
+
console.log("═══════════════════════════════════════════════════════════════\n");
|
|
157
|
+
for (const rule of RULES) {
|
|
158
|
+
console.log(`${rule.id}: ${rule.title}`);
|
|
159
|
+
console.log(` Category: ${rule.category} | Severity: ${rule.severity}`);
|
|
160
|
+
console.log(` ${rule.rationale}`);
|
|
161
|
+
console.log("");
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
// ============================================
|
|
165
|
+
// RUN
|
|
166
|
+
// ============================================
|
|
167
|
+
program.parse();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Reporter
|
|
3
|
+
*
|
|
4
|
+
* Eksporterer scan-resultat som JSON for maskinlesing
|
|
5
|
+
* eller videre prosessering (f.eks. LLM enrichment).
|
|
6
|
+
*/
|
|
7
|
+
import { ScanResult } from "../types";
|
|
8
|
+
export declare function generateJsonReport(result: ScanResult): string;
|
|
9
|
+
/**
|
|
10
|
+
* Kompakt versjon for API-kall / LLM
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateCompactJson(result: ScanResult): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JSON Reporter
|
|
4
|
+
*
|
|
5
|
+
* Eksporterer scan-resultat som JSON for maskinlesing
|
|
6
|
+
* eller videre prosessering (f.eks. LLM enrichment).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.generateJsonReport = generateJsonReport;
|
|
10
|
+
exports.generateCompactJson = generateCompactJson;
|
|
11
|
+
function generateJsonReport(result) {
|
|
12
|
+
return JSON.stringify(result, null, 2);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Kompakt versjon for API-kall / LLM
|
|
16
|
+
*/
|
|
17
|
+
function generateCompactJson(result) {
|
|
18
|
+
const compact = {
|
|
19
|
+
project: result.meta.projectName,
|
|
20
|
+
stack: result.meta.stack,
|
|
21
|
+
summary: {
|
|
22
|
+
total: result.summary.total,
|
|
23
|
+
critical: result.summary.bySeverity.CRITICAL,
|
|
24
|
+
high: result.summary.bySeverity.HIGH,
|
|
25
|
+
medium: result.summary.bySeverity.MEDIUM,
|
|
26
|
+
low: result.summary.bySeverity.LOW,
|
|
27
|
+
},
|
|
28
|
+
findings: result.claims.map(c => ({
|
|
29
|
+
id: c.ruleId,
|
|
30
|
+
title: c.title,
|
|
31
|
+
severity: c.severity,
|
|
32
|
+
impact: c.impactType,
|
|
33
|
+
statement: c.statement,
|
|
34
|
+
files: c.evidence.map(e => e.path),
|
|
35
|
+
fix: c.fixHint,
|
|
36
|
+
})),
|
|
37
|
+
};
|
|
38
|
+
return JSON.stringify(compact, null, 2);
|
|
39
|
+
}
|