wolverine-ai 4.6.0 → 4.6.1
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/bin/wolverine.js +14 -1
- package/package.json +1 -1
- package/src/core/runner.js +3 -1
- package/src/core/server-context.js +50 -2
package/bin/wolverine.js
CHANGED
|
@@ -70,7 +70,20 @@ if (args.includes("--init")) {
|
|
|
70
70
|
console.log(chalk.gray(` Database: ${ctx.database.type || "none"}${ctx.database.tables.length > 0 ? ` (${ctx.database.tables.length} tables)` : ""}`));
|
|
71
71
|
console.log(chalk.gray(` Env vars: ${ctx.envVars.length}`));
|
|
72
72
|
console.log(chalk.gray(` Files: ${ctx.structure.length}`));
|
|
73
|
-
console.log(chalk.gray(` Saved to: .wolverine/server-context.json
|
|
73
|
+
console.log(chalk.gray(` Saved to: .wolverine/server-context.json`));
|
|
74
|
+
if (ctx.warnings && ctx.warnings.length > 0) {
|
|
75
|
+
console.log(chalk.yellow(`\n ⚠️ Security warnings (${ctx.warnings.length}):`));
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
for (const w of ctx.warnings) {
|
|
78
|
+
const key = `${w.file}:${w.type}`;
|
|
79
|
+
if (seen.has(key)) continue;
|
|
80
|
+
seen.add(key);
|
|
81
|
+
console.log(chalk.yellow(` ${w.file}: ${w.label}`));
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
console.log(chalk.green(` No security warnings`));
|
|
85
|
+
}
|
|
86
|
+
console.log("");
|
|
74
87
|
process.exit(0);
|
|
75
88
|
}
|
|
76
89
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolverine-ai",
|
|
3
|
-
"version": "4.6.
|
|
3
|
+
"version": "4.6.1",
|
|
4
4
|
"description": "Self-healing Node.js server framework powered by AI. Catches crashes, diagnoses errors, generates fixes, verifies, and restarts — automatically.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/core/runner.js
CHANGED
|
@@ -213,11 +213,13 @@ class WolverineRunner {
|
|
|
213
213
|
|
|
214
214
|
// Scan server context (routes, DB, config, deps) for agent knowledge
|
|
215
215
|
try {
|
|
216
|
-
const { scan
|
|
216
|
+
const { scan } = require("./server-context");
|
|
217
217
|
const ctx = scan(this.cwd);
|
|
218
218
|
if (ctx) {
|
|
219
219
|
const routes = ctx.routes.reduce((s, r) => s + r.endpoints.length, 0);
|
|
220
|
+
const warns = (ctx.warnings || []).length;
|
|
220
221
|
console.log(chalk.gray(` 🗺️ Server context: ${routes} routes, ${ctx.structure.length} files, ${ctx.envVars.length} env vars`));
|
|
222
|
+
if (warns > 0) console.log(chalk.yellow(` ⚠️ ${warns} security warning(s) — run wolverine --init for details`));
|
|
221
223
|
}
|
|
222
224
|
} catch {}
|
|
223
225
|
|
|
@@ -168,12 +168,60 @@ function scan(cwd) {
|
|
|
168
168
|
scanForEnv(serverDir);
|
|
169
169
|
context.envVars = [...envVars].sort();
|
|
170
170
|
|
|
171
|
+
// 8. Security scan — detect dangerous patterns in server code
|
|
172
|
+
context.warnings = [];
|
|
173
|
+
const scanForDangers = (dir) => {
|
|
174
|
+
if (!fs.existsSync(dir)) return;
|
|
175
|
+
for (const file of _listFiles(dir, ".js")) {
|
|
176
|
+
try {
|
|
177
|
+
const code = fs.readFileSync(file, "utf-8");
|
|
178
|
+
const rel = path.relative(cwd, file);
|
|
179
|
+
// Hardcoded secrets
|
|
180
|
+
if (/['"][a-zA-Z0-9_-]{20,}['"]/.test(code)) {
|
|
181
|
+
const secretPatterns = [
|
|
182
|
+
{ regex: /sk-[a-zA-Z0-9]{20,}/g, label: "OpenAI API key" },
|
|
183
|
+
{ regex: /sk-ant-[a-zA-Z0-9_-]{20,}/g, label: "Anthropic API key" },
|
|
184
|
+
{ regex: /ghp_[a-zA-Z0-9]{36,}/g, label: "GitHub token" },
|
|
185
|
+
{ regex: /AKIA[0-9A-Z]{16}/g, label: "AWS access key" },
|
|
186
|
+
{ regex: /['"](?:password|secret|token|api_key|apikey|auth)\s*['"]?\s*[:=]\s*['"][^'"]{8,}['"]/gi, label: "Hardcoded credential" },
|
|
187
|
+
{ regex: /mongodb\+srv:\/\/[^\s'"]+/g, label: "MongoDB connection string" },
|
|
188
|
+
{ regex: /postgres:\/\/[^\s'"]+/g, label: "PostgreSQL connection string" },
|
|
189
|
+
{ regex: /redis:\/\/[^\s'"]+/g, label: "Redis connection string" },
|
|
190
|
+
];
|
|
191
|
+
for (const p of secretPatterns) {
|
|
192
|
+
if (p.regex.test(code)) {
|
|
193
|
+
context.warnings.push({ file: rel, type: "hardcoded_secret", label: p.label });
|
|
194
|
+
p.regex.lastIndex = 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// eval / Function constructor
|
|
199
|
+
if (/\beval\s*\(/.test(code)) context.warnings.push({ file: rel, type: "eval_usage", label: "eval() call" });
|
|
200
|
+
if (/new\s+Function\s*\(/.test(code)) context.warnings.push({ file: rel, type: "function_constructor", label: "new Function() call" });
|
|
201
|
+
// SQL injection risk (string concat in queries)
|
|
202
|
+
if (/\.(query|exec|prepare)\s*\(\s*['"`].*\$\{/.test(code) || /\.(query|exec)\s*\(\s*.*\+\s*(?:req|args|params|body)/i.test(code)) {
|
|
203
|
+
context.warnings.push({ file: rel, type: "sql_injection_risk", label: "String concatenation in SQL query" });
|
|
204
|
+
}
|
|
205
|
+
// Unvalidated redirect
|
|
206
|
+
if (/res\.redirect\s*\(\s*(?:req\.|args\.|params\.)/.test(code)) {
|
|
207
|
+
context.warnings.push({ file: rel, type: "open_redirect", label: "Unvalidated redirect from user input" });
|
|
208
|
+
}
|
|
209
|
+
} catch {}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
scanForDangers(serverDir);
|
|
213
|
+
|
|
214
|
+
// Sanitize: strip any actual secret values that might have leaked into context
|
|
215
|
+
const contextStr = JSON.stringify(context);
|
|
216
|
+
const { redact } = require("../security/secret-redactor");
|
|
217
|
+
const sanitized = JSON.parse(redact(contextStr));
|
|
218
|
+
|
|
171
219
|
// Save
|
|
172
220
|
const outPath = path.join(cwd, CONTEXT_PATH);
|
|
173
221
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
174
|
-
fs.writeFileSync(outPath, JSON.stringify(
|
|
222
|
+
fs.writeFileSync(outPath, JSON.stringify(sanitized, null, 2), "utf-8");
|
|
175
223
|
|
|
176
|
-
return
|
|
224
|
+
return sanitized;
|
|
177
225
|
}
|
|
178
226
|
|
|
179
227
|
/**
|