@quantizelab/quantize-brain 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 +170 -0
- package/index.js +346 -0
- package/package.json +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# quantize-brain
|
|
2
|
+
|
|
3
|
+
**Codebase Prompt Security Auditor CLI** by [Quantize Lab](https://www.quantizelab.dev)
|
|
4
|
+
|
|
5
|
+
Automatically crawls your entire codebase, extracts every system prompt, and audits each one for injection vulnerabilities, jailbreaks, and data exfiltration vectors using the Quantize Lab Security API.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@quantizelab/quantize-brain)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @quantizelab/quantize-brain
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or run directly without installing:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @quantizelab/quantize-brain audit --api-key YOUR_KEY
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### 1. Get your API key
|
|
29
|
+
|
|
30
|
+
Sign in at [quantizelab.dev](https://www.quantizelab.dev) → Profile → copy your **API Key**.
|
|
31
|
+
|
|
32
|
+
### 2. Initialize config (optional)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
quantize-brain init
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Creates a `quantize-brain.json` in your project root with sensible defaults for ignored directories and scanned file extensions.
|
|
39
|
+
|
|
40
|
+
### 3. Run an audit
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
quantize-brain audit --api-key sk_live_xxxxxxxxxxxx
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Authentication
|
|
49
|
+
|
|
50
|
+
You can supply your API key in three ways (highest priority first):
|
|
51
|
+
|
|
52
|
+
| Method | Example |
|
|
53
|
+
|---|---|
|
|
54
|
+
| CLI flag | `--api-key sk_live_xxx` or `-k sk_live_xxx` |
|
|
55
|
+
| `.env` file | `QUANTIZE_API_KEY=sk_live_xxx` |
|
|
56
|
+
| Environment variable | `export QUANTIZE_API_KEY=sk_live_xxx` |
|
|
57
|
+
|
|
58
|
+
### Using a `.env` file
|
|
59
|
+
|
|
60
|
+
Create a `.env` file in your project root:
|
|
61
|
+
|
|
62
|
+
```env
|
|
63
|
+
QUANTIZE_API_KEY=sk_live_xxxxxxxxxxxx
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Then just run:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
quantize-brain audit
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
No flag needed — the CLI reads `.env` automatically with zero external dependencies.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Commands
|
|
77
|
+
|
|
78
|
+
### `quantize-brain init`
|
|
79
|
+
|
|
80
|
+
Generates a `quantize-brain.json` configuration file in the current directory.
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"ignores": ["node_modules", ".git", ".next", "dist", "build"],
|
|
85
|
+
"extensions": [".js", ".ts", ".jsx", ".tsx", ".py", ".prompt", ".txt", ".md"],
|
|
86
|
+
"failOn": "high"
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `quantize-brain audit [options]`
|
|
91
|
+
|
|
92
|
+
Crawls the current workspace and audits all detected prompts.
|
|
93
|
+
|
|
94
|
+
| Option | Description |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `--api-key <key>`, `-k <key>` | Your Quantize Lab API key |
|
|
97
|
+
| `--fail-on <severity>` | Exit with code 1 if severity meets threshold (`critical`, `high`, `medium`, `low`) |
|
|
98
|
+
| `--host <url>` | Override the API host (default: `https://www.quantizelab.dev`) |
|
|
99
|
+
| `--help`, `-h` | Show help |
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## What Gets Detected
|
|
104
|
+
|
|
105
|
+
The CLI scans for prompt declarations using pattern matching across:
|
|
106
|
+
|
|
107
|
+
- **Variable assignments** — `systemPrompt = \`...\`` (JS/TS/Python)
|
|
108
|
+
- **Multi-line strings** — `system_prompt = """..."""`
|
|
109
|
+
- **Annotation blocks** — `// @quantize-prompt` or `// @decodes-prompt` followed by an assignment
|
|
110
|
+
- **Dedicated files** — `*.prompt`, `system_prompt.txt`, `system-prompt.txt`
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## CI/CD Integration
|
|
115
|
+
|
|
116
|
+
Use `--fail-on` to gate your builds on security findings:
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
# GitHub Actions example
|
|
120
|
+
- name: Audit prompts
|
|
121
|
+
run: npx @quantizelab/quantize-brain audit --fail-on high
|
|
122
|
+
env:
|
|
123
|
+
QUANTIZE_API_KEY: ${{ secrets.QUANTIZE_API_KEY }}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Exit codes:
|
|
127
|
+
- `0` — Clean, no findings at or above the threshold
|
|
128
|
+
- `1` — Findings found at or above `--fail-on` severity
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Example Output
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
🔍 Crawling codebase for system prompts...
|
|
136
|
+
Found 2 prompt(s). Starting security audit via Quantize Lab...
|
|
137
|
+
|
|
138
|
+
────────────────────────────────────────────────────────────────────────────────
|
|
139
|
+
📄 File: src/agent/system.ts:12
|
|
140
|
+
💡 Source: System Prompt Variable
|
|
141
|
+
Snippet: "You are a helpful assistant. Answer user questions truthfully..."
|
|
142
|
+
🤖 Auditing...
|
|
143
|
+
🛡️ Risk Score: 72/100
|
|
144
|
+
Findings:
|
|
145
|
+
- [HIGH] Instruction Override Susceptibility
|
|
146
|
+
Issue: Prompt lacks explicit boundary enforcement...
|
|
147
|
+
Fix: Add explicit role-lock instructions...
|
|
148
|
+
|
|
149
|
+
================================================================================
|
|
150
|
+
Audit complete.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Configuration File (`quantize-brain.json`)
|
|
156
|
+
|
|
157
|
+
| Field | Type | Description |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `ignores` | `string[]` | Directory/file names to skip during crawl |
|
|
160
|
+
| `extensions` | `string[]` | File extensions to scan |
|
|
161
|
+
| `failOn` | `string` | Default CI/CD failure threshold (`critical`, `high`, `medium`, `low`) |
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Links
|
|
166
|
+
|
|
167
|
+
- 🌐 [quantizelab.dev](https://www.quantizelab.dev)
|
|
168
|
+
- 📖 [Docs & API Reference](https://www.quantizelab.dev/docs/api)
|
|
169
|
+
- 🔧 [MCP Server](https://www.npmjs.com/package/@quantizelab/mcp-server)
|
|
170
|
+
- ⭐ [GitHub](https://github.com/thecodehaider/prompt-injection-scanner)
|
package/index.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
// CLI Help Text
|
|
8
|
+
const HELP_TEXT = `
|
|
9
|
+
quantize-brain — Quantize Lab Codebase Prompt Security Auditor
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
quantize-brain init Initialize a quantize-brain.json configuration file.
|
|
13
|
+
quantize-brain audit [options] Scan the current workspace for LLM system prompts and audit their security.
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--api-key, -k <key> Provide your Quantize Lab API Key (or set QUANTIZE_API_KEY env var).
|
|
17
|
+
--fail-on <severity> CI/CD mode: Exit with status 1 if findings meet or exceed severity level:
|
|
18
|
+
"critical", "high", "medium", or "low".
|
|
19
|
+
--host <url> Override Quantize Lab API host (default: https://www.quantizelab.dev).
|
|
20
|
+
--help, -h Show this help message.
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
// Default settings
|
|
24
|
+
const DEFAULT_IGNORES = [
|
|
25
|
+
"node_modules",
|
|
26
|
+
".git",
|
|
27
|
+
".next",
|
|
28
|
+
"dist",
|
|
29
|
+
"build",
|
|
30
|
+
"out",
|
|
31
|
+
".env",
|
|
32
|
+
"venv",
|
|
33
|
+
"env",
|
|
34
|
+
"target",
|
|
35
|
+
"bin"
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const DEFAULT_EXTENSIONS = [
|
|
39
|
+
".js",
|
|
40
|
+
".ts",
|
|
41
|
+
".jsx",
|
|
42
|
+
".tsx",
|
|
43
|
+
".py",
|
|
44
|
+
".prompt",
|
|
45
|
+
".txt",
|
|
46
|
+
".md",
|
|
47
|
+
".yaml",
|
|
48
|
+
".yml"
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// Helper to check files recursively
|
|
52
|
+
function getFiles(dir, ignores, extensions, fileList = []) {
|
|
53
|
+
const files = fs.readdirSync(dir);
|
|
54
|
+
|
|
55
|
+
for (const file of files) {
|
|
56
|
+
const filePath = path.join(dir, file);
|
|
57
|
+
const stat = fs.statSync(filePath);
|
|
58
|
+
|
|
59
|
+
const baseName = path.basename(filePath);
|
|
60
|
+
if (ignores.includes(baseName) || ignores.some(i => filePath.includes(path.sep + i + path.sep))) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (stat.isDirectory()) {
|
|
65
|
+
getFiles(filePath, ignores, extensions, fileList);
|
|
66
|
+
} else {
|
|
67
|
+
const ext = path.extname(filePath);
|
|
68
|
+
if (extensions.includes(ext)) {
|
|
69
|
+
fileList.push(filePath);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return fileList;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Regex rules to detect prompt declarations
|
|
77
|
+
const PROMPT_REGEXES = [
|
|
78
|
+
// JS/TS template literals and python multi-line strings:
|
|
79
|
+
// e.g. systemPrompt = `...` or system_prompt = """..."""
|
|
80
|
+
{
|
|
81
|
+
pattern: /(?:const|let|var|self\.)?(?:system_prompt|systemPrompt|sys_prompt|sysPrompt|system_instructions)\s*=\s*(?:"""|'''|`)([\s\S]*?)(?:"""|'''|`)/gi,
|
|
82
|
+
name: "System Prompt Variable"
|
|
83
|
+
},
|
|
84
|
+
// Standard single/double quoted strings
|
|
85
|
+
{
|
|
86
|
+
pattern: /(?:const|let|var|self\.)?(?:system_prompt|systemPrompt|sys_prompt|sysPrompt)\s*=\s*(?:"|')([\s\S]*?)(?:"|')/gi,
|
|
87
|
+
name: "System Prompt Short String"
|
|
88
|
+
},
|
|
89
|
+
// Special annotation block:
|
|
90
|
+
// @quantize-prompt or @decodes-prompt
|
|
91
|
+
{
|
|
92
|
+
pattern: /(?://|#)\s*@(?:quantize|decodes)-prompt\s*\r?\n(?:const|let|var|self\.)?\w*\s*=\s*(?:"""|'''|`|'|")([\s\S]*?)(?:"""|'''|`|'|")/gi,
|
|
93
|
+
name: "@quantize-prompt Annotation"
|
|
94
|
+
}
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// Parse prompts out of a file
|
|
98
|
+
function extractPrompts(filePath) {
|
|
99
|
+
const ext = path.extname(filePath);
|
|
100
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
101
|
+
const prompts = [];
|
|
102
|
+
|
|
103
|
+
// If it's a dedicated prompt file, treat the whole file as the prompt
|
|
104
|
+
if (ext === ".prompt" || filePath.endsWith("system_prompt.txt") || filePath.endsWith("system-prompt.txt")) {
|
|
105
|
+
prompts.push({
|
|
106
|
+
type: "Dedicated Prompt File",
|
|
107
|
+
content: content.trim(),
|
|
108
|
+
line: 1
|
|
109
|
+
});
|
|
110
|
+
return prompts;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Otherwise scan using regexes
|
|
114
|
+
for (const { pattern, name } of PROMPT_REGEXES) {
|
|
115
|
+
let match;
|
|
116
|
+
// Reset regex state
|
|
117
|
+
pattern.lastIndex = 0;
|
|
118
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
119
|
+
const promptText = match[1].trim();
|
|
120
|
+
if (promptText.length > 20) { // Avoid false positives of empty/short variables
|
|
121
|
+
// Find line number
|
|
122
|
+
const charIndex = match.index;
|
|
123
|
+
const line = content.substring(0, charIndex).split("\n").length;
|
|
124
|
+
prompts.push({
|
|
125
|
+
type: name,
|
|
126
|
+
content: promptText,
|
|
127
|
+
line
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return prompts;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Log error and exit
|
|
137
|
+
function fail(msg) {
|
|
138
|
+
console.error(`\x1b[31mError: ${msg}\x1b[0m`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Core execution
|
|
143
|
+
async function main() {
|
|
144
|
+
const command = process.argv[2];
|
|
145
|
+
|
|
146
|
+
if (!command || command === "--help" || command === "-h") {
|
|
147
|
+
console.log(HELP_TEXT);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (command === "init") {
|
|
152
|
+
const configPath = path.join(process.cwd(), "quantize-brain.json");
|
|
153
|
+
if (fs.existsSync(configPath)) {
|
|
154
|
+
console.log("\x1b[33mquantize-brain.json already exists in this directory.\x1b[0m");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const config = {
|
|
158
|
+
ignores: DEFAULT_IGNORES,
|
|
159
|
+
extensions: DEFAULT_EXTENSIONS,
|
|
160
|
+
failOn: "high"
|
|
161
|
+
};
|
|
162
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
163
|
+
console.log("\x1b[32mSuccessfully created quantize-brain.json config file!\x1b[0m");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (command !== "audit") {
|
|
168
|
+
console.log(`Unknown command: ${command}`);
|
|
169
|
+
console.log(HELP_TEXT);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Helper to load keys from local .env files
|
|
174
|
+
function loadLocalEnv() {
|
|
175
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
176
|
+
if (fs.existsSync(envPath)) {
|
|
177
|
+
try {
|
|
178
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
179
|
+
const lines = content.split(/\r?\n/);
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
const trimmed = line.trim();
|
|
182
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
183
|
+
const firstEqual = trimmed.indexOf("=");
|
|
184
|
+
if (firstEqual !== -1) {
|
|
185
|
+
const key = trimmed.substring(0, firstEqual).trim();
|
|
186
|
+
const val = trimmed.substring(firstEqual + 1).trim();
|
|
187
|
+
const cleanVal = val.replace(/^["']|["']$/g, "");
|
|
188
|
+
if (key === "QUANTIZE_API_KEY") {
|
|
189
|
+
process.env[key] = cleanVal;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch (e) {
|
|
195
|
+
// Silently skip if error reading local .env
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Parse options
|
|
201
|
+
loadLocalEnv();
|
|
202
|
+
const args = process.argv.slice(3);
|
|
203
|
+
let apiKey = process.env.QUANTIZE_API_KEY;
|
|
204
|
+
let failOn = null;
|
|
205
|
+
let host = "https://www.quantizelab.dev";
|
|
206
|
+
|
|
207
|
+
// Load local config if exists
|
|
208
|
+
const configPath = path.join(process.cwd(), "quantize-brain.json");
|
|
209
|
+
let config = {};
|
|
210
|
+
if (fs.existsSync(configPath)) {
|
|
211
|
+
try {
|
|
212
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
213
|
+
} catch (e) {
|
|
214
|
+
console.warn("Warning: Could not parse quantize-brain.json. Using defaults.");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const ignores = config.ignores || DEFAULT_IGNORES;
|
|
219
|
+
const extensions = config.extensions || DEFAULT_EXTENSIONS;
|
|
220
|
+
failOn = config.failOn || null;
|
|
221
|
+
|
|
222
|
+
for (let i = 0; i < args.length; i++) {
|
|
223
|
+
if ((args[i] === "--api-key" || args[i] === "-k") && args[i + 1]) {
|
|
224
|
+
apiKey = args[i + 1];
|
|
225
|
+
i++;
|
|
226
|
+
} else if (args[i].startsWith("--api-key=")) {
|
|
227
|
+
apiKey = args[i].split("=")[1];
|
|
228
|
+
} else if (args[i] === "--fail-on" && args[i + 1]) {
|
|
229
|
+
failOn = args[i + 1];
|
|
230
|
+
i++;
|
|
231
|
+
} else if (args[i].startsWith("--fail-on=")) {
|
|
232
|
+
failOn = args[i].split("=")[1];
|
|
233
|
+
} else if (args[i] === "--host" && args[i + 1]) {
|
|
234
|
+
host = args[i + 1];
|
|
235
|
+
i++;
|
|
236
|
+
} else if (args[i].startsWith("--host=")) {
|
|
237
|
+
host = args[i].split("=")[1];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!apiKey) {
|
|
242
|
+
fail("No API key provided. Use --api-key <key> or set the QUANTIZE_API_KEY environment variable.");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (host.endsWith("/")) {
|
|
246
|
+
host = host.slice(0, -1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log("\x1b[36m🔍 Crawling codebase for system prompts...\x1b[0m");
|
|
250
|
+
const allFiles = getFiles(process.cwd(), ignores, extensions);
|
|
251
|
+
const foundPrompts = [];
|
|
252
|
+
|
|
253
|
+
for (const file of allFiles) {
|
|
254
|
+
const prompts = extractPrompts(file);
|
|
255
|
+
for (const p of prompts) {
|
|
256
|
+
foundPrompts.push({
|
|
257
|
+
file: path.relative(process.cwd(), file),
|
|
258
|
+
...p
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (foundPrompts.length === 0) {
|
|
264
|
+
console.log("\x1b[32m✨ No prompts detected in the target directory!\x1b[0m");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
console.log(`\x1b[32mFound ${foundPrompts.length} prompt(s). Starting security audit via Quantize Lab...\x1b[0m\n`);
|
|
269
|
+
|
|
270
|
+
let maxSeverityLevel = 0; // 0 = None, 1 = Low, 2 = Medium, 3 = High, 4 = Critical
|
|
271
|
+
const severityMap = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
272
|
+
|
|
273
|
+
for (const item of foundPrompts) {
|
|
274
|
+
console.log(`--------------------------------------------------------------------------------`);
|
|
275
|
+
console.log(`📄 \x1b[1mFile:\x1b[0m ${item.file}:${item.line}`);
|
|
276
|
+
console.log(`💡 \x1b[1mSource:\x1b[0m ${item.type}`);
|
|
277
|
+
console.log(`Snippet: "${item.content.substring(0, 80).replace(/\r?\n/g, " ")}..."`);
|
|
278
|
+
console.log(`🤖 \x1b[35mAuditing...\x1b[0m`);
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const response = await fetch(`${host}/api/v1/scan`, {
|
|
282
|
+
method: "POST",
|
|
283
|
+
headers: {
|
|
284
|
+
"Content-Type": "application/json",
|
|
285
|
+
"x-api-key": apiKey
|
|
286
|
+
},
|
|
287
|
+
body: JSON.stringify({ prompt: item.content })
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (!response.ok) {
|
|
291
|
+
const errJson = await response.json().catch(() => ({}));
|
|
292
|
+
console.log(`\x1b[31m⚠️ Audit API failed: ${errJson.error || response.statusText}\x1b[0m`);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const report = await response.json();
|
|
297
|
+
const score = report.risk_score || 0;
|
|
298
|
+
let scoreColor = "\x1b[32m"; // Green
|
|
299
|
+
if (score >= 40) scoreColor = "\x1b[33m"; // Yellow
|
|
300
|
+
if (score >= 70) scoreColor = "\x1b[31m"; // Red
|
|
301
|
+
|
|
302
|
+
console.log(`🛡️ \x1b[1mRisk Score:\x1b[0m ${scoreColor}${score}/100\x1b[0m`);
|
|
303
|
+
|
|
304
|
+
if (report.findings && report.findings.length > 0) {
|
|
305
|
+
console.log(`\x1b[1mFindings:\x1b[0m`);
|
|
306
|
+
for (const finding of report.findings) {
|
|
307
|
+
const sev = (finding.severity || "low").toLowerCase();
|
|
308
|
+
const sevLevel = severityMap[sev] || 1;
|
|
309
|
+
if (sevLevel > maxSeverityLevel) {
|
|
310
|
+
maxSeverityLevel = sevLevel;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let sevColor = "\x1b[34m"; // Low - Blue
|
|
314
|
+
if (sev === "medium") sevColor = "\x1b[33m";
|
|
315
|
+
if (sev === "high" || sev === "critical") sevColor = "\x1b[31m";
|
|
316
|
+
|
|
317
|
+
console.log(` - [${sevColor}${finding.severity.toUpperCase()}\x1b[0m] ${finding.category}`);
|
|
318
|
+
console.log(` \x1b[2mIssue:\x1b[0m ${finding.description}`);
|
|
319
|
+
console.log(` \x1b[2mFix:\x1b[0m ${finding.fix}`);
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
console.log(`\x1b[32m✨ Clean! No critical vulnerabilities detected.\x1b[0m`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
} catch (e) {
|
|
326
|
+
console.log(`\x1b[31m⚠️ Error connecting to audit service: ${e.message}\x1b[0m`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(`================================================================================`);
|
|
331
|
+
console.log(`\x1b[32mAudit complete.\x1b[0m`);
|
|
332
|
+
|
|
333
|
+
// Handle CI/CD fail code
|
|
334
|
+
if (failOn) {
|
|
335
|
+
const failThreshold = severityMap[failOn.toLowerCase()];
|
|
336
|
+
if (failThreshold && maxSeverityLevel >= failThreshold) {
|
|
337
|
+
console.error(`\n\x1b[31m❌ CI/CD Build Failure: Found security vulnerabilities of level "${failOn}" or higher.\x1b[0m`);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
main().catch(e => {
|
|
344
|
+
console.error(e);
|
|
345
|
+
process.exit(1);
|
|
346
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quantizelab/quantize-brain",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Codebase prompt crawler and security scanner CLI by Quantize Lab",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"quantize-brain": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js"
|
|
12
|
+
}
|
|
13
|
+
}
|