@prajwolkc/stk 0.6.1 → 0.7.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/dist/commands/brain.js +36 -3
- package/dist/mcp/server.js +243 -1
- package/dist/services/brain.d.ts +19 -0
- package/dist/services/brain.js +76 -0
- package/dist/services/metrics.d.ts +35 -0
- package/dist/services/metrics.js +78 -0
- package/dist/services/security.d.ts +19 -0
- package/dist/services/security.js +194 -0
- package/package.json +2 -1
- package/src/data/seed-patterns.json +1802 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { resolve, join } from "path";
|
|
4
|
+
/** Check for exposed secrets in .env files */
|
|
5
|
+
export function checkSecrets() {
|
|
6
|
+
const findings = [];
|
|
7
|
+
const envFiles = [".env", ".env.local", ".env.production", ".env.staging"];
|
|
8
|
+
// Check if .gitignore exists and covers env files
|
|
9
|
+
let gitignoreContent = "";
|
|
10
|
+
if (existsSync(".gitignore")) {
|
|
11
|
+
gitignoreContent = readFileSync(".gitignore", "utf-8");
|
|
12
|
+
}
|
|
13
|
+
for (const file of envFiles) {
|
|
14
|
+
if (!existsSync(file))
|
|
15
|
+
continue;
|
|
16
|
+
if (!gitignoreContent.includes(file) && !gitignoreContent.includes(".env*") && !gitignoreContent.includes(".env")) {
|
|
17
|
+
findings.push({
|
|
18
|
+
check: "secrets",
|
|
19
|
+
level: "critical",
|
|
20
|
+
message: `${file} is NOT in .gitignore — secrets may be committed`,
|
|
21
|
+
file,
|
|
22
|
+
fix: `Add "${file}" to .gitignore`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const content = readFileSync(file, "utf-8");
|
|
26
|
+
const lines = content.split("\n");
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
const [key, ...rest] = line.split("=");
|
|
29
|
+
const value = rest.join("=").trim();
|
|
30
|
+
if (!key || !value || key.startsWith("#"))
|
|
31
|
+
continue;
|
|
32
|
+
// Check for known secret patterns
|
|
33
|
+
if (value.startsWith("AKIA") && key.includes("AWS")) {
|
|
34
|
+
findings.push({ check: "secrets", level: "critical", message: `AWS access key found in ${file}`, file, fix: "Use environment variables or secrets manager" });
|
|
35
|
+
}
|
|
36
|
+
if (value.startsWith("sk_live_")) {
|
|
37
|
+
findings.push({ check: "secrets", level: "critical", message: `Live Stripe secret key in ${file}`, file, fix: "Use sk_test_ for development" });
|
|
38
|
+
}
|
|
39
|
+
if (key.includes("JWT_SECRET") && value.length < 32) {
|
|
40
|
+
findings.push({ check: "secrets", level: "warning", message: `JWT_SECRET is short (${value.length} chars) — use at least 32`, file, fix: "Generate a longer secret: openssl rand -hex 32" });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return findings;
|
|
45
|
+
}
|
|
46
|
+
/** Check for outdated/vulnerable dependencies */
|
|
47
|
+
export function checkDeps() {
|
|
48
|
+
const findings = [];
|
|
49
|
+
const pkgPaths = ["package.json", "node-backend/package.json", "frontend/package.json", "backend/package.json"];
|
|
50
|
+
for (const pkg of pkgPaths) {
|
|
51
|
+
if (!existsSync(pkg))
|
|
52
|
+
continue;
|
|
53
|
+
const dir = pkg === "package.json" ? "." : pkg.replace("/package.json", "");
|
|
54
|
+
try {
|
|
55
|
+
const result = execSync(`npm audit --json 2>/dev/null`, { cwd: resolve(dir), encoding: "utf-8", timeout: 30000 });
|
|
56
|
+
const audit = JSON.parse(result);
|
|
57
|
+
const vulns = audit.metadata?.vulnerabilities ?? {};
|
|
58
|
+
const critical = vulns.critical ?? 0;
|
|
59
|
+
const high = vulns.high ?? 0;
|
|
60
|
+
const moderate = vulns.moderate ?? 0;
|
|
61
|
+
if (critical > 0) {
|
|
62
|
+
findings.push({ check: "deps", level: "critical", message: `${critical} critical vulnerabilities in ${dir}`, file: pkg, fix: "Run: npm audit fix" });
|
|
63
|
+
}
|
|
64
|
+
if (high > 0) {
|
|
65
|
+
findings.push({ check: "deps", level: "warning", message: `${high} high vulnerabilities in ${dir}`, file: pkg, fix: "Run: npm audit fix" });
|
|
66
|
+
}
|
|
67
|
+
if (moderate > 0) {
|
|
68
|
+
findings.push({ check: "deps", level: "info", message: `${moderate} moderate vulnerabilities in ${dir}`, file: pkg });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// npm audit returns non-zero when vulns found
|
|
73
|
+
try {
|
|
74
|
+
const stderr = execSync(`npm audit --json 2>&1 || true`, { cwd: resolve(dir), encoding: "utf-8", timeout: 30000 });
|
|
75
|
+
const audit = JSON.parse(stderr);
|
|
76
|
+
const vulns = audit.metadata?.vulnerabilities ?? {};
|
|
77
|
+
if ((vulns.critical ?? 0) > 0) {
|
|
78
|
+
findings.push({ check: "deps", level: "critical", message: `${vulns.critical} critical vulnerabilities in ${dir}`, file: pkg });
|
|
79
|
+
}
|
|
80
|
+
if ((vulns.high ?? 0) > 0) {
|
|
81
|
+
findings.push({ check: "deps", level: "warning", message: `${vulns.high} high vulnerabilities in ${dir}`, file: pkg });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch { /* skip */ }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return findings;
|
|
88
|
+
}
|
|
89
|
+
/** Check for missing rate limiting */
|
|
90
|
+
export function checkRateLimit() {
|
|
91
|
+
const findings = [];
|
|
92
|
+
const routeDirs = ["src/routes", "node-backend/src/routes", "backend/src/routes", "api/src/routes"];
|
|
93
|
+
for (const dir of routeDirs) {
|
|
94
|
+
if (!existsSync(dir))
|
|
95
|
+
continue;
|
|
96
|
+
// Check if rate limiter middleware exists
|
|
97
|
+
const middlewareDirs = [
|
|
98
|
+
dir.replace("/routes", "/middleware"),
|
|
99
|
+
dir.replace("/routes", "/middlewares"),
|
|
100
|
+
];
|
|
101
|
+
let hasRateLimit = false;
|
|
102
|
+
for (const mDir of middlewareDirs) {
|
|
103
|
+
if (!existsSync(mDir))
|
|
104
|
+
continue;
|
|
105
|
+
const files = readdirSync(mDir);
|
|
106
|
+
for (const f of files) {
|
|
107
|
+
const content = readFileSync(join(mDir, f), "utf-8");
|
|
108
|
+
if (content.includes("rate") && content.includes("limit")) {
|
|
109
|
+
hasRateLimit = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!hasRateLimit) {
|
|
115
|
+
findings.push({
|
|
116
|
+
check: "rate_limit",
|
|
117
|
+
level: "warning",
|
|
118
|
+
message: `No rate limiting middleware found in ${dir.replace("/routes", "/middleware")}`,
|
|
119
|
+
fix: "Add express-rate-limit or similar middleware",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
return findings;
|
|
125
|
+
}
|
|
126
|
+
/** Check for open CORS */
|
|
127
|
+
export function checkCors() {
|
|
128
|
+
const findings = [];
|
|
129
|
+
const searchFiles = ["src/index.ts", "src/app.ts", "src/server.ts", "node-backend/src/index.ts", "node-backend/src/app.ts"];
|
|
130
|
+
for (const file of searchFiles) {
|
|
131
|
+
if (!existsSync(file))
|
|
132
|
+
continue;
|
|
133
|
+
const content = readFileSync(file, "utf-8");
|
|
134
|
+
if (content.includes("origin: '*'") || content.includes('origin: "*"')) {
|
|
135
|
+
findings.push({ check: "cors", level: "warning", message: `CORS allows all origins (*) in ${file}`, file, fix: "Restrict CORS to your frontend domain" });
|
|
136
|
+
}
|
|
137
|
+
if (content.includes("cors()") && !content.includes("origin")) {
|
|
138
|
+
findings.push({ check: "cors", level: "info", message: `CORS initialized without explicit origin in ${file}`, file, fix: "Set explicit CORS origin" });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return findings;
|
|
142
|
+
}
|
|
143
|
+
/** Check for routes without auth middleware */
|
|
144
|
+
export function checkAuth() {
|
|
145
|
+
const findings = [];
|
|
146
|
+
const routeDirs = ["src/routes", "node-backend/src/routes", "backend/src/routes"];
|
|
147
|
+
for (const dir of routeDirs) {
|
|
148
|
+
if (!existsSync(dir))
|
|
149
|
+
continue;
|
|
150
|
+
const files = readdirSync(dir).filter(f => f.endsWith(".ts") || f.endsWith(".js"));
|
|
151
|
+
const authKeywords = ["authenticate", "requirePermission", "authorize", "auth", "requireAuth", "isAuthenticated", "protect"];
|
|
152
|
+
let unprotectedCount = 0;
|
|
153
|
+
for (const file of files) {
|
|
154
|
+
if (file === "auth.ts" || file === "auth.js" || file === "health.ts")
|
|
155
|
+
continue; // auth routes are public
|
|
156
|
+
const content = readFileSync(join(dir, file), "utf-8");
|
|
157
|
+
const hasAuth = authKeywords.some(kw => content.includes(kw));
|
|
158
|
+
const hasRoutes = /router\.(get|post|put|patch|delete)\s*\(/.test(content);
|
|
159
|
+
if (hasRoutes && !hasAuth) {
|
|
160
|
+
findings.push({ check: "auth", level: "warning", message: `No auth middleware detected in ${file}`, file: join(dir, file), fix: "Add authenticate() or requirePermission() middleware" });
|
|
161
|
+
unprotectedCount++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (unprotectedCount > 0) {
|
|
165
|
+
findings.push({ check: "auth", level: "info", message: `${unprotectedCount} route files without visible auth middleware`, fix: "Review each route file for proper authentication" });
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
return findings;
|
|
170
|
+
}
|
|
171
|
+
/** Run all security checks */
|
|
172
|
+
export function runAllChecks(checks) {
|
|
173
|
+
const allChecks = {
|
|
174
|
+
secrets: checkSecrets,
|
|
175
|
+
deps: checkDeps,
|
|
176
|
+
rate_limit: checkRateLimit,
|
|
177
|
+
cors: checkCors,
|
|
178
|
+
auth: checkAuth,
|
|
179
|
+
};
|
|
180
|
+
const toRun = checks ?? Object.keys(allChecks);
|
|
181
|
+
const findings = [];
|
|
182
|
+
for (const check of toRun) {
|
|
183
|
+
const fn = allChecks[check];
|
|
184
|
+
if (fn) {
|
|
185
|
+
try {
|
|
186
|
+
findings.push(...fn());
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
findings.push({ check, level: "info", message: `Check "${check}" failed to run` });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return findings;
|
|
194
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prajwolkc/stk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "One CLI to deploy, monitor, debug, and learn about your entire stack. Infrastructure monitoring, knowledge base brain, deploy watching, and GitHub issues — all from one command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"dist",
|
|
34
|
+
"src/data",
|
|
34
35
|
"README.md",
|
|
35
36
|
"LICENSE"
|
|
36
37
|
],
|