@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.
Files changed (3) hide show
  1. package/README.md +170 -0
  2. package/index.js +346 -0
  3. 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
+ [![npm version](https://img.shields.io/npm/v/@quantizelab/quantize-brain.svg)](https://www.npmjs.com/package/@quantizelab/quantize-brain)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
+ }