wolverine-ai 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/PLATFORM.md +442 -0
- package/README.md +475 -0
- package/SERVER_BEST_PRACTICES.md +62 -0
- package/TELEMETRY.md +108 -0
- package/bin/wolverine.js +95 -0
- package/examples/01-basic-typo.js +31 -0
- package/examples/02-multi-file/routes/users.js +15 -0
- package/examples/02-multi-file/server.js +25 -0
- package/examples/03-syntax-error.js +23 -0
- package/examples/04-secret-leak.js +14 -0
- package/examples/05-expired-key.js +27 -0
- package/examples/06-json-config/config.json +13 -0
- package/examples/06-json-config/server.js +28 -0
- package/examples/07-rate-limit-loop.js +11 -0
- package/examples/08-sandbox-escape.js +20 -0
- package/examples/buggy-server.js +39 -0
- package/examples/demos/01-basic-typo/index.js +20 -0
- package/examples/demos/01-basic-typo/routes/api.js +13 -0
- package/examples/demos/01-basic-typo/routes/health.js +4 -0
- package/examples/demos/02-multi-file/index.js +24 -0
- package/examples/demos/02-multi-file/routes/api.js +13 -0
- package/examples/demos/02-multi-file/routes/health.js +4 -0
- package/examples/demos/03-syntax-error/index.js +18 -0
- package/examples/demos/04-secret-leak/index.js +16 -0
- package/examples/demos/05-expired-key/index.js +21 -0
- package/examples/demos/06-json-config/config.json +9 -0
- package/examples/demos/06-json-config/index.js +20 -0
- package/examples/demos/07-null-crash/index.js +16 -0
- package/examples/run-demo.js +110 -0
- package/package.json +67 -0
- package/server/config/settings.json +62 -0
- package/server/index.js +33 -0
- package/server/routes/api.js +12 -0
- package/server/routes/health.js +16 -0
- package/server/routes/time.js +12 -0
- package/src/agent/agent-engine.js +727 -0
- package/src/agent/goal-loop.js +140 -0
- package/src/agent/research-agent.js +120 -0
- package/src/agent/sub-agents.js +176 -0
- package/src/backup/backup-manager.js +321 -0
- package/src/brain/brain.js +315 -0
- package/src/brain/embedder.js +131 -0
- package/src/brain/function-map.js +263 -0
- package/src/brain/vector-store.js +267 -0
- package/src/core/ai-client.js +387 -0
- package/src/core/cluster-manager.js +144 -0
- package/src/core/config.js +89 -0
- package/src/core/error-parser.js +87 -0
- package/src/core/health-monitor.js +129 -0
- package/src/core/models.js +132 -0
- package/src/core/patcher.js +55 -0
- package/src/core/runner.js +464 -0
- package/src/core/system-info.js +141 -0
- package/src/core/verifier.js +146 -0
- package/src/core/wolverine.js +290 -0
- package/src/dashboard/server.js +1332 -0
- package/src/index.js +94 -0
- package/src/logger/event-logger.js +237 -0
- package/src/logger/pricing.js +96 -0
- package/src/logger/repair-history.js +109 -0
- package/src/logger/token-tracker.js +277 -0
- package/src/mcp/mcp-client.js +224 -0
- package/src/mcp/mcp-registry.js +228 -0
- package/src/mcp/mcp-security.js +152 -0
- package/src/monitor/perf-monitor.js +300 -0
- package/src/monitor/process-monitor.js +231 -0
- package/src/monitor/route-prober.js +191 -0
- package/src/notifications/notifier.js +227 -0
- package/src/platform/heartbeat.js +93 -0
- package/src/platform/queue.js +53 -0
- package/src/platform/register.js +64 -0
- package/src/platform/telemetry.js +76 -0
- package/src/security/admin-auth.js +150 -0
- package/src/security/injection-detector.js +174 -0
- package/src/security/rate-limiter.js +152 -0
- package/src/security/sandbox.js +128 -0
- package/src/security/secret-redactor.js +217 -0
- package/src/skills/skill-registry.js +129 -0
- package/src/skills/sql.js +375 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL Skill — safe database interface + SQL injection prevention.
|
|
3
|
+
*
|
|
4
|
+
* Two roles:
|
|
5
|
+
* 1. DATABASE INTERFACE — provides a safe query API for the server
|
|
6
|
+
* - Parameterized queries only (no string concatenation ever)
|
|
7
|
+
* - Connection pooling
|
|
8
|
+
* - Supports SQLite (built-in), PostgreSQL, MySQL via adapters
|
|
9
|
+
* - Auto-close on process exit
|
|
10
|
+
*
|
|
11
|
+
* 2. INJECTION PREVENTION — middleware that scans all incoming requests
|
|
12
|
+
* - Detects SQL injection patterns in query params, body, headers
|
|
13
|
+
* - Blocks malicious requests before they reach route handlers
|
|
14
|
+
* - Logs attempts to wolverine event system
|
|
15
|
+
*
|
|
16
|
+
* Usage in server/:
|
|
17
|
+
* const { db, sqlGuard } = require("../src/skills/sql");
|
|
18
|
+
* app.use(sqlGuard()); // protect all routes
|
|
19
|
+
* const users = await db.all("SELECT * FROM users WHERE id = ?", [userId]);
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const chalk = require("chalk");
|
|
23
|
+
|
|
24
|
+
// ── SQL Injection Detection ──────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const SQLI_PATTERNS = [
|
|
27
|
+
// Classic injection
|
|
28
|
+
/('\s*(OR|AND)\s+')/i,
|
|
29
|
+
/('\s*;\s*(DROP|DELETE|UPDATE|INSERT|ALTER|EXEC|UNION))/i,
|
|
30
|
+
/(--\s*$|#\s*$)/m,
|
|
31
|
+
/(\b(UNION)\s+(ALL\s+)?SELECT\b)/i,
|
|
32
|
+
// Tautologies
|
|
33
|
+
/('\s*=\s*')/,
|
|
34
|
+
/(\b1\s*=\s*1\b)/,
|
|
35
|
+
/(\bOR\s+1\s*=\s*1\b)/i,
|
|
36
|
+
// Stacked queries
|
|
37
|
+
/(;\s*(DROP|DELETE|TRUNCATE|ALTER|CREATE|INSERT|UPDATE)\s)/i,
|
|
38
|
+
// Comment-based bypass
|
|
39
|
+
/(\/\*.*\*\/)/,
|
|
40
|
+
// Hex/char encoding tricks
|
|
41
|
+
/(0x[0-9a-f]{4,})/i,
|
|
42
|
+
/(CHAR\s*\(\s*\d+\s*(,\s*\d+\s*)*\))/i,
|
|
43
|
+
// SLEEP/BENCHMARK (timing attacks)
|
|
44
|
+
/(SLEEP\s*\(\s*\d+\s*\))/i,
|
|
45
|
+
/(BENCHMARK\s*\()/i,
|
|
46
|
+
// Information schema probing
|
|
47
|
+
/(INFORMATION_SCHEMA)/i,
|
|
48
|
+
/(sys\.objects|sysobjects|syscolumns)/i,
|
|
49
|
+
// Load file / into outfile
|
|
50
|
+
/(LOAD_FILE|INTO\s+OUTFILE|INTO\s+DUMPFILE)/i,
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check a string for SQL injection patterns.
|
|
55
|
+
* Returns { safe: boolean, patterns: string[] }
|
|
56
|
+
*/
|
|
57
|
+
function scanForInjection(input) {
|
|
58
|
+
if (!input || typeof input !== "string") return { safe: true, patterns: [] };
|
|
59
|
+
|
|
60
|
+
const found = [];
|
|
61
|
+
for (const pattern of SQLI_PATTERNS) {
|
|
62
|
+
if (pattern.test(input)) {
|
|
63
|
+
found.push(pattern.source.slice(0, 40));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { safe: found.length === 0, patterns: found };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Recursively scan an object (body, query, params) for injection.
|
|
72
|
+
*/
|
|
73
|
+
function deepScan(obj, path = "") {
|
|
74
|
+
const results = [];
|
|
75
|
+
if (!obj) return results;
|
|
76
|
+
|
|
77
|
+
if (typeof obj === "string") {
|
|
78
|
+
const scan = scanForInjection(obj);
|
|
79
|
+
if (!scan.safe) results.push({ path: path || "value", patterns: scan.patterns, value: obj.slice(0, 100) });
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (Array.isArray(obj)) {
|
|
84
|
+
obj.forEach((item, i) => results.push(...deepScan(item, `${path}[${i}]`)));
|
|
85
|
+
return results;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof obj === "object") {
|
|
89
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
90
|
+
results.push(...deepScan(val, path ? `${path}.${key}` : key));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Express middleware — blocks requests with SQL injection patterns.
|
|
99
|
+
*
|
|
100
|
+
* @param {object} options
|
|
101
|
+
* @param {object} options.logger — wolverine EventLogger (optional)
|
|
102
|
+
* @param {boolean} options.blockMode — true = block request, false = log only (default: true)
|
|
103
|
+
*/
|
|
104
|
+
function sqlGuard(options = {}) {
|
|
105
|
+
const logger = options.logger || null;
|
|
106
|
+
const blockMode = options.blockMode !== false;
|
|
107
|
+
|
|
108
|
+
return (req, res, next) => {
|
|
109
|
+
const threats = [];
|
|
110
|
+
|
|
111
|
+
// Scan query params
|
|
112
|
+
threats.push(...deepScan(req.query, "query"));
|
|
113
|
+
|
|
114
|
+
// Scan body
|
|
115
|
+
if (req.body) threats.push(...deepScan(req.body, "body"));
|
|
116
|
+
|
|
117
|
+
// Scan URL params
|
|
118
|
+
if (req.params) threats.push(...deepScan(req.params, "params"));
|
|
119
|
+
|
|
120
|
+
// Scan select headers (user-agent, referer, cookie values)
|
|
121
|
+
const suspectHeaders = ["user-agent", "referer", "x-forwarded-for"];
|
|
122
|
+
for (const h of suspectHeaders) {
|
|
123
|
+
if (req.headers[h]) threats.push(...deepScan(req.headers[h], `header.${h}`));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (threats.length === 0) {
|
|
127
|
+
return next();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// SQL injection detected
|
|
131
|
+
const summary = threats.map(t => `${t.path}: ${t.patterns.join(", ")}`).join(" | ");
|
|
132
|
+
console.log(chalk.red(` 🛡️ SQL INJECTION BLOCKED: ${req.method} ${req.path} — ${summary}`));
|
|
133
|
+
|
|
134
|
+
if (logger) {
|
|
135
|
+
logger.critical("security.sqli_blocked", `SQL injection blocked: ${req.method} ${req.path}`, {
|
|
136
|
+
method: req.method,
|
|
137
|
+
path: req.path,
|
|
138
|
+
threats: threats.map(t => ({ path: t.path, value: t.value })),
|
|
139
|
+
ip: req.ip || req.socket.remoteAddress,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (blockMode) {
|
|
144
|
+
res.status(403).json({ error: "Forbidden", message: "Potentially malicious input detected." });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Log-only mode — let request through but flag it
|
|
149
|
+
req._sqliWarning = threats;
|
|
150
|
+
next();
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Safe Database Interface ──────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Cluster-safe database — zero waits, zero blocking.
|
|
158
|
+
*
|
|
159
|
+
* Architecture:
|
|
160
|
+
* - Reads: every worker has its own read-only connection (concurrent, no locks)
|
|
161
|
+
* - Writes: single dedicated write connection with a synchronous FIFO queue
|
|
162
|
+
*
|
|
163
|
+
* How the write queue works:
|
|
164
|
+
* - Writes are synchronous in better-sqlite3 (microseconds each)
|
|
165
|
+
* - Queue is just an array that drains in a microtask
|
|
166
|
+
* - No busy_timeout, no delays, no IPC — writes are fast, they just can't overlap
|
|
167
|
+
* - Queue forms naturally under load, drains instantly when the current write finishes
|
|
168
|
+
*
|
|
169
|
+
* SQLite specifics:
|
|
170
|
+
* - WAL mode: readers never block writers, writers never block readers
|
|
171
|
+
* - No busy_timeout needed — only one connection writes, so no contention
|
|
172
|
+
* - NORMAL sync: safe with WAL, 1 fsync per transaction instead of 2
|
|
173
|
+
*/
|
|
174
|
+
class SafeDB {
|
|
175
|
+
constructor(options = {}) {
|
|
176
|
+
this.type = options.type || "sqlite";
|
|
177
|
+
this.path = options.path || ":memory:";
|
|
178
|
+
this._reader = null; // read-only connection (concurrent)
|
|
179
|
+
this._writer = null; // single write connection (serialized)
|
|
180
|
+
this._closed = false;
|
|
181
|
+
this._queue = []; // pending write operations
|
|
182
|
+
this._draining = false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async connect() {
|
|
186
|
+
if (this.type === "sqlite") {
|
|
187
|
+
try {
|
|
188
|
+
const Database = require("better-sqlite3");
|
|
189
|
+
|
|
190
|
+
// Read connection — used by get() and all()
|
|
191
|
+
this._reader = new Database(this.path, { readonly: false });
|
|
192
|
+
this._reader.pragma("journal_mode = WAL");
|
|
193
|
+
this._reader.pragma("foreign_keys = ON");
|
|
194
|
+
this._reader.pragma("synchronous = NORMAL");
|
|
195
|
+
this._reader.pragma("cache_size = -20000");
|
|
196
|
+
|
|
197
|
+
// Write connection — only used by run(), exec(), transaction()
|
|
198
|
+
// Separate connection means reads never wait for writes
|
|
199
|
+
this._writer = new Database(this.path);
|
|
200
|
+
this._writer.pragma("journal_mode = WAL");
|
|
201
|
+
this._writer.pragma("foreign_keys = ON");
|
|
202
|
+
this._writer.pragma("synchronous = NORMAL");
|
|
203
|
+
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (err.code === "MODULE_NOT_FOUND") {
|
|
206
|
+
throw new Error("Install better-sqlite3: npm install better-sqlite3");
|
|
207
|
+
}
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
} else if (this.type === "custom") {
|
|
211
|
+
// User manages their own connection
|
|
212
|
+
} else {
|
|
213
|
+
throw new Error(`Unsupported DB type: ${this.type}. Use "sqlite" or pass a custom driver.`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
process.on("exit", () => this.close());
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Write query (INSERT, UPDATE, DELETE, CREATE).
|
|
221
|
+
* Queued and executed in order. Returns a promise that resolves with the result.
|
|
222
|
+
*/
|
|
223
|
+
run(sql, params = []) {
|
|
224
|
+
this._assertOpen();
|
|
225
|
+
this._assertSafe(sql);
|
|
226
|
+
return this._enqueueWrite(() => this._writer.prepare(sql).run(...params));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Read one row. Direct on the read connection — never waits.
|
|
231
|
+
*/
|
|
232
|
+
get(sql, params = []) {
|
|
233
|
+
this._assertOpen();
|
|
234
|
+
this._assertSafe(sql);
|
|
235
|
+
return this._reader.prepare(sql).get(...params);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Read all rows. Direct on the read connection — never waits.
|
|
240
|
+
*/
|
|
241
|
+
all(sql, params = []) {
|
|
242
|
+
this._assertOpen();
|
|
243
|
+
this._assertSafe(sql);
|
|
244
|
+
return this._reader.prepare(sql).all(...params);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Raw SQL execution (migrations/schema). Queued through the writer.
|
|
249
|
+
*/
|
|
250
|
+
exec(sql) {
|
|
251
|
+
this._assertOpen();
|
|
252
|
+
return this._enqueueWrite(() => this._writer.exec(sql));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Atomic transaction. All writes inside fn run as one unit.
|
|
257
|
+
* Queued as a single operation — no other writes can interleave.
|
|
258
|
+
*/
|
|
259
|
+
transaction(fn) {
|
|
260
|
+
this._assertOpen();
|
|
261
|
+
return this._enqueueWrite(() => {
|
|
262
|
+
const txn = this._writer.transaction(() => fn(this._writerProxy()));
|
|
263
|
+
return txn();
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
close() {
|
|
268
|
+
if (this._closed) return;
|
|
269
|
+
this._closed = true;
|
|
270
|
+
try {
|
|
271
|
+
if (this._writer) {
|
|
272
|
+
try { this._writer.pragma("wal_checkpoint(TRUNCATE)"); } catch {}
|
|
273
|
+
this._writer.close();
|
|
274
|
+
}
|
|
275
|
+
if (this._reader) this._reader.close();
|
|
276
|
+
} catch {}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
_assertOpen() {
|
|
280
|
+
if (this._closed || (!this._reader && !this._writer)) {
|
|
281
|
+
throw new Error("Database not connected. Call db.connect() first.");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
_assertSafe(sql) {
|
|
286
|
+
if (/'\s*\+\s*/.test(sql) || /`\$\{/.test(sql)) {
|
|
287
|
+
throw new Error("UNSAFE: SQL appears to use string concatenation. Use parameterized queries (?) instead.");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Write queue — FIFO, no delays, drains synchronously.
|
|
293
|
+
* Each write is microseconds. Queue only forms under concurrent write pressure.
|
|
294
|
+
* Drains completely in a single microtask when the current write finishes.
|
|
295
|
+
*/
|
|
296
|
+
_enqueueWrite(fn) {
|
|
297
|
+
return new Promise((resolve, reject) => {
|
|
298
|
+
this._queue.push({ fn, resolve, reject });
|
|
299
|
+
if (!this._draining) this._drain();
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
_drain() {
|
|
304
|
+
this._draining = true;
|
|
305
|
+
while (this._queue.length > 0) {
|
|
306
|
+
const { fn, resolve, reject } = this._queue.shift();
|
|
307
|
+
try {
|
|
308
|
+
resolve(fn());
|
|
309
|
+
} catch (err) {
|
|
310
|
+
reject(err);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
this._draining = false;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Proxy for use inside transactions — writes go directly to writer (already locked).
|
|
318
|
+
*/
|
|
319
|
+
_writerProxy() {
|
|
320
|
+
const writer = this._writer;
|
|
321
|
+
return {
|
|
322
|
+
run: (sql, params = []) => writer.prepare(sql).run(...params),
|
|
323
|
+
exec: (sql) => writer.exec(sql),
|
|
324
|
+
get: (sql, params = []) => writer.prepare(sql).get(...params),
|
|
325
|
+
all: (sql, params = []) => writer.prepare(sql).all(...params),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ── Skill Metadata (for SkillRegistry discovery) ──
|
|
331
|
+
|
|
332
|
+
const SKILL_NAME = "sql";
|
|
333
|
+
const SKILL_DESCRIPTION = "SQL database interface with injection prevention. Provides sqlGuard() middleware to block SQL injection on all endpoints, and SafeDB class for parameterized-only database queries.";
|
|
334
|
+
const SKILL_KEYWORDS = ["sql", "database", "db", "query", "injection", "sqlite", "postgres", "mysql", "select", "insert", "update", "delete", "table", "schema", "migration", "parameterized"];
|
|
335
|
+
const SKILL_USAGE = `// Protect all routes from SQL injection
|
|
336
|
+
const { sqlGuard } = require("../src/skills/sql");
|
|
337
|
+
app.use(sqlGuard({ logger: wolverineLogger }));
|
|
338
|
+
|
|
339
|
+
// Cluster-safe database (each worker gets its own connection)
|
|
340
|
+
const { SafeDB } = require("../src/skills/sql");
|
|
341
|
+
const db = new SafeDB({ type: "sqlite", path: "./server/data.db" });
|
|
342
|
+
await db.connect(); // WAL mode, busy_timeout=5s, write serialization
|
|
343
|
+
|
|
344
|
+
// Reads (concurrent across workers)
|
|
345
|
+
const users = db.all("SELECT * FROM users WHERE role = ?", ["admin"]);
|
|
346
|
+
|
|
347
|
+
// Writes (serialized — no corruption)
|
|
348
|
+
db.run("INSERT INTO users (name, role) VALUES (?, ?)", ["Alice", "admin"]);
|
|
349
|
+
|
|
350
|
+
// Batch writes (atomic transaction, single lock)
|
|
351
|
+
db.transaction((tx) => {
|
|
352
|
+
tx.run("INSERT INTO orders (user_id, total) VALUES (?, ?)", [1, 99.99]);
|
|
353
|
+
tx.run("UPDATE users SET order_count = order_count + 1 WHERE id = ?", [1]);
|
|
354
|
+
});`;
|
|
355
|
+
|
|
356
|
+
// ── Exports ──
|
|
357
|
+
|
|
358
|
+
module.exports = {
|
|
359
|
+
// Skill metadata
|
|
360
|
+
SKILL_NAME,
|
|
361
|
+
SKILL_DESCRIPTION,
|
|
362
|
+
SKILL_KEYWORDS,
|
|
363
|
+
SKILL_USAGE,
|
|
364
|
+
|
|
365
|
+
// Middleware
|
|
366
|
+
sqlGuard,
|
|
367
|
+
scanForInjection,
|
|
368
|
+
deepScan,
|
|
369
|
+
|
|
370
|
+
// Database
|
|
371
|
+
SafeDB,
|
|
372
|
+
|
|
373
|
+
// Pattern list
|
|
374
|
+
SQLI_PATTERNS,
|
|
375
|
+
};
|