@vibekiln/cutline-mcp-cli 0.13.0 → 0.15.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 +15 -0
- package/dist/auth/callback.d.ts +1 -1
- package/dist/auth/callback.js +9 -1
- package/dist/commands/login.js +4 -1
- package/dist/commands/serve.d.ts +4 -0
- package/dist/commands/serve.js +19 -6
- package/dist/commands/setup.js +17 -8
- package/dist/index.js +4 -2
- package/dist/servers/{chunk-RUCYK3TR.js → chunk-FHWY2TYO.js} +349 -89
- package/dist/servers/chunk-Q5V4VTF5.js +3180 -0
- package/dist/servers/{chunk-KMUSQOTJ.js → chunk-WTLGBZBN.js} +15 -1
- package/dist/servers/cutline-server.js +1218 -4011
- package/dist/servers/{data-client-RY2DCLME.js → data-client-3OADPXXN.js} +45 -3
- package/dist/servers/exploration-server.js +1 -1
- package/dist/servers/integrations-server.js +2 -2
- package/dist/servers/output-server.js +1 -1
- package/dist/servers/premortem-server.js +30 -65
- package/dist/servers/slopburn-server.js +10069 -0
- package/dist/servers/tools-server.js +67 -6
- package/package.json +5 -3
- package/server.json +1 -1
|
@@ -0,0 +1,3180 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computeGenericGraphMetrics
|
|
3
|
+
} from "./chunk-IDSVMCGM.js";
|
|
4
|
+
|
|
5
|
+
// ../mcp/dist/mcp/src/context-graph/codebase-scanner.js
|
|
6
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
7
|
+
import { join, relative, basename, extname } from "node:path";
|
|
8
|
+
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
9
|
+
"node_modules",
|
|
10
|
+
".next",
|
|
11
|
+
".nuxt",
|
|
12
|
+
"dist",
|
|
13
|
+
"build",
|
|
14
|
+
"out",
|
|
15
|
+
".git",
|
|
16
|
+
".svn",
|
|
17
|
+
".hg",
|
|
18
|
+
"__pycache__",
|
|
19
|
+
".pytest_cache",
|
|
20
|
+
".mypy_cache",
|
|
21
|
+
".tox",
|
|
22
|
+
"venv",
|
|
23
|
+
".venv",
|
|
24
|
+
"env",
|
|
25
|
+
".env",
|
|
26
|
+
"vendor",
|
|
27
|
+
"target",
|
|
28
|
+
".gradle",
|
|
29
|
+
".idea",
|
|
30
|
+
".vscode",
|
|
31
|
+
".cursor",
|
|
32
|
+
"coverage",
|
|
33
|
+
".turbo",
|
|
34
|
+
".vercel",
|
|
35
|
+
".firebase",
|
|
36
|
+
".serverless",
|
|
37
|
+
"tmp",
|
|
38
|
+
"temp",
|
|
39
|
+
".cache",
|
|
40
|
+
".parcel-cache"
|
|
41
|
+
]);
|
|
42
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
43
|
+
".png",
|
|
44
|
+
".jpg",
|
|
45
|
+
".jpeg",
|
|
46
|
+
".gif",
|
|
47
|
+
".webp",
|
|
48
|
+
".ico",
|
|
49
|
+
".svg",
|
|
50
|
+
".woff",
|
|
51
|
+
".woff2",
|
|
52
|
+
".ttf",
|
|
53
|
+
".eot",
|
|
54
|
+
".otf",
|
|
55
|
+
".pdf",
|
|
56
|
+
".zip",
|
|
57
|
+
".tar",
|
|
58
|
+
".gz",
|
|
59
|
+
".bz2",
|
|
60
|
+
".mp3",
|
|
61
|
+
".mp4",
|
|
62
|
+
".wav",
|
|
63
|
+
".ogg",
|
|
64
|
+
".webm",
|
|
65
|
+
".exe",
|
|
66
|
+
".dll",
|
|
67
|
+
".so",
|
|
68
|
+
".dylib",
|
|
69
|
+
".lock"
|
|
70
|
+
]);
|
|
71
|
+
async function walkFileTree(options) {
|
|
72
|
+
const { root, maxDepth = 12, maxFiles = 5e3, extraIgnoreDirs = [] } = options;
|
|
73
|
+
const ignoreDirs = /* @__PURE__ */ new Set([...DEFAULT_IGNORE_DIRS, ...extraIgnoreDirs]);
|
|
74
|
+
const gitignorePatterns = await loadGitignore(root);
|
|
75
|
+
const files = [];
|
|
76
|
+
async function walk(dir, depth) {
|
|
77
|
+
if (depth > maxDepth || files.length >= maxFiles)
|
|
78
|
+
return;
|
|
79
|
+
let entries;
|
|
80
|
+
try {
|
|
81
|
+
entries = await readdir(dir);
|
|
82
|
+
} catch {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const name of entries) {
|
|
86
|
+
if (files.length >= maxFiles)
|
|
87
|
+
break;
|
|
88
|
+
if (name.startsWith(".") && name !== ".env.example")
|
|
89
|
+
continue;
|
|
90
|
+
const fullPath = join(dir, name);
|
|
91
|
+
const relPath = relative(root, fullPath).replace(/\\/g, "/");
|
|
92
|
+
let stats;
|
|
93
|
+
try {
|
|
94
|
+
stats = await stat(fullPath);
|
|
95
|
+
} catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (stats.isDirectory()) {
|
|
99
|
+
if (ignoreDirs.has(name))
|
|
100
|
+
continue;
|
|
101
|
+
if (matchesGitignore(relPath + "/", gitignorePatterns))
|
|
102
|
+
continue;
|
|
103
|
+
await walk(fullPath, depth + 1);
|
|
104
|
+
} else if (stats.isFile()) {
|
|
105
|
+
const ext = extname(name).toLowerCase();
|
|
106
|
+
if (BINARY_EXTENSIONS.has(ext))
|
|
107
|
+
continue;
|
|
108
|
+
if (matchesGitignore(relPath, gitignorePatterns))
|
|
109
|
+
continue;
|
|
110
|
+
files.push({ path: relPath, size: stats.size, ext });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
await walk(root, 0);
|
|
115
|
+
return files;
|
|
116
|
+
}
|
|
117
|
+
var DEP_CLASSIFIERS = [
|
|
118
|
+
// Frameworks
|
|
119
|
+
{ pattern: /^next$/i, category: "frameworks", label: "next.js" },
|
|
120
|
+
{ pattern: /^nuxt$/i, category: "frameworks", label: "nuxt" },
|
|
121
|
+
{ pattern: /^react$/i, category: "frameworks", label: "react" },
|
|
122
|
+
{ pattern: /^vue$/i, category: "frameworks", label: "vue" },
|
|
123
|
+
{ pattern: /^svelte$/i, category: "frameworks", label: "svelte" },
|
|
124
|
+
{ pattern: /^angular/i, category: "frameworks", label: "angular" },
|
|
125
|
+
{ pattern: /^express$/i, category: "frameworks", label: "express" },
|
|
126
|
+
{ pattern: /^fastify$/i, category: "frameworks", label: "fastify" },
|
|
127
|
+
{ pattern: /^hono$/i, category: "frameworks", label: "hono" },
|
|
128
|
+
{ pattern: /^django$/i, category: "frameworks", label: "django" },
|
|
129
|
+
{ pattern: /^flask$/i, category: "frameworks", label: "flask" },
|
|
130
|
+
{ pattern: /^fastapi$/i, category: "frameworks", label: "fastapi" },
|
|
131
|
+
{ pattern: /^spring/i, category: "frameworks", label: "spring-boot" },
|
|
132
|
+
{ pattern: /^rails$/i, category: "frameworks", label: "rails" },
|
|
133
|
+
{ pattern: /^remix$/i, category: "frameworks", label: "remix" },
|
|
134
|
+
{ pattern: /^astro$/i, category: "frameworks", label: "astro" },
|
|
135
|
+
{ pattern: /^gatsby$/i, category: "frameworks", label: "gatsby" },
|
|
136
|
+
{ pattern: /^electron$/i, category: "frameworks", label: "electron" },
|
|
137
|
+
{ pattern: /^react-native$/i, category: "frameworks", label: "react-native" },
|
|
138
|
+
{ pattern: /^expo$/i, category: "frameworks", label: "expo" },
|
|
139
|
+
// ORMs / DB
|
|
140
|
+
{ pattern: /^prisma$|^@prisma/i, category: "orms", label: "prisma" },
|
|
141
|
+
{ pattern: /^typeorm$/i, category: "orms", label: "typeorm" },
|
|
142
|
+
{ pattern: /^drizzle-orm$/i, category: "orms", label: "drizzle" },
|
|
143
|
+
{ pattern: /^sequelize$/i, category: "orms", label: "sequelize" },
|
|
144
|
+
{ pattern: /^mongoose$/i, category: "orms", label: "mongoose" },
|
|
145
|
+
{ pattern: /^knex$/i, category: "orms", label: "knex" },
|
|
146
|
+
{ pattern: /^sqlalchemy$/i, category: "orms", label: "sqlalchemy" },
|
|
147
|
+
{ pattern: /^pg$|^postgres$/i, category: "orms", label: "postgresql" },
|
|
148
|
+
{ pattern: /^mysql2?$/i, category: "orms", label: "mysql" },
|
|
149
|
+
{ pattern: /^better-sqlite3$/i, category: "orms", label: "sqlite" },
|
|
150
|
+
// Infrastructure
|
|
151
|
+
{ pattern: /^firebase-admin$|^@firebase/i, category: "infrastructure", label: "firebase" },
|
|
152
|
+
{ pattern: /^@google-cloud/i, category: "infrastructure", label: "gcp" },
|
|
153
|
+
{ pattern: /^@?aws-sdk/i, category: "infrastructure", label: "aws" },
|
|
154
|
+
{ pattern: /^@azure/i, category: "infrastructure", label: "azure" },
|
|
155
|
+
{ pattern: /^redis$|^ioredis$/i, category: "infrastructure", label: "redis" },
|
|
156
|
+
{ pattern: /^kafkajs$|^kafka-node$/i, category: "infrastructure", label: "kafka" },
|
|
157
|
+
{ pattern: /^amqplib$/i, category: "infrastructure", label: "rabbitmq" },
|
|
158
|
+
{ pattern: /^stripe$/i, category: "infrastructure", label: "stripe" },
|
|
159
|
+
{ pattern: /^@sendgrid/i, category: "infrastructure", label: "sendgrid" },
|
|
160
|
+
{ pattern: /^nodemailer$/i, category: "infrastructure", label: "nodemailer" },
|
|
161
|
+
{ pattern: /^@supabase/i, category: "infrastructure", label: "supabase" },
|
|
162
|
+
{ pattern: /^@vercel/i, category: "infrastructure", label: "vercel" },
|
|
163
|
+
{ pattern: /^docker/i, category: "infrastructure", label: "docker" },
|
|
164
|
+
// Security
|
|
165
|
+
{ pattern: /^helmet$/i, category: "security_libs", label: "helmet" },
|
|
166
|
+
{ pattern: /^bcrypt$|^bcryptjs$/i, category: "security_libs", label: "bcrypt" },
|
|
167
|
+
{ pattern: /^jsonwebtoken$/i, category: "security_libs", label: "jwt" },
|
|
168
|
+
{ pattern: /^passport$/i, category: "security_libs", label: "passport" },
|
|
169
|
+
{ pattern: /^cors$/i, category: "security_libs", label: "cors" },
|
|
170
|
+
{ pattern: /^csurf$/i, category: "security_libs", label: "csrf" },
|
|
171
|
+
{ pattern: /^rate-limiter-flexible$/i, category: "security_libs", label: "rate-limiter" },
|
|
172
|
+
{ pattern: /^zod$/i, category: "security_libs", label: "zod" },
|
|
173
|
+
{ pattern: /^joi$/i, category: "security_libs", label: "joi" },
|
|
174
|
+
{ pattern: /^express-validator$/i, category: "security_libs", label: "express-validator" },
|
|
175
|
+
// AI/LLM
|
|
176
|
+
{ pattern: /^openai$/i, category: "ai_libs", label: "openai" },
|
|
177
|
+
{ pattern: /^@anthropic-ai/i, category: "ai_libs", label: "anthropic" },
|
|
178
|
+
{ pattern: /^@google-cloud\/vertexai$|^@google\/generative-ai$/i, category: "ai_libs", label: "vertex-ai" },
|
|
179
|
+
{ pattern: /^langchain$|^@langchain/i, category: "ai_libs", label: "langchain" },
|
|
180
|
+
{ pattern: /^llamaindex$/i, category: "ai_libs", label: "llamaindex" },
|
|
181
|
+
{ pattern: /^ai$|^@ai-sdk/i, category: "ai_libs", label: "vercel-ai-sdk" },
|
|
182
|
+
{ pattern: /^replicate$/i, category: "ai_libs", label: "replicate" },
|
|
183
|
+
{ pattern: /^cohere-ai$/i, category: "ai_libs", label: "cohere" },
|
|
184
|
+
// Testing
|
|
185
|
+
{ pattern: /^jest$/i, category: "testing", label: "jest" },
|
|
186
|
+
{ pattern: /^vitest$/i, category: "testing", label: "vitest" },
|
|
187
|
+
{ pattern: /^mocha$/i, category: "testing", label: "mocha" },
|
|
188
|
+
{ pattern: /^cypress$/i, category: "testing", label: "cypress" },
|
|
189
|
+
{ pattern: /^playwright$/i, category: "testing", label: "playwright" },
|
|
190
|
+
{ pattern: /^@testing-library/i, category: "testing", label: "testing-library" },
|
|
191
|
+
{ pattern: /^pytest$/i, category: "testing", label: "pytest" },
|
|
192
|
+
{ pattern: /^supertest$/i, category: "testing", label: "supertest" }
|
|
193
|
+
];
|
|
194
|
+
async function fingerprint(root, files) {
|
|
195
|
+
const result = {
|
|
196
|
+
languages: [],
|
|
197
|
+
frameworks: [],
|
|
198
|
+
orms: [],
|
|
199
|
+
infrastructure: [],
|
|
200
|
+
security_libs: [],
|
|
201
|
+
ai_libs: [],
|
|
202
|
+
testing: [],
|
|
203
|
+
all_dependencies: [],
|
|
204
|
+
iac_tools: []
|
|
205
|
+
};
|
|
206
|
+
const extCounts = /* @__PURE__ */ new Map();
|
|
207
|
+
for (const f of files) {
|
|
208
|
+
const ext = f.ext.toLowerCase();
|
|
209
|
+
extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
|
|
210
|
+
}
|
|
211
|
+
result.languages = detectLanguages(extCounts);
|
|
212
|
+
const deps = /* @__PURE__ */ new Set();
|
|
213
|
+
const pkgJson = await readJsonSafe(join(root, "package.json"));
|
|
214
|
+
if (pkgJson) {
|
|
215
|
+
result.package_manager = detectNodePackageManager(root, files);
|
|
216
|
+
for (const name of Object.keys(pkgJson.dependencies ?? {}))
|
|
217
|
+
deps.add(name);
|
|
218
|
+
for (const name of Object.keys(pkgJson.devDependencies ?? {}))
|
|
219
|
+
deps.add(name);
|
|
220
|
+
}
|
|
221
|
+
const reqTxt = await readTextSafe(join(root, "requirements.txt"));
|
|
222
|
+
if (reqTxt) {
|
|
223
|
+
result.package_manager = result.package_manager ?? "pip";
|
|
224
|
+
for (const line of reqTxt.split("\n")) {
|
|
225
|
+
const pkg = line.trim().split(/[=<>!~\[]/)[0].trim();
|
|
226
|
+
if (pkg && !pkg.startsWith("#") && !pkg.startsWith("-"))
|
|
227
|
+
deps.add(pkg);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const pyproject = await readTextSafe(join(root, "pyproject.toml"));
|
|
231
|
+
if (pyproject) {
|
|
232
|
+
result.package_manager = result.package_manager ?? "pip";
|
|
233
|
+
const depSection = pyproject.match(/\[(?:tool\.poetry\.)?dependencies\]([\s\S]*?)(?:\[|$)/);
|
|
234
|
+
if (depSection) {
|
|
235
|
+
for (const line of depSection[1].split("\n")) {
|
|
236
|
+
const match = line.match(/^(\w[\w-]*)\s*=/);
|
|
237
|
+
if (match)
|
|
238
|
+
deps.add(match[1]);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const gomod = await readTextSafe(join(root, "go.mod"));
|
|
243
|
+
if (gomod) {
|
|
244
|
+
result.package_manager = "go";
|
|
245
|
+
for (const line of gomod.split("\n")) {
|
|
246
|
+
const match = line.match(/^\s+([\w./\-@]+)\s+v/);
|
|
247
|
+
if (match)
|
|
248
|
+
deps.add(match[1].split("/").pop());
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const gemfile = await readTextSafe(join(root, "Gemfile"));
|
|
252
|
+
if (gemfile) {
|
|
253
|
+
result.package_manager = result.package_manager ?? "bundler";
|
|
254
|
+
for (const line of gemfile.split("\n")) {
|
|
255
|
+
const match = line.match(/gem\s+['"]([^'"]+)['"]/);
|
|
256
|
+
if (match)
|
|
257
|
+
deps.add(match[1]);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
result.all_dependencies = [...deps];
|
|
261
|
+
const seen = /* @__PURE__ */ new Map();
|
|
262
|
+
for (const depName of deps) {
|
|
263
|
+
for (const classifier of DEP_CLASSIFIERS) {
|
|
264
|
+
if (classifier.pattern.test(depName)) {
|
|
265
|
+
if (!seen.has(classifier.category))
|
|
266
|
+
seen.set(classifier.category, /* @__PURE__ */ new Set());
|
|
267
|
+
const catSet = seen.get(classifier.category);
|
|
268
|
+
if (!catSet.has(classifier.label)) {
|
|
269
|
+
catSet.add(classifier.label);
|
|
270
|
+
result[classifier.category].push(classifier.label);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const fileNames = new Set(files.map((f) => basename(f.path).toLowerCase()));
|
|
276
|
+
const filePaths = files.map((f) => f.path.toLowerCase());
|
|
277
|
+
const iacSet = /* @__PURE__ */ new Set();
|
|
278
|
+
if (filePaths.some((p) => p.endsWith(".tf")))
|
|
279
|
+
iacSet.add("terraform");
|
|
280
|
+
if (fileNames.has("dockerfile") || filePaths.some((p) => /docker-compose/i.test(p)))
|
|
281
|
+
iacSet.add("docker");
|
|
282
|
+
if (filePaths.some((p) => /pulumi\.(yaml|yml)$/i.test(p)))
|
|
283
|
+
iacSet.add("pulumi");
|
|
284
|
+
if (filePaths.some((p) => /\/k8s\/|\/kubernetes\/|\/helm\//i.test(p)))
|
|
285
|
+
iacSet.add("kubernetes");
|
|
286
|
+
if (filePaths.some((p) => /cloudformation/i.test(p)))
|
|
287
|
+
iacSet.add("cloudformation");
|
|
288
|
+
result.iac_tools = [...iacSet];
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
async function parseLockfiles(root, files, packageManager) {
|
|
292
|
+
const entries = [];
|
|
293
|
+
const seenKey = /* @__PURE__ */ new Set();
|
|
294
|
+
function add(name, version, ecosystem, dev) {
|
|
295
|
+
if (!name || !version)
|
|
296
|
+
return;
|
|
297
|
+
const key = `${ecosystem}:${name}@${version}`;
|
|
298
|
+
if (seenKey.has(key))
|
|
299
|
+
return;
|
|
300
|
+
seenKey.add(key);
|
|
301
|
+
entries.push({ name, version, ecosystem, dev });
|
|
302
|
+
}
|
|
303
|
+
const pkgLock = await readJsonSafe(join(root, "package-lock.json"));
|
|
304
|
+
if (pkgLock) {
|
|
305
|
+
if (pkgLock.packages) {
|
|
306
|
+
for (const [key, val] of Object.entries(pkgLock.packages)) {
|
|
307
|
+
if (key === "")
|
|
308
|
+
continue;
|
|
309
|
+
const name = key.replace(/^node_modules\//, "");
|
|
310
|
+
if (name.includes("node_modules/"))
|
|
311
|
+
continue;
|
|
312
|
+
if (val.version)
|
|
313
|
+
add(name, val.version, "npm", val.dev ?? false);
|
|
314
|
+
}
|
|
315
|
+
} else if (pkgLock.dependencies) {
|
|
316
|
+
for (const [name, val] of Object.entries(pkgLock.dependencies)) {
|
|
317
|
+
if (val.version)
|
|
318
|
+
add(name, val.version, "npm", val.dev ?? false);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (!pkgLock || packageManager === "yarn") {
|
|
323
|
+
const yarnLock = await readTextSafe(join(root, "yarn.lock"));
|
|
324
|
+
if (yarnLock) {
|
|
325
|
+
const blocks = yarnLock.split(/\n(?=\S)/);
|
|
326
|
+
for (const block of blocks) {
|
|
327
|
+
const headerMatch = block.match(/^"?(@?[^@\s"]+)@/);
|
|
328
|
+
const versionMatch = block.match(/^\s+version\s+"([^"]+)"/m);
|
|
329
|
+
if (headerMatch && versionMatch) {
|
|
330
|
+
add(headerMatch[1], versionMatch[1], "npm", false);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (packageManager === "pnpm") {
|
|
336
|
+
const pnpmLock = await readTextSafe(join(root, "pnpm-lock.yaml"));
|
|
337
|
+
if (pnpmLock) {
|
|
338
|
+
const pkgRegex = /^\s+'?\/?(@?[^@\s']+)@(\d[^':\s]*)[':]?/;
|
|
339
|
+
for (const line of pnpmLock.split("\n")) {
|
|
340
|
+
const m = line.match(pkgRegex);
|
|
341
|
+
if (m)
|
|
342
|
+
add(m[1], m[2], "npm", false);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const reqTxt = await readTextSafe(join(root, "requirements.txt"));
|
|
347
|
+
if (reqTxt) {
|
|
348
|
+
for (const line of reqTxt.split("\n")) {
|
|
349
|
+
const trimmed = line.trim();
|
|
350
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-"))
|
|
351
|
+
continue;
|
|
352
|
+
const m = trimmed.match(/^([a-zA-Z0-9_.-]+)\s*[=~!<>]=\s*([0-9][^\s;,#]*)/);
|
|
353
|
+
if (m)
|
|
354
|
+
add(m[1], m[2], "PyPI", false);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const poetryLock = await readTextSafe(join(root, "poetry.lock"));
|
|
358
|
+
if (poetryLock) {
|
|
359
|
+
const blocks = poetryLock.split(/\n\[\[package\]\]/);
|
|
360
|
+
for (const block of blocks) {
|
|
361
|
+
const nameMatch = block.match(/^name\s*=\s*"([^"]+)"/m);
|
|
362
|
+
const versionMatch = block.match(/^version\s*=\s*"([^"]+)"/m);
|
|
363
|
+
if (nameMatch && versionMatch) {
|
|
364
|
+
add(nameMatch[1], versionMatch[1], "PyPI", false);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const goSum = await readTextSafe(join(root, "go.sum"));
|
|
369
|
+
if (goSum) {
|
|
370
|
+
const seen = /* @__PURE__ */ new Set();
|
|
371
|
+
for (const line of goSum.split("\n")) {
|
|
372
|
+
const m = line.match(/^(\S+)\s+v(\d[^\s/]*)/);
|
|
373
|
+
if (m) {
|
|
374
|
+
const key = `${m[1]}@${m[2]}`;
|
|
375
|
+
if (!seen.has(key)) {
|
|
376
|
+
seen.add(key);
|
|
377
|
+
add(m[1], m[2], "Go", false);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const gemfileLock = await readTextSafe(join(root, "Gemfile.lock"));
|
|
383
|
+
if (gemfileLock) {
|
|
384
|
+
const specSection = gemfileLock.match(/GEM[\s\S]*?specs:\n([\s\S]*?)(?:\n\S|\n$)/);
|
|
385
|
+
if (specSection) {
|
|
386
|
+
for (const line of specSection[1].split("\n")) {
|
|
387
|
+
const m = line.match(/^\s{4}(\S+)\s+\(([^)]+)\)/);
|
|
388
|
+
if (m)
|
|
389
|
+
add(m[1], m[2], "RubyGems", false);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return entries;
|
|
394
|
+
}
|
|
395
|
+
function detectLanguages(extCounts) {
|
|
396
|
+
const langMap = {
|
|
397
|
+
".ts": "typescript",
|
|
398
|
+
".tsx": "typescript",
|
|
399
|
+
".js": "javascript",
|
|
400
|
+
".jsx": "javascript",
|
|
401
|
+
".mjs": "javascript",
|
|
402
|
+
".py": "python",
|
|
403
|
+
".go": "go",
|
|
404
|
+
".rs": "rust",
|
|
405
|
+
".java": "java",
|
|
406
|
+
".kt": "kotlin",
|
|
407
|
+
".rb": "ruby",
|
|
408
|
+
".cs": "csharp",
|
|
409
|
+
".swift": "swift",
|
|
410
|
+
".php": "php",
|
|
411
|
+
".dart": "dart",
|
|
412
|
+
".ex": "elixir",
|
|
413
|
+
".exs": "elixir"
|
|
414
|
+
};
|
|
415
|
+
const langCounts = /* @__PURE__ */ new Map();
|
|
416
|
+
for (const [ext, count] of extCounts) {
|
|
417
|
+
const lang = langMap[ext];
|
|
418
|
+
if (lang)
|
|
419
|
+
langCounts.set(lang, (langCounts.get(lang) ?? 0) + count);
|
|
420
|
+
}
|
|
421
|
+
return [...langCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([lang]) => lang);
|
|
422
|
+
}
|
|
423
|
+
function detectNodePackageManager(root, files) {
|
|
424
|
+
const names = new Set(files.map((f) => basename(f.path)));
|
|
425
|
+
if (names.has("pnpm-lock.yaml"))
|
|
426
|
+
return "pnpm";
|
|
427
|
+
if (names.has("yarn.lock"))
|
|
428
|
+
return "yarn";
|
|
429
|
+
if (names.has("bun.lockb"))
|
|
430
|
+
return "bun";
|
|
431
|
+
return "npm";
|
|
432
|
+
}
|
|
433
|
+
var SENSITIVE_PATTERNS = [
|
|
434
|
+
// PII
|
|
435
|
+
{ pattern: /\b(email|e_mail|emailAddress|user_email)\b/i, classification: "pii" },
|
|
436
|
+
{ pattern: /\b(phone|phoneNumber|phone_number|mobile|cellPhone)\b/i, classification: "pii" },
|
|
437
|
+
{ pattern: /\b(firstName|first_name|lastName|last_name|fullName|full_name|displayName)\b/i, classification: "pii" },
|
|
438
|
+
{ pattern: /\b(dateOfBirth|date_of_birth|dob|birthDate|birth_date)\b/i, classification: "pii" },
|
|
439
|
+
{ pattern: /\b(address|streetAddress|street_address|homeAddress|mailingAddress)\b/i, classification: "pii" },
|
|
440
|
+
{ pattern: /\b(city|state|zipCode|zip_code|postalCode|postal_code|country)\b/i, classification: "pii" },
|
|
441
|
+
{ pattern: /\b(ipAddress|ip_address|userAgent|user_agent)\b/i, classification: "pii" },
|
|
442
|
+
{ pattern: /\b(avatar|profilePhoto|profile_photo|profileImage)\b/i, classification: "pii" },
|
|
443
|
+
// Financial
|
|
444
|
+
{ pattern: /\b(creditCard|credit_card|cardNumber|card_number)\b/i, classification: "financial" },
|
|
445
|
+
{ pattern: /\b(bankAccount|bank_account|accountNumber|account_number|routingNumber)\b/i, classification: "financial" },
|
|
446
|
+
{ pattern: /\b(stripeId|stripe_id|stripeCustomerId|stripe_customer_id|paymentMethod)\b/i, classification: "financial" },
|
|
447
|
+
{ pattern: /\b(balance|amount|price|salary|income|revenue)\b/i, classification: "financial" },
|
|
448
|
+
{ pattern: /\b(invoice|billing|subscription|plan|tier)\b/i, classification: "financial" },
|
|
449
|
+
// Credentials
|
|
450
|
+
{ pattern: /\b(password|passwordHash|password_hash|hashedPassword)\b/i, classification: "credential" },
|
|
451
|
+
{ pattern: /\b(secret|apiKey|api_key|apiSecret|api_secret)\b/i, classification: "credential" },
|
|
452
|
+
{ pattern: /\b(token|accessToken|access_token|refreshToken|refresh_token)\b/i, classification: "credential" },
|
|
453
|
+
{ pattern: /\b(sessionId|session_id|authToken|auth_token)\b/i, classification: "credential" },
|
|
454
|
+
{ pattern: /\b(privateKey|private_key|publicKey|encryptionKey)\b/i, classification: "credential" },
|
|
455
|
+
// Health
|
|
456
|
+
{ pattern: /\b(diagnosis|medication|prescription|medicalRecord|medical_record)\b/i, classification: "health" },
|
|
457
|
+
{ pattern: /\b(healthData|health_data|bloodType|blood_type|allergy|allergies)\b/i, classification: "health" },
|
|
458
|
+
{ pattern: /\b(insuranceId|insurance_id|patientId|patient_id)\b/i, classification: "health" },
|
|
459
|
+
// Government ID
|
|
460
|
+
{ pattern: /\b(ssn|socialSecurity|social_security|socialSecurityNumber)\b/i, classification: "government_id" },
|
|
461
|
+
{ pattern: /\b(taxId|tax_id|ein|nationalId|national_id|passport|passportNumber)\b/i, classification: "government_id" },
|
|
462
|
+
{ pattern: /\b(driversLicense|drivers_license|licenseNumber|license_number)\b/i, classification: "government_id" }
|
|
463
|
+
];
|
|
464
|
+
var MODEL_FILE_PATTERNS = [
|
|
465
|
+
/\bmodels?\b/i,
|
|
466
|
+
/\bschemas?\b/i,
|
|
467
|
+
/\btypes?\b/i,
|
|
468
|
+
/\bentit(y|ies)\b/i,
|
|
469
|
+
/\binterfaces?\b/i,
|
|
470
|
+
/\bdb\b/i,
|
|
471
|
+
/\bdatabase\b/i,
|
|
472
|
+
/\bmigrations?\b/i
|
|
473
|
+
];
|
|
474
|
+
var MODEL_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
475
|
+
".ts",
|
|
476
|
+
".tsx",
|
|
477
|
+
".js",
|
|
478
|
+
".jsx",
|
|
479
|
+
".py",
|
|
480
|
+
".go",
|
|
481
|
+
".rs",
|
|
482
|
+
".java",
|
|
483
|
+
".kt",
|
|
484
|
+
".rb",
|
|
485
|
+
".prisma",
|
|
486
|
+
".graphql",
|
|
487
|
+
".gql",
|
|
488
|
+
".proto",
|
|
489
|
+
".sql"
|
|
490
|
+
]);
|
|
491
|
+
async function discoverSensitiveData(root, files, extraModelExts = []) {
|
|
492
|
+
const allowedExts = /* @__PURE__ */ new Set([...MODEL_EXTENSIONS, ...extraModelExts]);
|
|
493
|
+
const fields = [];
|
|
494
|
+
let filesScanned = 0;
|
|
495
|
+
const candidates = files.filter((f) => {
|
|
496
|
+
if (!allowedExts.has(f.ext.toLowerCase()))
|
|
497
|
+
return false;
|
|
498
|
+
if (f.size > 2e5)
|
|
499
|
+
return false;
|
|
500
|
+
return MODEL_FILE_PATTERNS.some((p) => p.test(f.path));
|
|
501
|
+
});
|
|
502
|
+
for (const f of files) {
|
|
503
|
+
const ext = f.ext.toLowerCase();
|
|
504
|
+
if ([".prisma", ".graphql", ".gql", ".proto", ".sql"].includes(ext)) {
|
|
505
|
+
if (!candidates.some((c) => c.path === f.path)) {
|
|
506
|
+
candidates.push(f);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const toScan = candidates.slice(0, 200);
|
|
511
|
+
for (const file of toScan) {
|
|
512
|
+
let content;
|
|
513
|
+
try {
|
|
514
|
+
content = await readFile(join(root, file.path), "utf-8");
|
|
515
|
+
} catch {
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
filesScanned++;
|
|
519
|
+
const lines = content.split("\n");
|
|
520
|
+
for (let i = 0; i < lines.length; i++) {
|
|
521
|
+
const line = lines[i];
|
|
522
|
+
for (const sp of SENSITIVE_PATTERNS) {
|
|
523
|
+
const match = sp.pattern.exec(line);
|
|
524
|
+
if (match) {
|
|
525
|
+
fields.push({
|
|
526
|
+
file: file.path,
|
|
527
|
+
line: i + 1,
|
|
528
|
+
field: match[1] || match[0],
|
|
529
|
+
classification: sp.classification
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const seen = /* @__PURE__ */ new Set();
|
|
536
|
+
const deduped = fields.filter((f) => {
|
|
537
|
+
const key = `${f.file}:${f.field}:${f.classification}`;
|
|
538
|
+
if (seen.has(key))
|
|
539
|
+
return false;
|
|
540
|
+
seen.add(key);
|
|
541
|
+
return true;
|
|
542
|
+
});
|
|
543
|
+
const byClass = {
|
|
544
|
+
pii: 0,
|
|
545
|
+
financial: 0,
|
|
546
|
+
credential: 0,
|
|
547
|
+
health: 0,
|
|
548
|
+
government_id: 0
|
|
549
|
+
};
|
|
550
|
+
for (const f of deduped) {
|
|
551
|
+
byClass[f.classification]++;
|
|
552
|
+
}
|
|
553
|
+
return { fields: deduped, by_class: byClass, files_scanned: filesScanned };
|
|
554
|
+
}
|
|
555
|
+
async function scanCodebase(options) {
|
|
556
|
+
const t0 = Date.now();
|
|
557
|
+
const files = await walkFileTree(options);
|
|
558
|
+
const ecosystem = await fingerprint(options.root, files);
|
|
559
|
+
const [sensitive_data, dependencies] = await Promise.all([
|
|
560
|
+
discoverSensitiveData(options.root, files, options.extraModelExts),
|
|
561
|
+
parseLockfiles(options.root, files, ecosystem.package_manager)
|
|
562
|
+
]);
|
|
563
|
+
return {
|
|
564
|
+
root: options.root,
|
|
565
|
+
files,
|
|
566
|
+
ecosystem,
|
|
567
|
+
sensitive_data,
|
|
568
|
+
dependencies,
|
|
569
|
+
elapsed_ms: Date.now() - t0
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
async function readJsonSafe(path) {
|
|
573
|
+
try {
|
|
574
|
+
const content = await readFile(path, "utf-8");
|
|
575
|
+
return JSON.parse(content);
|
|
576
|
+
} catch {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async function readTextSafe(path) {
|
|
581
|
+
try {
|
|
582
|
+
return await readFile(path, "utf-8");
|
|
583
|
+
} catch {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
async function loadGitignore(root) {
|
|
588
|
+
const content = await readTextSafe(join(root, ".gitignore"));
|
|
589
|
+
if (!content)
|
|
590
|
+
return [];
|
|
591
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!")).map(gitignoreToRegex).filter(Boolean);
|
|
592
|
+
}
|
|
593
|
+
function gitignoreToRegex(pattern) {
|
|
594
|
+
try {
|
|
595
|
+
let p = pattern;
|
|
596
|
+
const dirOnly = p.endsWith("/");
|
|
597
|
+
if (dirOnly)
|
|
598
|
+
p = p.slice(0, -1);
|
|
599
|
+
p = p.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
600
|
+
p = p.replace(/\*\*/g, "{{GLOBSTAR}}");
|
|
601
|
+
p = p.replace(/\*/g, "[^/]*");
|
|
602
|
+
p = p.replace(/\?/g, "[^/]");
|
|
603
|
+
p = p.replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
604
|
+
if (!pattern.startsWith("/")) {
|
|
605
|
+
p = `(^|.*/?)${p}`;
|
|
606
|
+
} else {
|
|
607
|
+
p = `^${p.slice(1)}`;
|
|
608
|
+
}
|
|
609
|
+
return new RegExp(`${p}(/.*)?$`, "i");
|
|
610
|
+
} catch {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function matchesGitignore(relPath, patterns) {
|
|
615
|
+
for (const re of patterns) {
|
|
616
|
+
if (re.test(relPath))
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// ../mcp/dist/mcp/src/tools/code-audit-prompts.js
|
|
623
|
+
var UNIVERSAL_CONSTRAINTS = [
|
|
624
|
+
{
|
|
625
|
+
id_suffix: "auth_middleware",
|
|
626
|
+
category: "security",
|
|
627
|
+
summary: "All API endpoints MUST be protected by authentication middleware. No public data-mutation routes.",
|
|
628
|
+
keywords: ["auth", "middleware", "api", "endpoint", "protection"],
|
|
629
|
+
severity: "critical",
|
|
630
|
+
action: "Add auth middleware to every API route. Verify no unprotected state-changing endpoints exist.",
|
|
631
|
+
checklist_ref: "A1",
|
|
632
|
+
file_patterns: ["**/api/**", "**/routes/**", "**/handlers/**"],
|
|
633
|
+
framework: "baseline"
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
id_suffix: "authz_per_resource",
|
|
637
|
+
category: "security",
|
|
638
|
+
summary: "Authorization checks MUST be per-resource, not just per-route. User A must not access User B's data.",
|
|
639
|
+
keywords: ["authorization", "rbac", "access-control", "isolation", "multi-tenant"],
|
|
640
|
+
severity: "critical",
|
|
641
|
+
action: "Implement per-resource authorization checks. Verify data isolation in queries.",
|
|
642
|
+
checklist_ref: "A2",
|
|
643
|
+
file_patterns: ["**/api/**", "**/middleware/**", "**/auth/**"],
|
|
644
|
+
framework: "baseline"
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
id_suffix: "input_validation",
|
|
648
|
+
category: "security",
|
|
649
|
+
summary: "All user inputs MUST be validated and sanitized before use in queries, commands, or rendered output.",
|
|
650
|
+
keywords: ["validation", "sanitization", "injection", "xss", "input"],
|
|
651
|
+
severity: "critical",
|
|
652
|
+
action: "Add input validation on all API routes. Use parameterized queries. Sanitize rendered content.",
|
|
653
|
+
checklist_ref: "B1",
|
|
654
|
+
file_patterns: ["**/api/**", "**/routes/**", "**/components/**"],
|
|
655
|
+
framework: "baseline"
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
id_suffix: "secrets_management",
|
|
659
|
+
category: "security",
|
|
660
|
+
summary: "Secrets, API keys, and credentials MUST NOT be committed to version control or exposed client-side.",
|
|
661
|
+
keywords: ["secrets", "env", "api-key", "credentials", "config"],
|
|
662
|
+
severity: "critical",
|
|
663
|
+
action: "Use environment variables for secrets. Verify .gitignore covers .env. Audit client bundles for leaked keys.",
|
|
664
|
+
checklist_ref: "C4",
|
|
665
|
+
file_patterns: ["**/.env*", "**/config/**", "**/.gitignore"],
|
|
666
|
+
framework: "baseline"
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
id_suffix: "rate_limiting",
|
|
670
|
+
category: "security",
|
|
671
|
+
summary: "Rate limiting MUST be applied to authentication endpoints, payment endpoints, and data export routes.",
|
|
672
|
+
keywords: ["rate-limit", "throttle", "brute-force", "dos"],
|
|
673
|
+
severity: "warning",
|
|
674
|
+
action: "Add rate limiting middleware to sensitive endpoints (login, signup, payment, export).",
|
|
675
|
+
checklist_ref: "D1",
|
|
676
|
+
file_patterns: ["**/api/auth/**", "**/api/login*", "**/api/payment*", "**/middleware/**"],
|
|
677
|
+
framework: "baseline"
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
id_suffix: "csrf_protection",
|
|
681
|
+
category: "security",
|
|
682
|
+
summary: "CSRF protection MUST be enabled on all state-changing endpoints.",
|
|
683
|
+
keywords: ["csrf", "cross-site", "token", "form"],
|
|
684
|
+
severity: "warning",
|
|
685
|
+
action: "Enable CSRF tokens on POST/PUT/DELETE routes. Verify SameSite cookie attributes.",
|
|
686
|
+
checklist_ref: "D2",
|
|
687
|
+
file_patterns: ["**/api/**", "**/middleware/**"],
|
|
688
|
+
framework: "baseline"
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
id_suffix: "error_leakage",
|
|
692
|
+
category: "security",
|
|
693
|
+
summary: "Error responses MUST NOT leak stack traces, internal paths, or database error details to clients.",
|
|
694
|
+
keywords: ["error", "stack-trace", "leak", "debug", "internal"],
|
|
695
|
+
severity: "warning",
|
|
696
|
+
action: "Use generic error responses in production. Strip internal details from API error payloads.",
|
|
697
|
+
checklist_ref: "D3",
|
|
698
|
+
file_patterns: ["**/api/**", "**/middleware/**", "**/error*"],
|
|
699
|
+
framework: "baseline"
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
id_suffix: "cors_explicit_origins",
|
|
703
|
+
category: "security",
|
|
704
|
+
summary: "CORS origins MUST be explicitly whitelisted. Wildcard (*) origins are forbidden on authenticated or state-changing endpoints.",
|
|
705
|
+
keywords: ["cors", "origin", "wildcard", "cross-origin", "headers"],
|
|
706
|
+
severity: "warning",
|
|
707
|
+
action: "Replace wildcard CORS with an explicit allowlist of trusted origins. Verify credentials mode is not combined with wildcard.",
|
|
708
|
+
checklist_ref: "D5",
|
|
709
|
+
file_patterns: ["**/api/**", "**/middleware/**", "**/config/**", "**/server.*"],
|
|
710
|
+
framework: "baseline"
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
id_suffix: "dependency_scanning",
|
|
714
|
+
category: "security",
|
|
715
|
+
summary: "Dependencies MUST be scanned for known vulnerabilities. Lockfiles must be committed and integrity-checked.",
|
|
716
|
+
keywords: ["dependency", "vulnerability", "npm-audit", "lockfile", "supply-chain", "cve"],
|
|
717
|
+
severity: "warning",
|
|
718
|
+
action: "Run `npm audit` or equivalent in CI. Commit lockfiles. Enable Dependabot or similar automated scanning.",
|
|
719
|
+
checklist_ref: "F1",
|
|
720
|
+
file_patterns: ["**/package*.json", "**/requirements*.txt", "**/*lock*", "**/.github/**"],
|
|
721
|
+
framework: "baseline"
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
id_suffix: "file_upload_validation",
|
|
725
|
+
category: "security",
|
|
726
|
+
summary: "File uploads MUST be validated for type, size, and content. Untrusted files must not be served inline or executed.",
|
|
727
|
+
keywords: ["upload", "file", "mime", "size-limit", "content-type", "malware"],
|
|
728
|
+
severity: "warning",
|
|
729
|
+
action: "Validate MIME type and file extension server-side. Enforce size limits. Store uploads outside webroot. Serve with Content-Disposition: attachment.",
|
|
730
|
+
checklist_ref: "B3",
|
|
731
|
+
file_patterns: ["**/api/upload*", "**/api/file*", "**/storage/**", "**/middleware/**"],
|
|
732
|
+
framework: "baseline"
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
id_suffix: "no_default_credentials",
|
|
736
|
+
category: "security",
|
|
737
|
+
summary: "Default credentials, example configs with real-looking values, and scaffolding secrets MUST be removed before deployment.",
|
|
738
|
+
keywords: ["default-credentials", "example-config", "scaffold", "boilerplate", "seed-data"],
|
|
739
|
+
severity: "critical",
|
|
740
|
+
action: "Audit for default passwords, example API keys, and seed data in config files. Verify .env.example contains only placeholders, not real values.",
|
|
741
|
+
checklist_ref: "C6",
|
|
742
|
+
file_patterns: ["**/.env*", "**/config/**", "**/seed*", "**/fixtures/**", "**/docker-compose*"],
|
|
743
|
+
framework: "baseline"
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
id_suffix: "no_debug_production",
|
|
747
|
+
category: "security",
|
|
748
|
+
summary: "Debug mode, verbose logging, and dev tools MUST be disabled in production builds. No DEBUG=*, React DevTools, or source maps in prod.",
|
|
749
|
+
keywords: ["debug", "devtools", "source-map", "verbose", "production", "NODE_ENV"],
|
|
750
|
+
severity: "warning",
|
|
751
|
+
action: "Ensure NODE_ENV=production in deployment. Disable source maps in prod builds. Remove debug middleware and verbose logging.",
|
|
752
|
+
checklist_ref: "C7",
|
|
753
|
+
file_patterns: ["**/config/**", "**/next.config*", "**/webpack*", "**/.env*", "**/Dockerfile*"],
|
|
754
|
+
framework: "baseline"
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
id_suffix: "secure_token_storage",
|
|
758
|
+
category: "security",
|
|
759
|
+
summary: "Auth tokens MUST be stored in httpOnly secure cookies, NOT in localStorage or sessionStorage. Client-side JS must not access tokens.",
|
|
760
|
+
keywords: ["token", "localStorage", "sessionStorage", "cookie", "httpOnly", "secure"],
|
|
761
|
+
severity: "critical",
|
|
762
|
+
action: "Move tokens from localStorage/sessionStorage to httpOnly, Secure, SameSite=Strict cookies. Verify no client JS reads auth tokens.",
|
|
763
|
+
checklist_ref: "A7",
|
|
764
|
+
file_patterns: ["**/auth/**", "**/api/login*", "**/api/session*", "**/lib/auth*", "**/hooks/**"],
|
|
765
|
+
framework: "baseline"
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
id_suffix: "user_enumeration_prevention",
|
|
769
|
+
category: "security",
|
|
770
|
+
summary: "Login, signup, and password reset flows MUST NOT reveal whether an account exists. Use identical responses and consistent timing.",
|
|
771
|
+
keywords: ["enumeration", "user-exists", "timing", "login", "reset", "signup"],
|
|
772
|
+
severity: "warning",
|
|
773
|
+
action: "Return identical error messages for valid/invalid accounts on login and reset. Add constant-time comparison. Avoid 'email already registered' on signup.",
|
|
774
|
+
checklist_ref: "A8",
|
|
775
|
+
file_patterns: ["**/api/auth/**", "**/api/login*", "**/api/signup*", "**/api/reset*", "**/api/forgot*"],
|
|
776
|
+
framework: "baseline"
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
id_suffix: "minimal_data_exposure",
|
|
780
|
+
category: "security",
|
|
781
|
+
summary: "API endpoints MUST return only the fields the client needs. No over-fetching of user records, internal IDs, or other users' data.",
|
|
782
|
+
keywords: ["over-fetch", "data-exposure", "select", "projection", "field-filtering"],
|
|
783
|
+
severity: "warning",
|
|
784
|
+
action: "Use explicit field selection in queries. Strip internal fields (createdBy, internalId, etc.) from API responses. Never return full DB documents.",
|
|
785
|
+
checklist_ref: "D6",
|
|
786
|
+
file_patterns: ["**/api/**", "**/models/**", "**/queries/**", "**/serializers/**"],
|
|
787
|
+
framework: "baseline"
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
id_suffix: "destructive_action_confirmation",
|
|
791
|
+
category: "security",
|
|
792
|
+
summary: "Sensitive actions (account deletion, email change, role escalation) MUST require re-authentication or a confirmation step.",
|
|
793
|
+
keywords: ["re-auth", "confirmation", "delete", "email-change", "destructive", "step-up"],
|
|
794
|
+
severity: "warning",
|
|
795
|
+
action: "Require password re-entry or MFA step-up for destructive operations. Add confirmation tokens with short TTL for irreversible actions.",
|
|
796
|
+
checklist_ref: "D7",
|
|
797
|
+
file_patterns: ["**/api/user*", "**/api/account*", "**/api/admin*", "**/api/delete*"],
|
|
798
|
+
framework: "baseline"
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
id_suffix: "server_side_payment_validation",
|
|
802
|
+
category: "security",
|
|
803
|
+
summary: "Payment amounts, prices, and billing logic MUST be validated server-side. Client-submitted prices or quantities must never be trusted.",
|
|
804
|
+
keywords: ["payment", "price", "billing", "server-side", "validation", "tamper"],
|
|
805
|
+
severity: "critical",
|
|
806
|
+
action: "Validate all prices/quantities against server-side catalog. Never trust client-submitted amounts. Use Stripe Checkout or server-created PaymentIntents.",
|
|
807
|
+
checklist_ref: "D8",
|
|
808
|
+
file_patterns: ["**/api/payment*", "**/api/checkout*", "**/api/stripe*", "**/billing/**"],
|
|
809
|
+
framework: "baseline"
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
id_suffix: "insecure_deserialization",
|
|
813
|
+
category: "security",
|
|
814
|
+
summary: "Untrusted data MUST NOT be deserialized using unsafe functions (pickle.load, yaml.load, eval, Function constructor). Use safe parsers with schema validation.",
|
|
815
|
+
keywords: ["deserialization", "pickle", "yaml", "eval", "prototype-pollution", "unsafe"],
|
|
816
|
+
severity: "critical",
|
|
817
|
+
action: "Replace unsafe deserialization with schema-validated parsers (zod, joi). Ban eval/Function constructor on user input. Use yaml.safeLoad instead of yaml.load.",
|
|
818
|
+
checklist_ref: "B5",
|
|
819
|
+
file_patterns: ["**/api/**", "**/utils/**", "**/lib/**", "**/parsers/**"],
|
|
820
|
+
framework: "baseline"
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
id_suffix: "path_traversal_prevention",
|
|
824
|
+
category: "security",
|
|
825
|
+
summary: "File paths from user input MUST be sanitized and restricted to an allowed base directory. Reject `..` traversal sequences.",
|
|
826
|
+
keywords: ["path-traversal", "directory-traversal", "file-access", "sanitize", "realpath"],
|
|
827
|
+
severity: "critical",
|
|
828
|
+
action: "Resolve paths with path.resolve and verify they stay within the allowed base directory. Reject inputs containing '..' or absolute paths. Use path.basename for filenames.",
|
|
829
|
+
checklist_ref: "B6",
|
|
830
|
+
file_patterns: ["**/api/file*", "**/api/download*", "**/api/upload*", "**/storage/**", "**/static/**"],
|
|
831
|
+
framework: "baseline"
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
id_suffix: "password_hashing",
|
|
835
|
+
category: "security",
|
|
836
|
+
summary: "Passwords MUST be hashed with bcrypt, scrypt, or argon2. MD5, SHA-1, and SHA-256 without salt are forbidden. Never store plaintext passwords.",
|
|
837
|
+
keywords: ["password", "hash", "bcrypt", "argon2", "scrypt", "md5", "plaintext"],
|
|
838
|
+
severity: "critical",
|
|
839
|
+
action: "Use bcrypt (cost >= 10) or argon2id for password hashing. Remove any MD5/SHA usage for passwords. Verify no plaintext password storage in DB schemas.",
|
|
840
|
+
checklist_ref: "A4",
|
|
841
|
+
file_patterns: ["**/auth/**", "**/api/auth/**", "**/api/signup*", "**/api/login*", "**/models/user*"],
|
|
842
|
+
framework: "baseline"
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
id_suffix: "transport_security",
|
|
846
|
+
category: "security",
|
|
847
|
+
summary: "All traffic MUST use HTTPS/TLS 1.2+. HTTP MUST redirect to HTTPS. HSTS headers MUST be enabled with a minimum max-age of 1 year.",
|
|
848
|
+
keywords: ["https", "tls", "ssl", "hsts", "http-redirect", "transport", "encryption-in-transit"],
|
|
849
|
+
severity: "critical",
|
|
850
|
+
action: "Enforce HTTPS on all endpoints. Add HSTS header (Strict-Transport-Security: max-age=31536000; includeSubDomains). Redirect HTTP to HTTPS at load balancer or app level.",
|
|
851
|
+
checklist_ref: "C5",
|
|
852
|
+
file_patterns: ["**/server.*", "**/app.*", "**/config/**", "**/middleware/**", "**/*.tf", "**/nginx*"],
|
|
853
|
+
framework: "baseline"
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
id_suffix: "origin_header_validation",
|
|
857
|
+
category: "security",
|
|
858
|
+
summary: "State-changing endpoints MUST verify the Origin or Referer header in addition to CSRF tokens. Reject requests from unexpected origins.",
|
|
859
|
+
keywords: ["origin", "referer", "csrf", "cross-site", "request-forgery", "samesite"],
|
|
860
|
+
severity: "warning",
|
|
861
|
+
action: "Check Origin/Referer header on POST/PUT/DELETE requests. Combine with CSRF tokens and SameSite=Strict cookies for defense in depth.",
|
|
862
|
+
checklist_ref: "D2",
|
|
863
|
+
file_patterns: ["**/middleware/**", "**/api/**", "**/config/cors*"],
|
|
864
|
+
framework: "baseline"
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
id_suffix: "secret_rotation",
|
|
868
|
+
category: "security",
|
|
869
|
+
summary: "Secrets and API keys MUST have a defined rotation schedule (90 days max). Long-lived credentials without rotation are forbidden in production.",
|
|
870
|
+
keywords: ["secret-rotation", "api-key", "credentials", "lifecycle", "expiration", "rotate"],
|
|
871
|
+
severity: "warning",
|
|
872
|
+
action: "Implement secret rotation via your cloud provider's secret manager (GCP Secret Manager, AWS Secrets Manager). Flag any API key older than 90 days. Use short-lived tokens where possible.",
|
|
873
|
+
checklist_ref: "C8",
|
|
874
|
+
file_patterns: ["**/.env*", "**/config/**", "**/secrets/**", "**/*.tf", "**/ci/**"],
|
|
875
|
+
framework: "baseline"
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
id_suffix: "redirect_url_allowlist",
|
|
879
|
+
category: "security",
|
|
880
|
+
summary: "All redirect URLs (OAuth callbacks, post-login, post-payment) MUST be validated against an explicit allowlist. Open redirects are forbidden.",
|
|
881
|
+
keywords: ["redirect", "open-redirect", "allowlist", "callback", "oauth", "return-url", "phishing"],
|
|
882
|
+
severity: "critical",
|
|
883
|
+
action: "Validate every redirect target against a hardcoded allowlist of trusted domains/paths. Reject absolute URLs to unknown hosts. Never pass user-controlled redirect targets without validation.",
|
|
884
|
+
checklist_ref: "D9",
|
|
885
|
+
file_patterns: ["**/api/auth/**", "**/api/login*", "**/api/callback*", "**/api/checkout*", "**/middleware/**"],
|
|
886
|
+
framework: "baseline"
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
id_suffix: "sensitive_tokens_not_in_urls",
|
|
890
|
+
category: "security",
|
|
891
|
+
summary: "Auth/session/API tokens MUST NOT be passed in URL query parameters during redirects. Sensitive tokens in URLs leak via logs, browser history, and referrers.",
|
|
892
|
+
keywords: ["token", "query-param", "callback", "returnUrl", "redirect", "referrer", "url-leakage"],
|
|
893
|
+
severity: "critical",
|
|
894
|
+
action: "Use Authorization headers or httpOnly cookies for token transport. Validate callback/return URLs against an allowlist and never append bearer/session tokens to redirected URLs.",
|
|
895
|
+
checklist_ref: "D11",
|
|
896
|
+
file_patterns: ["**/auth/**", "**/api/auth/**", "**/api/**/checkout*", "**/mcp-auth*", "**/session/**", "**/middleware/**"],
|
|
897
|
+
framework: "baseline"
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
id_suffix: "no_secrets_in_query_params",
|
|
901
|
+
category: "security",
|
|
902
|
+
summary: "Secrets (revalidation secrets, API secrets, webhook secrets) MUST NOT be transported via URL query parameters. Query-string secrets are leaked through logs, referrers, and browser history.",
|
|
903
|
+
keywords: ["secret", "query-param", "url", "revalidate", "webhook", "referrer", "leakage"],
|
|
904
|
+
severity: "critical",
|
|
905
|
+
action: "Accept secrets only via headers or signed request bodies. Reject secret-bearing query params in production endpoints. Rotate any secret previously sent in URLs.",
|
|
906
|
+
checklist_ref: "D12",
|
|
907
|
+
file_patterns: ["**/api/**", "**/webhooks/**", "**/revalidate/**", "**/middleware/**"],
|
|
908
|
+
framework: "baseline"
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
id_suffix: "no_state_change_get_cookie_auth",
|
|
912
|
+
category: "security",
|
|
913
|
+
summary: "State-changing operations MUST NOT be reachable via GET when cookie authentication is accepted. GET + cookie auth creates CSRF risk.",
|
|
914
|
+
keywords: ["csrf", "get", "state-change", "cookie-auth", "origin-check", "referer", "method-safety"],
|
|
915
|
+
severity: "critical",
|
|
916
|
+
action: "Use POST/PUT/DELETE for side effects. If GET fallback is unavoidable, enforce strict same-origin checks (Origin/Referer/sec-fetch-site) and avoid cookie-based auth fallback where possible.",
|
|
917
|
+
checklist_ref: "D13",
|
|
918
|
+
file_patterns: ["**/api/**", "**/auth/**", "**/checkout/**", "**/middleware/**"],
|
|
919
|
+
framework: "baseline"
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
id_suffix: "ai_cost_caps",
|
|
923
|
+
category: "security",
|
|
924
|
+
summary: "AI/LLM API usage MUST have cost caps enforced in code (token limits, request budgets) and in provider dashboards (spending alerts, hard limits).",
|
|
925
|
+
keywords: ["ai", "llm", "cost-cap", "budget", "token-limit", "spending", "billing-alert"],
|
|
926
|
+
severity: "warning",
|
|
927
|
+
action: "Set max_tokens on every LLM call. Implement per-user and per-request budget limits. Configure spending alerts and hard caps in provider dashboards (OpenAI, Vertex AI, Anthropic).",
|
|
928
|
+
checklist_ref: "F4",
|
|
929
|
+
file_patterns: ["**/ai/**", "**/llm/**", "**/agents/**", "**/orchestrator/**", "**/config/**"],
|
|
930
|
+
framework: "baseline"
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
id_suffix: "webhook_signature_verification",
|
|
934
|
+
category: "security",
|
|
935
|
+
summary: "Inbound webhooks (Stripe, payment providers, third-party events) MUST verify the request signature before processing. Unverified webhooks are a fraud vector.",
|
|
936
|
+
keywords: ["webhook", "signature", "stripe", "hmac", "verify", "payment", "event"],
|
|
937
|
+
severity: "critical",
|
|
938
|
+
action: "Verify webhook signatures using the provider's SDK (e.g., stripe.webhooks.constructEvent). Reject requests with missing or invalid signatures. Use raw body for signature verification.",
|
|
939
|
+
checklist_ref: "D10",
|
|
940
|
+
file_patterns: ["**/api/webhook*", "**/api/stripe*", "**/api/payment*", "**/webhooks/**"],
|
|
941
|
+
framework: "baseline"
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
id_suffix: "critical_action_audit_logging",
|
|
945
|
+
category: "security",
|
|
946
|
+
summary: "Critical actions (account deletion, role changes, payment events, data exports, permission escalation) MUST be logged with actor, timestamp, and affected resource.",
|
|
947
|
+
keywords: ["audit-log", "deletion", "role-change", "payment", "export", "logging", "accountability"],
|
|
948
|
+
severity: "critical",
|
|
949
|
+
action: "Log all destructive and sensitive operations with structured data (who, what, when, which resource). Store audit logs in append-only / tamper-evident storage. Set up alerts for anomalous patterns.",
|
|
950
|
+
checklist_ref: "E5",
|
|
951
|
+
file_patterns: ["**/api/**", "**/admin/**", "**/auth/**", "**/logger/**", "**/middleware/**"],
|
|
952
|
+
framework: "baseline"
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
id_suffix: "session_lifetime_limits",
|
|
956
|
+
category: "security",
|
|
957
|
+
summary: "Session and JWT lifetimes MUST be bounded. JWTs should not exceed 7 days. Refresh token rotation MUST be implemented to limit the blast radius of stolen tokens.",
|
|
958
|
+
keywords: ["session", "jwt", "lifetime", "expiry", "refresh-token", "rotation", "max-age"],
|
|
959
|
+
severity: "warning",
|
|
960
|
+
action: "Set JWT maxAge <= 7 days. Implement refresh token rotation (one-time-use refresh tokens). Expire idle sessions. Invalidate all tokens on password change.",
|
|
961
|
+
checklist_ref: "A9",
|
|
962
|
+
file_patterns: ["**/auth/**", "**/api/session*", "**/api/login*", "**/middleware/**", "**/config/**"],
|
|
963
|
+
framework: "baseline"
|
|
964
|
+
},
|
|
965
|
+
{
|
|
966
|
+
id_suffix: "password_reset_token_expiry",
|
|
967
|
+
category: "security",
|
|
968
|
+
summary: "Password reset tokens/links MUST be single-use and time-limited. Expired or reused reset tokens must fail closed.",
|
|
969
|
+
keywords: ["password-reset", "token", "expiry", "single-use", "account-takeover"],
|
|
970
|
+
severity: "critical",
|
|
971
|
+
action: "Enforce reset token TTL and one-time use semantics. Invalidate outstanding reset tokens after successful password change.",
|
|
972
|
+
checklist_ref: "A10",
|
|
973
|
+
file_patterns: ["**/auth/**", "**/api/reset*", "**/api/forgot*", "**/api/password*"],
|
|
974
|
+
framework: "baseline"
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
id_suffix: "backup_testing",
|
|
978
|
+
category: "stability",
|
|
979
|
+
summary: "Backups MUST be automated and periodically tested with a restore verification process. An untested backup is equivalent to no backup.",
|
|
980
|
+
keywords: ["backup", "restore", "disaster-recovery", "test", "verification", "snapshot"],
|
|
981
|
+
severity: "warning",
|
|
982
|
+
action: "Automate database and storage backups on a schedule. Test restores at least quarterly. Document and verify the restore procedure. Alert on backup failures.",
|
|
983
|
+
checklist_ref: "E6",
|
|
984
|
+
file_patterns: ["**/*.tf", "**/backup*", "**/config/**", "**/ci/**", "**/scripts/**"],
|
|
985
|
+
framework: "baseline"
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
id_suffix: "environment_separation",
|
|
989
|
+
category: "infrastructure",
|
|
990
|
+
summary: "Test and production environments MUST be fully separated (distinct credentials, databases, and API keys). Webhooks in test MUST NOT trigger production systems.",
|
|
991
|
+
keywords: ["environment", "staging", "production", "separation", "isolation", "test", "webhook"],
|
|
992
|
+
severity: "warning",
|
|
993
|
+
action: "Use distinct GCP/AWS projects or accounts for test vs production. Never share database instances, API keys, or webhook endpoints across environments. Verify webhook URLs are environment-scoped.",
|
|
994
|
+
checklist_ref: "E7",
|
|
995
|
+
file_patterns: ["**/.env*", "**/config/**", "**/*.tf", "**/docker-compose*", "**/ci/**"],
|
|
996
|
+
framework: "baseline"
|
|
997
|
+
}
|
|
998
|
+
];
|
|
999
|
+
var SCALABILITY_CONSTRAINTS = [
|
|
1000
|
+
{
|
|
1001
|
+
id_suffix: "n_plus_one_queries",
|
|
1002
|
+
category: "performance",
|
|
1003
|
+
summary: "Data fetching MUST use JOINs, eager loading, or batch queries. Avoid fetching related records in loops (N+1 pattern).",
|
|
1004
|
+
keywords: ["n+1", "query", "loop", "eager-loading", "join", "batch", "dataloader"],
|
|
1005
|
+
severity: "warning",
|
|
1006
|
+
action: "Replace loop-based DB fetches with JOINs, includes/eager-loading, or DataLoader-style batching. Profile query counts per request.",
|
|
1007
|
+
checklist_ref: "I1",
|
|
1008
|
+
file_patterns: ["**/api/**", "**/models/**", "**/repositories/**", "**/services/**", "**/db/**"],
|
|
1009
|
+
framework: "baseline"
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
id_suffix: "database_indexing",
|
|
1013
|
+
category: "scalability",
|
|
1014
|
+
summary: "Columns used in WHERE, ORDER BY, and JOIN clauses MUST have database indexes. Use composite indexes for multi-column filters.",
|
|
1015
|
+
keywords: ["index", "database", "query-performance", "composite-index", "explain"],
|
|
1016
|
+
severity: "warning",
|
|
1017
|
+
action: "Add indexes on all filtered/sorted columns. Use EXPLAIN to verify index usage. Add composite indexes for compound queries.",
|
|
1018
|
+
checklist_ref: "I2",
|
|
1019
|
+
file_patterns: ["**/migrations/**", "**/schema*", "**/models/**", "**/*.prisma", "**/*.sql"],
|
|
1020
|
+
framework: "baseline"
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
id_suffix: "algorithmic_complexity",
|
|
1024
|
+
category: "performance",
|
|
1025
|
+
summary: "Nested loops over large datasets MUST be reviewed. Prefer O(n) or O(log n) lookups (Maps, Sets, binary search) over O(n^2) scans.",
|
|
1026
|
+
keywords: ["complexity", "O(n^2)", "nested-loop", "performance", "map", "set", "algorithm"],
|
|
1027
|
+
severity: "warning",
|
|
1028
|
+
action: "Replace nested array scans with Map/Set lookups. Use indexing or sorting + binary search for large collections. Profile hot paths.",
|
|
1029
|
+
checklist_ref: "I3",
|
|
1030
|
+
file_patterns: ["**/utils/**", "**/lib/**", "**/services/**", "**/api/**"],
|
|
1031
|
+
framework: "baseline"
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
id_suffix: "memory_leak_prevention",
|
|
1035
|
+
category: "stability",
|
|
1036
|
+
summary: "Database connections, event listeners, timers, and streams MUST be explicitly cleaned up. Use dispose/close patterns and AbortControllers.",
|
|
1037
|
+
keywords: ["memory-leak", "listener", "timer", "connection", "dispose", "cleanup", "abort"],
|
|
1038
|
+
severity: "warning",
|
|
1039
|
+
action: "Add cleanup in useEffect returns, finally blocks, and process signal handlers. Use AbortController for cancellable operations. Close DB connections in pool.",
|
|
1040
|
+
checklist_ref: "I4",
|
|
1041
|
+
file_patterns: ["**/hooks/**", "**/services/**", "**/db/**", "**/workers/**", "**/lib/**"],
|
|
1042
|
+
framework: "baseline"
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
id_suffix: "pagination_required",
|
|
1046
|
+
category: "scalability",
|
|
1047
|
+
summary: "Queries returning unbounded result sets MUST use cursor or offset pagination. Never load entire tables into memory.",
|
|
1048
|
+
keywords: ["pagination", "cursor", "offset", "limit", "unbounded", "streaming"],
|
|
1049
|
+
severity: "critical",
|
|
1050
|
+
action: "Add LIMIT/offset or cursor-based pagination to all list endpoints. Stream large exports instead of buffering. Enforce max page size.",
|
|
1051
|
+
checklist_ref: "I5",
|
|
1052
|
+
file_patterns: ["**/api/**", "**/queries/**", "**/repositories/**", "**/db/**"],
|
|
1053
|
+
framework: "baseline"
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
id_suffix: "async_io_offloading",
|
|
1057
|
+
category: "performance",
|
|
1058
|
+
summary: "CPU-intensive or blocking I/O operations MUST be offloaded from the main thread/event loop. Use worker threads, queues, or background jobs.",
|
|
1059
|
+
keywords: ["blocking", "sync", "worker", "thread", "event-loop", "offload", "queue"],
|
|
1060
|
+
severity: "warning",
|
|
1061
|
+
action: "Move image processing, PDF generation, and heavy computation to worker threads or background queues. Never use sync file/network I/O on request path.",
|
|
1062
|
+
checklist_ref: "I6",
|
|
1063
|
+
file_patterns: ["**/api/**", "**/workers/**", "**/jobs/**", "**/services/**"],
|
|
1064
|
+
framework: "baseline"
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
id_suffix: "connection_pooling",
|
|
1068
|
+
category: "scalability",
|
|
1069
|
+
summary: "Database and HTTP clients MUST use connection pooling. One-connection-per-request patterns are forbidden at scale.",
|
|
1070
|
+
keywords: ["connection-pool", "pool", "database", "http-client", "keep-alive"],
|
|
1071
|
+
severity: "warning",
|
|
1072
|
+
action: "Configure connection pools for all DB clients. Reuse HTTP agents with keep-alive. Set pool size limits matching expected concurrency.",
|
|
1073
|
+
checklist_ref: "I7",
|
|
1074
|
+
file_patterns: ["**/db/**", "**/config/**", "**/lib/**", "**/services/**"],
|
|
1075
|
+
framework: "baseline"
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
id_suffix: "concurrency_bounds",
|
|
1079
|
+
category: "scalability",
|
|
1080
|
+
summary: "Worker threads, parallel API calls, and queue consumers MUST have bounded concurrency. Use semaphores or rate-limiting queues.",
|
|
1081
|
+
keywords: ["concurrency", "semaphore", "rate-limit", "worker", "parallel", "queue", "throttle"],
|
|
1082
|
+
severity: "warning",
|
|
1083
|
+
action: "Use p-limit, Bull queue concurrency settings, or semaphores for parallel operations. Never Promise.all on unbounded arrays of async work.",
|
|
1084
|
+
checklist_ref: "I8",
|
|
1085
|
+
file_patterns: ["**/workers/**", "**/jobs/**", "**/services/**", "**/api/**"],
|
|
1086
|
+
framework: "baseline"
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
id_suffix: "stateless_backend",
|
|
1090
|
+
category: "scalability",
|
|
1091
|
+
summary: "Application state MUST NOT be stored in process memory. Use external stores (Redis, DB) for sessions, caches, and shared state to enable horizontal scaling.",
|
|
1092
|
+
keywords: ["stateless", "session", "cache", "redis", "horizontal-scaling", "in-memory"],
|
|
1093
|
+
severity: "warning",
|
|
1094
|
+
action: "Move sessions to Redis/DB. Replace in-memory caches with Redis or Memcached. Ensure any instance can handle any request.",
|
|
1095
|
+
checklist_ref: "I9",
|
|
1096
|
+
file_patterns: ["**/server.*", "**/app.*", "**/config/**", "**/session/**", "**/cache/**"],
|
|
1097
|
+
framework: "baseline"
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
id_suffix: "single_source_schema",
|
|
1101
|
+
category: "stability",
|
|
1102
|
+
summary: "Data models and schemas MUST be defined once and shared (via codegen, shared types, or a schema registry). No redundant redefinition across layers.",
|
|
1103
|
+
keywords: ["schema", "single-source", "codegen", "data-drift", "duplication", "types"],
|
|
1104
|
+
severity: "warning",
|
|
1105
|
+
action: "Define schemas once (Prisma, Zod, protobuf, JSON Schema) and generate types for all layers. Remove duplicated type definitions across frontend/backend.",
|
|
1106
|
+
checklist_ref: "I10",
|
|
1107
|
+
file_patterns: ["**/models/**", "**/types/**", "**/schema*", "**/shared/**", "**/*.prisma"],
|
|
1108
|
+
framework: "baseline"
|
|
1109
|
+
}
|
|
1110
|
+
];
|
|
1111
|
+
var RELIABILITY_CONSTRAINTS = [
|
|
1112
|
+
{
|
|
1113
|
+
id_suffix: "verified_dependencies",
|
|
1114
|
+
category: "stability",
|
|
1115
|
+
summary: "All imported packages MUST exist in the registry and resolve correctly. Lock files must be committed. Flag phantom/hallucinated imports.",
|
|
1116
|
+
keywords: ["dependency", "phantom", "hallucinated", "import", "lockfile", "registry"],
|
|
1117
|
+
severity: "warning",
|
|
1118
|
+
action: "Run npm ls / pip check to verify all imports resolve. Commit lockfiles. Audit for AI-generated imports of non-existent packages.",
|
|
1119
|
+
checklist_ref: "J1",
|
|
1120
|
+
file_patterns: ["**/package*.json", "**/requirements*.txt", "**/*lock*", "**/imports/**"],
|
|
1121
|
+
framework: "baseline"
|
|
1122
|
+
},
|
|
1123
|
+
{
|
|
1124
|
+
id_suffix: "no_silent_failures",
|
|
1125
|
+
category: "stability",
|
|
1126
|
+
summary: "Catch blocks MUST log or re-throw errors. Empty catch blocks and swallowed promises are forbidden.",
|
|
1127
|
+
keywords: ["silent-failure", "empty-catch", "error-handling", "swallowed", "promise"],
|
|
1128
|
+
severity: "critical",
|
|
1129
|
+
action: "Audit all catch blocks for empty bodies. Add logging or re-throw. Add .catch() or try/catch to all async paths. Enable unhandledRejection handler.",
|
|
1130
|
+
checklist_ref: "J2",
|
|
1131
|
+
file_patterns: ["**/api/**", "**/services/**", "**/lib/**", "**/utils/**"],
|
|
1132
|
+
framework: "baseline"
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
id_suffix: "state_sync_discipline",
|
|
1136
|
+
category: "stability",
|
|
1137
|
+
summary: "Shared state MUST have a single source of truth. Derived/component state must sync via subscriptions or reactivity, not manual copies.",
|
|
1138
|
+
keywords: ["state", "sync", "source-of-truth", "stale", "derived", "store", "reactivity"],
|
|
1139
|
+
severity: "warning",
|
|
1140
|
+
action: "Consolidate state into stores (Redux, Zustand, context). Remove manual state copies. Use derived/computed values instead of duplicated state.",
|
|
1141
|
+
checklist_ref: "J3",
|
|
1142
|
+
file_patterns: ["**/store/**", "**/state/**", "**/context/**", "**/hooks/**", "**/components/**"],
|
|
1143
|
+
framework: "baseline"
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
id_suffix: "boundary_correctness",
|
|
1147
|
+
category: "stability",
|
|
1148
|
+
summary: "Array/collection operations MUST handle empty collections, off-by-one indexes, and boundary conditions. Use safe access patterns.",
|
|
1149
|
+
keywords: ["off-by-one", "boundary", "empty-array", "index", "optional-chaining", "bounds"],
|
|
1150
|
+
severity: "warning",
|
|
1151
|
+
action: "Use .at(), optional chaining, and nullish coalescing for array access. Add explicit length checks before index operations. Test with empty inputs.",
|
|
1152
|
+
checklist_ref: "J4",
|
|
1153
|
+
file_patterns: ["**/utils/**", "**/lib/**", "**/services/**", "**/api/**"],
|
|
1154
|
+
framework: "baseline"
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
id_suffix: "api_contract_validation",
|
|
1158
|
+
category: "stability",
|
|
1159
|
+
summary: "External API responses MUST be validated against a schema (zod, joi, type guard) before use. Do not assume response shape matches documentation.",
|
|
1160
|
+
keywords: ["api-contract", "validation", "schema", "response", "type-guard", "external"],
|
|
1161
|
+
severity: "warning",
|
|
1162
|
+
action: "Add runtime validation (zod.parse, joi.validate) on all external API responses. Handle validation failures gracefully with fallbacks or retries.",
|
|
1163
|
+
checklist_ref: "J5",
|
|
1164
|
+
file_patterns: ["**/services/**", "**/integrations/**", "**/api/**", "**/lib/**"],
|
|
1165
|
+
framework: "baseline"
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
id_suffix: "defensive_edge_cases",
|
|
1169
|
+
category: "stability",
|
|
1170
|
+
summary: "Functions MUST handle null, undefined, empty arrays, unauthenticated users, network timeouts, and malformed input. Fail gracefully with meaningful errors.",
|
|
1171
|
+
keywords: ["edge-case", "null", "undefined", "timeout", "defensive", "graceful-degradation"],
|
|
1172
|
+
severity: "warning",
|
|
1173
|
+
action: "Add null/undefined guards at function boundaries. Set timeouts on network requests. Return meaningful error objects instead of crashing.",
|
|
1174
|
+
checklist_ref: "J6",
|
|
1175
|
+
file_patterns: ["**/api/**", "**/services/**", "**/utils/**", "**/lib/**"],
|
|
1176
|
+
framework: "baseline"
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
id_suffix: "function_size_limit",
|
|
1180
|
+
category: "stability",
|
|
1181
|
+
summary: "Functions exceeding ~100 lines SHOULD be decomposed. God functions that mix concerns are a testability and maintainability risk.",
|
|
1182
|
+
keywords: ["god-function", "decomposition", "single-responsibility", "testability", "complexity"],
|
|
1183
|
+
severity: "info",
|
|
1184
|
+
action: "Break functions over 100 lines into focused helpers. Extract validation, transformation, and I/O into separate functions. Aim for single responsibility.",
|
|
1185
|
+
checklist_ref: "J7",
|
|
1186
|
+
file_patterns: ["**/api/**", "**/services/**", "**/utils/**", "**/lib/**"],
|
|
1187
|
+
framework: "baseline"
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
id_suffix: "idiomatic_patterns",
|
|
1191
|
+
category: "stability",
|
|
1192
|
+
summary: "Code SHOULD follow language-specific idioms and current best practices. Avoid legacy patterns (var, callbacks where async/await is available, class components where hooks exist).",
|
|
1193
|
+
keywords: ["idiomatic", "best-practice", "legacy", "var", "callback", "modernize"],
|
|
1194
|
+
severity: "info",
|
|
1195
|
+
action: "Replace var with const/let. Convert callbacks to async/await. Use hooks instead of class components. Follow framework-specific conventions.",
|
|
1196
|
+
checklist_ref: "J8",
|
|
1197
|
+
file_patterns: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
|
|
1198
|
+
framework: "baseline"
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
id_suffix: "refactor_safety",
|
|
1202
|
+
category: "stability",
|
|
1203
|
+
summary: "Changes to shared modules MUST account for all call sites. Renaming, removing, or changing function signatures without updating consumers is a regression vector.",
|
|
1204
|
+
keywords: ["refactor", "regression", "call-site", "breaking-change", "rename", "shared-module"],
|
|
1205
|
+
severity: "warning",
|
|
1206
|
+
action: "Use IDE-assisted rename refactoring. Run TypeScript compiler and tests after signature changes. Search for all usages before modifying exported functions.",
|
|
1207
|
+
checklist_ref: "J9",
|
|
1208
|
+
file_patterns: ["**/lib/**", "**/shared/**", "**/utils/**", "**/types/**"],
|
|
1209
|
+
framework: "baseline"
|
|
1210
|
+
},
|
|
1211
|
+
{
|
|
1212
|
+
id_suffix: "inline_documentation",
|
|
1213
|
+
category: "stability",
|
|
1214
|
+
summary: "Complex business logic, regex, algorithms, and non-obvious code paths SHOULD include explanatory comments. AI-generated code without context creates maintenance debt.",
|
|
1215
|
+
keywords: ["documentation", "comments", "regex", "algorithm", "maintenance", "readability"],
|
|
1216
|
+
severity: "info",
|
|
1217
|
+
action: "Add JSDoc/docstrings to complex functions. Comment non-obvious regex patterns. Document business rules and trade-offs. Flag AI-generated code without explanation.",
|
|
1218
|
+
checklist_ref: "J10",
|
|
1219
|
+
file_patterns: ["**/utils/**", "**/lib/**", "**/services/**", "**/api/**"],
|
|
1220
|
+
framework: "baseline"
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
id_suffix: "startup_env_schema_validation",
|
|
1224
|
+
category: "stability",
|
|
1225
|
+
summary: "Runtime environment variables MUST be validated against an explicit schema at startup, and app boot must fail fast on invalid critical config.",
|
|
1226
|
+
keywords: ["env", "startup", "schema", "validation", "fail-fast", "configuration"],
|
|
1227
|
+
severity: "critical",
|
|
1228
|
+
action: "Create startup env schema checks for server and public runtime variables. Crash startup when required production config is missing or malformed.",
|
|
1229
|
+
checklist_ref: "J11",
|
|
1230
|
+
file_patterns: ["**/config/**", "**/env/**", "**/server/**", "**/.env*"],
|
|
1231
|
+
framework: "baseline"
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
id_suffix: "ui_error_boundaries",
|
|
1235
|
+
category: "stability",
|
|
1236
|
+
summary: "Critical user-facing UI surfaces MUST be wrapped in error boundaries to prevent full-app white screens during component crashes.",
|
|
1237
|
+
keywords: ["error-boundary", "react", "ui", "crash", "fallback", "reliability"],
|
|
1238
|
+
severity: "warning",
|
|
1239
|
+
action: "Add error boundaries around major routes/layouts and high-risk widgets. Provide fallback UI and telemetry when boundaries catch errors.",
|
|
1240
|
+
checklist_ref: "J12",
|
|
1241
|
+
file_patterns: ["**/components/**", "**/pages/**", "**/app/**", "**/*error*"],
|
|
1242
|
+
framework: "baseline"
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
id_suffix: "health_readiness_endpoints",
|
|
1246
|
+
category: "stability",
|
|
1247
|
+
summary: "Services MUST expose dedicated liveness and readiness endpoints (e.g., /api/health and /api/readyz) for monitoring and deployment safety checks.",
|
|
1248
|
+
keywords: ["health", "readyz", "liveness", "readiness", "monitoring", "probe"],
|
|
1249
|
+
severity: "critical",
|
|
1250
|
+
action: "Implement lightweight health/readiness endpoints with no sensitive payload data. Integrate endpoints into uptime monitoring and deployment probes.",
|
|
1251
|
+
checklist_ref: "J13",
|
|
1252
|
+
file_patterns: ["**/api/health*", "**/api/ready*", "**/monitoring/**", "**/deploy/**"],
|
|
1253
|
+
framework: "baseline"
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
id_suffix: "structured_production_logging",
|
|
1257
|
+
category: "stability",
|
|
1258
|
+
summary: "Production logging MUST be structured and include correlation/request IDs, with automatic redaction of tokens, API keys, and credentials.",
|
|
1259
|
+
keywords: ["logging", "structured", "correlation-id", "request-id", "redaction", "observability"],
|
|
1260
|
+
severity: "critical",
|
|
1261
|
+
action: "Emit JSON logs in production and propagate request/correlation IDs across handlers and background jobs. Apply secret-redaction middleware before log emission.",
|
|
1262
|
+
checklist_ref: "J14",
|
|
1263
|
+
file_patterns: ["**/logger/**", "**/api/**", "**/middleware/**", "**/monitoring/**"],
|
|
1264
|
+
framework: "baseline"
|
|
1265
|
+
},
|
|
1266
|
+
{
|
|
1267
|
+
id_suffix: "typed_ai_generated_code",
|
|
1268
|
+
category: "stability",
|
|
1269
|
+
summary: "AI-generated production code MUST use TypeScript (or equivalent static typing) and pass strict type-check gates before merge.",
|
|
1270
|
+
keywords: ["ai-generated", "typescript", "typing", "typecheck", "ci-gate", "quality"],
|
|
1271
|
+
severity: "warning",
|
|
1272
|
+
action: "Require strict type-check in CI for generated code changes and block merges on type errors. Prefer typed templates for agent-generated modules.",
|
|
1273
|
+
checklist_ref: "J15",
|
|
1274
|
+
file_patterns: ["**/*.ts", "**/*.tsx", "**/ai/**", "**/agents/**", "**/.github/**"],
|
|
1275
|
+
framework: "baseline"
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
id_suffix: "async_email_dispatch",
|
|
1279
|
+
category: "performance",
|
|
1280
|
+
summary: "Transactional emails SHOULD be dispatched asynchronously (queue/background workers) so request handlers do not block on provider latency.",
|
|
1281
|
+
keywords: ["email", "async", "queue", "worker", "latency", "smtp", "request-path"],
|
|
1282
|
+
severity: "warning",
|
|
1283
|
+
action: "Move email delivery to async jobs/queues and return request responses before provider completion. Add retry/backoff for transient send failures.",
|
|
1284
|
+
checklist_ref: "J16",
|
|
1285
|
+
file_patterns: ["**/api/**", "**/jobs/**", "**/workers/**", "**/email/**", "**/notifications/**"],
|
|
1286
|
+
framework: "baseline"
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
id_suffix: "cdn_media_delivery",
|
|
1290
|
+
category: "performance",
|
|
1291
|
+
summary: "User-uploaded media MUST be stored in object storage and delivered through CDN caching, not served directly from app servers.",
|
|
1292
|
+
keywords: ["cdn", "media", "uploads", "object-storage", "cache", "bandwidth"],
|
|
1293
|
+
severity: "warning",
|
|
1294
|
+
action: "Store uploads in object storage (S3/GCS/etc.) and serve via CDN URLs with cache headers. Keep app servers out of large media delivery paths.",
|
|
1295
|
+
checklist_ref: "J17",
|
|
1296
|
+
file_patterns: ["**/api/upload*", "**/storage/**", "**/media/**", "**/cdn/**", "**/config/**"],
|
|
1297
|
+
framework: "baseline"
|
|
1298
|
+
}
|
|
1299
|
+
];
|
|
1300
|
+
var IAC_CONSTRAINTS = [
|
|
1301
|
+
{
|
|
1302
|
+
id_suffix: "iac_least_privilege_iam",
|
|
1303
|
+
category: "infrastructure",
|
|
1304
|
+
summary: 'IAM policies MUST follow least privilege. No wildcard actions (Action: "*") or wildcard resources (Resource: "*") in production.',
|
|
1305
|
+
keywords: ["iam", "least-privilege", "terraform", "cloudformation", "policy", "wildcard"],
|
|
1306
|
+
severity: "critical",
|
|
1307
|
+
action: "Audit all IAM policy documents. Replace wildcard actions/resources with specific service:action and ARN scoping. Use condition keys where possible.",
|
|
1308
|
+
checklist_ref: "K1",
|
|
1309
|
+
file_patterns: ["**/*.tf", "**/iam/**", "**/policies/**", "**/cloudformation/**"],
|
|
1310
|
+
framework: "baseline"
|
|
1311
|
+
},
|
|
1312
|
+
{
|
|
1313
|
+
id_suffix: "iac_no_public_storage",
|
|
1314
|
+
category: "infrastructure",
|
|
1315
|
+
summary: "Storage buckets and blobs MUST NOT be publicly accessible unless explicitly required and documented. No public-read ACLs without business justification.",
|
|
1316
|
+
keywords: ["s3", "gcs", "storage", "public-read", "acl", "bucket", "blob"],
|
|
1317
|
+
severity: "critical",
|
|
1318
|
+
action: "Remove public ACLs from all storage resources. Enable bucket-level public access prevention. Document any intentional public assets with approval.",
|
|
1319
|
+
checklist_ref: "K2",
|
|
1320
|
+
file_patterns: ["**/*.tf", "**/storage/**", "**/s3/**", "**/gcs/**"],
|
|
1321
|
+
framework: "baseline"
|
|
1322
|
+
},
|
|
1323
|
+
{
|
|
1324
|
+
id_suffix: "iac_restricted_ingress",
|
|
1325
|
+
category: "infrastructure",
|
|
1326
|
+
summary: "Network security groups MUST restrict ingress to specific ports and source CIDRs. No 0.0.0.0/0 on non-443 ports.",
|
|
1327
|
+
keywords: ["security-group", "firewall", "ingress", "cidr", "network", "port"],
|
|
1328
|
+
severity: "critical",
|
|
1329
|
+
action: "Review all security group / firewall rules. Restrict SSH (22) to VPN/bastion CIDRs. Close all non-essential ports. Allow 0.0.0.0/0 only for 443 (HTTPS).",
|
|
1330
|
+
checklist_ref: "K3",
|
|
1331
|
+
file_patterns: ["**/*.tf", "**/security-groups/**", "**/firewall/**", "**/network/**"],
|
|
1332
|
+
framework: "baseline"
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
id_suffix: "iac_encryption_at_rest",
|
|
1336
|
+
category: "infrastructure",
|
|
1337
|
+
summary: "Databases and storage MUST have encryption at rest explicitly enabled in IaC configuration.",
|
|
1338
|
+
keywords: ["encryption", "at-rest", "kms", "aes", "database", "storage", "rds"],
|
|
1339
|
+
severity: "warning",
|
|
1340
|
+
action: "Explicitly set encryption_at_rest or kms_key_id on all database and storage resources. Do not rely on provider defaults.",
|
|
1341
|
+
checklist_ref: "K4",
|
|
1342
|
+
file_patterns: ["**/*.tf", "**/database/**", "**/rds/**", "**/storage/**"],
|
|
1343
|
+
framework: "baseline"
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
id_suffix: "iac_docker_nonroot",
|
|
1347
|
+
category: "infrastructure",
|
|
1348
|
+
summary: "Dockerfiles MUST specify a non-root USER. Running containers as root is forbidden in production.",
|
|
1349
|
+
keywords: ["docker", "dockerfile", "user", "root", "container", "nonroot"],
|
|
1350
|
+
severity: "warning",
|
|
1351
|
+
action: "Add a USER directive to all Dockerfiles. Create a dedicated app user. Set file ownership appropriately for the non-root user.",
|
|
1352
|
+
checklist_ref: "K5",
|
|
1353
|
+
file_patterns: ["**/Dockerfile*", "**/docker/**"],
|
|
1354
|
+
framework: "baseline"
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
id_suffix: "iac_docker_pinned_tags",
|
|
1358
|
+
category: "infrastructure",
|
|
1359
|
+
summary: "Docker images MUST use pinned version tags (e.g., node:20.11-slim), not :latest or untagged references.",
|
|
1360
|
+
keywords: ["docker", "image", "tag", "latest", "pinned", "version", "reproducible"],
|
|
1361
|
+
severity: "warning",
|
|
1362
|
+
action: "Replace all :latest tags with specific version tags. Pin base images to digest hashes for maximum reproducibility in CI.",
|
|
1363
|
+
checklist_ref: "K6",
|
|
1364
|
+
file_patterns: ["**/Dockerfile*", "**/docker-compose*"],
|
|
1365
|
+
framework: "baseline"
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
id_suffix: "iac_no_secrets_in_build",
|
|
1369
|
+
category: "infrastructure",
|
|
1370
|
+
summary: "Secrets MUST NOT be passed via Docker build args, ENV directives, or docker-compose environment variables. Use secret managers or mounted secrets.",
|
|
1371
|
+
keywords: ["docker", "secret", "build-arg", "env", "credential", "compose"],
|
|
1372
|
+
severity: "critical",
|
|
1373
|
+
action: "Remove all secrets from Dockerfiles and docker-compose.yml. Use Docker BuildKit secrets, mounted volume secrets, or external secret managers.",
|
|
1374
|
+
checklist_ref: "K7",
|
|
1375
|
+
file_patterns: ["**/Dockerfile*", "**/docker-compose*", "**/.env*"],
|
|
1376
|
+
framework: "baseline"
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
id_suffix: "iac_k8s_resource_limits",
|
|
1380
|
+
category: "infrastructure",
|
|
1381
|
+
summary: "Kubernetes pods MUST have CPU and memory resource limits defined. Pods MUST NOT run with privileged: true.",
|
|
1382
|
+
keywords: ["kubernetes", "k8s", "resources", "limits", "privileged", "pod", "container"],
|
|
1383
|
+
severity: "warning",
|
|
1384
|
+
action: "Add resources.limits.cpu and resources.limits.memory to all pod specs. Remove privileged: true. Set securityContext.runAsNonRoot: true.",
|
|
1385
|
+
checklist_ref: "K8",
|
|
1386
|
+
file_patterns: ["**/k8s/**", "**/kubernetes/**", "**/helm/**", "**/manifests/**"],
|
|
1387
|
+
framework: "baseline"
|
|
1388
|
+
}
|
|
1389
|
+
];
|
|
1390
|
+
var SOC2_CONSTRAINTS = [
|
|
1391
|
+
{
|
|
1392
|
+
id_suffix: "soc2_cc6_access_control",
|
|
1393
|
+
category: "compliance",
|
|
1394
|
+
summary: "[SOC 2 CC6.1] Logical access to systems and data MUST be restricted to authorized individuals using unique credentials.",
|
|
1395
|
+
keywords: ["soc2", "access-control", "unique-credentials", "identity"],
|
|
1396
|
+
severity: "critical",
|
|
1397
|
+
action: "Ensure every user has unique credentials. No shared accounts. Enforce MFA for administrative access.",
|
|
1398
|
+
checklist_ref: "SOC2-CC6.1",
|
|
1399
|
+
file_patterns: ["**/auth/**", "**/api/session/**", "**/middleware/**"],
|
|
1400
|
+
framework: "soc2"
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
id_suffix: "soc2_cc6_least_privilege",
|
|
1404
|
+
category: "compliance",
|
|
1405
|
+
summary: "[SOC 2 CC6.3] Access MUST follow least-privilege principle. Users receive only the permissions needed for their role.",
|
|
1406
|
+
keywords: ["soc2", "least-privilege", "rbac", "permissions", "roles"],
|
|
1407
|
+
severity: "critical",
|
|
1408
|
+
action: "Implement RBAC. Audit that admin endpoints check role. Verify no default-admin patterns.",
|
|
1409
|
+
checklist_ref: "SOC2-CC6.3",
|
|
1410
|
+
file_patterns: ["**/api/admin/**", "**/middleware/**", "**/auth/**", "**/organizations/**"],
|
|
1411
|
+
framework: "soc2"
|
|
1412
|
+
},
|
|
1413
|
+
{
|
|
1414
|
+
id_suffix: "soc2_cc7_monitoring",
|
|
1415
|
+
category: "compliance",
|
|
1416
|
+
summary: "[SOC 2 CC7.2] Security events (failed logins, permission changes, data exports) MUST be logged and monitored.",
|
|
1417
|
+
keywords: ["soc2", "monitoring", "security-events", "alerting", "siem"],
|
|
1418
|
+
severity: "critical",
|
|
1419
|
+
action: "Log all auth events, admin actions, and data exports. Set up alerts for anomalous patterns.",
|
|
1420
|
+
checklist_ref: "SOC2-CC7.2",
|
|
1421
|
+
file_patterns: ["**/api/**", "**/auth/**", "**/admin/**", "**/logger/**"],
|
|
1422
|
+
framework: "soc2"
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
id_suffix: "soc2_cc7_incident_response",
|
|
1426
|
+
category: "compliance",
|
|
1427
|
+
summary: "[SOC 2 CC7.3] An incident response plan MUST exist. Security incidents must be detected, reported, and remediated.",
|
|
1428
|
+
keywords: ["soc2", "incident-response", "breach", "notification", "remediation"],
|
|
1429
|
+
severity: "warning",
|
|
1430
|
+
action: "Document incident response procedures. Implement error alerting. Define escalation paths.",
|
|
1431
|
+
checklist_ref: "SOC2-CC7.3",
|
|
1432
|
+
file_patterns: ["**/error*", "**/sentry/**", "**/monitoring/**"],
|
|
1433
|
+
framework: "soc2"
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
id_suffix: "soc2_cc8_change_management",
|
|
1437
|
+
category: "compliance",
|
|
1438
|
+
summary: "[SOC 2 CC8.1] Changes to production MUST follow a documented change management process with review and approval.",
|
|
1439
|
+
keywords: ["soc2", "change-management", "ci-cd", "deployment", "review"],
|
|
1440
|
+
severity: "warning",
|
|
1441
|
+
action: "Enforce PR reviews. Use CI/CD with gated deployments. Log all production changes.",
|
|
1442
|
+
checklist_ref: "SOC2-CC8.1",
|
|
1443
|
+
file_patterns: ["**/.github/**", "**/ci/**", "**/deploy*"],
|
|
1444
|
+
framework: "soc2"
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
id_suffix: "soc2_cc6_encryption",
|
|
1448
|
+
category: "compliance",
|
|
1449
|
+
summary: "[SOC 2 CC6.7] Data MUST be encrypted in transit (TLS 1.2+) and sensitive data encrypted at rest.",
|
|
1450
|
+
keywords: ["soc2", "encryption", "tls", "at-rest", "transit"],
|
|
1451
|
+
severity: "critical",
|
|
1452
|
+
action: "Enforce HTTPS everywhere. Encrypt PII/sensitive fields at rest. Verify TLS configuration.",
|
|
1453
|
+
checklist_ref: "SOC2-CC6.7",
|
|
1454
|
+
file_patterns: ["**/config/**", "**/security/**", "**/crypto/**"],
|
|
1455
|
+
framework: "soc2"
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
id_suffix: "soc2_cc9_vendor_risk",
|
|
1459
|
+
category: "compliance",
|
|
1460
|
+
summary: "[SOC 2 CC9.2] Third-party vendor risks MUST be assessed. AI providers must have data processing agreements.",
|
|
1461
|
+
keywords: ["soc2", "vendor", "third-party", "dpa", "subprocessor"],
|
|
1462
|
+
severity: "warning",
|
|
1463
|
+
action: "Maintain vendor inventory. Verify DPAs with AI/cloud providers. Review data retention policies.",
|
|
1464
|
+
checklist_ref: "SOC2-CC9.2",
|
|
1465
|
+
file_patterns: ["**/ai/**", "**/llm/**", "**/integrations/**"],
|
|
1466
|
+
framework: "soc2"
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
id_suffix: "soc2_audit_logging",
|
|
1470
|
+
category: "compliance",
|
|
1471
|
+
summary: "[SOC 2 CC7.1] Audit logs MUST capture who did what, when, and to which resource. Logs must be tamper-evident.",
|
|
1472
|
+
keywords: ["soc2", "audit", "logging", "compliance", "immutable"],
|
|
1473
|
+
severity: "critical",
|
|
1474
|
+
action: "Structured audit logging for auth, data access, and admin actions. Store logs immutably.",
|
|
1475
|
+
checklist_ref: "SOC2-CC7.1",
|
|
1476
|
+
file_patterns: ["**/api/**", "**/auth/**", "**/admin/**", "**/logger/**"],
|
|
1477
|
+
framework: "soc2"
|
|
1478
|
+
}
|
|
1479
|
+
];
|
|
1480
|
+
var BLUEPRINT_RULES = [
|
|
1481
|
+
{
|
|
1482
|
+
trigger: (eco) => eco.frameworks.some((f) => /next/i.test(f)),
|
|
1483
|
+
constraints: [
|
|
1484
|
+
{
|
|
1485
|
+
id_suffix: "nextjs_ssr_secrets",
|
|
1486
|
+
category: "security",
|
|
1487
|
+
summary: "Server-only secrets MUST NOT leak into client bundles via getStaticProps/getServerSideProps return values.",
|
|
1488
|
+
keywords: ["next.js", "ssr", "secrets", "props", "client-bundle"],
|
|
1489
|
+
severity: "critical",
|
|
1490
|
+
action: "Audit getStaticProps/getServerSideProps for secrets in returned props. Use server-only modules.",
|
|
1491
|
+
checklist_ref: "G-nextjs",
|
|
1492
|
+
file_patterns: ["**/pages/**", "**/app/**"],
|
|
1493
|
+
framework: "baseline"
|
|
1494
|
+
}
|
|
1495
|
+
]
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
trigger: (eco) => eco.infrastructure.some((i) => /firebase/i.test(i)),
|
|
1499
|
+
constraints: [
|
|
1500
|
+
{
|
|
1501
|
+
id_suffix: "firestore_rules",
|
|
1502
|
+
category: "security",
|
|
1503
|
+
summary: "Firestore security rules MUST enforce per-user data isolation. Admin SDK must only be used server-side.",
|
|
1504
|
+
keywords: ["firestore", "rules", "firebase", "admin-sdk", "security-rules"],
|
|
1505
|
+
severity: "critical",
|
|
1506
|
+
action: "Write and test Firestore rules for per-user/per-org isolation. Never import firebase-admin in client code.",
|
|
1507
|
+
checklist_ref: "E1",
|
|
1508
|
+
file_patterns: ["**/*.rules", "**/firestore.rules", "**/firebase/**"],
|
|
1509
|
+
framework: "baseline"
|
|
1510
|
+
}
|
|
1511
|
+
]
|
|
1512
|
+
},
|
|
1513
|
+
{
|
|
1514
|
+
trigger: (eco) => eco.security_libs.some((l) => /jsonwebtoken|jose|jwt/i.test(l)),
|
|
1515
|
+
constraints: [
|
|
1516
|
+
{
|
|
1517
|
+
id_suffix: "jwt_validation",
|
|
1518
|
+
category: "security",
|
|
1519
|
+
summary: "JWTs MUST be validated on every request (signature, expiry, audience). Do not trust unverified tokens.",
|
|
1520
|
+
keywords: ["jwt", "token", "validation", "expiry", "signature"],
|
|
1521
|
+
severity: "critical",
|
|
1522
|
+
action: "Verify JWT signature, expiration, and audience on every authenticated request.",
|
|
1523
|
+
checklist_ref: "A3",
|
|
1524
|
+
file_patterns: ["**/auth/**", "**/middleware/**", "**/api/**"],
|
|
1525
|
+
framework: "baseline"
|
|
1526
|
+
}
|
|
1527
|
+
]
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
trigger: (eco) => eco.frameworks.some((f) => /express/i.test(f)),
|
|
1531
|
+
constraints: [
|
|
1532
|
+
{
|
|
1533
|
+
id_suffix: "express_helmet",
|
|
1534
|
+
category: "security",
|
|
1535
|
+
summary: "Express apps MUST use helmet for security headers and configure cookies with httpOnly, secure, sameSite.",
|
|
1536
|
+
keywords: ["helmet", "headers", "cookies", "express", "hsts", "csp"],
|
|
1537
|
+
severity: "warning",
|
|
1538
|
+
action: "Add helmet middleware. Set cookies to httpOnly, secure, sameSite=strict.",
|
|
1539
|
+
checklist_ref: "G-express",
|
|
1540
|
+
file_patterns: ["**/server.*", "**/app.*", "**/index.*"],
|
|
1541
|
+
framework: "baseline"
|
|
1542
|
+
}
|
|
1543
|
+
]
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
trigger: (eco) => eco.ai_libs.length > 0,
|
|
1547
|
+
constraints: [
|
|
1548
|
+
{
|
|
1549
|
+
id_suffix: "ai_key_scoping",
|
|
1550
|
+
category: "security",
|
|
1551
|
+
summary: "AI/LLM API keys MUST have minimal permissions and be scoped per-environment. Monitor usage for anomalies.",
|
|
1552
|
+
keywords: ["ai", "llm", "api-key", "openai", "vertex", "scoping"],
|
|
1553
|
+
severity: "warning",
|
|
1554
|
+
action: "Use per-environment API keys with minimal scopes. Add usage monitoring and spending alerts.",
|
|
1555
|
+
checklist_ref: "F3",
|
|
1556
|
+
file_patterns: ["**/ai/**", "**/llm/**", "**/agents/**", "**/orchestrator/**"],
|
|
1557
|
+
framework: "baseline"
|
|
1558
|
+
}
|
|
1559
|
+
]
|
|
1560
|
+
},
|
|
1561
|
+
{
|
|
1562
|
+
trigger: (eco) => eco.orms.some((o) => /prisma|typeorm|sequelize|drizzle/i.test(o)),
|
|
1563
|
+
constraints: [
|
|
1564
|
+
{
|
|
1565
|
+
id_suffix: "orm_parameterized",
|
|
1566
|
+
category: "security",
|
|
1567
|
+
summary: "All database queries MUST use the ORM's parameterized query API. No raw string concatenation for SQL.",
|
|
1568
|
+
keywords: ["orm", "sql", "injection", "parameterized", "query"],
|
|
1569
|
+
severity: "critical",
|
|
1570
|
+
action: "Audit raw SQL usage. Replace string concatenation with parameterized queries.",
|
|
1571
|
+
checklist_ref: "B2",
|
|
1572
|
+
file_patterns: ["**/db/**", "**/models/**", "**/repositories/**", "**/api/**"],
|
|
1573
|
+
framework: "baseline"
|
|
1574
|
+
}
|
|
1575
|
+
]
|
|
1576
|
+
},
|
|
1577
|
+
// ── PCI-DSS (triggered by payment/billing infrastructure) ───────────────
|
|
1578
|
+
{
|
|
1579
|
+
trigger: (eco, ctx) => {
|
|
1580
|
+
const paymentLibs = /stripe|braintree|adyen|square|paypal|chargebee|recurly|paddle/i;
|
|
1581
|
+
const hasDep = eco.all_dependencies.some((d) => paymentLibs.test(d));
|
|
1582
|
+
const hasInfra = eco.infrastructure.some((i) => paymentLibs.test(i));
|
|
1583
|
+
const descMatch = ctx?.productDescription ? /payment|billing|checkout|subscript|invoice|credit.?card|cardholder/i.test(ctx.productDescription) : false;
|
|
1584
|
+
return hasDep || hasInfra || descMatch;
|
|
1585
|
+
},
|
|
1586
|
+
constraints: [
|
|
1587
|
+
{
|
|
1588
|
+
id_suffix: "pci_no_raw_card_data",
|
|
1589
|
+
category: "compliance",
|
|
1590
|
+
summary: "[PCI-DSS Req 3] Raw cardholder data (PAN, CVV, PIN) MUST NOT be stored, logged, or rendered anywhere in the application.",
|
|
1591
|
+
keywords: ["pci", "cardholder", "pan", "cvv", "card-data", "tokenization"],
|
|
1592
|
+
severity: "critical",
|
|
1593
|
+
action: "Use tokenized payment methods (Stripe Elements, etc.). Audit logs and DB for raw card numbers.",
|
|
1594
|
+
checklist_ref: "PCI-R3",
|
|
1595
|
+
file_patterns: ["**/api/stripe/**", "**/api/payment*", "**/billing/**", "**/checkout/**"],
|
|
1596
|
+
framework: "pci_dss"
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
id_suffix: "pci_tls_transmission",
|
|
1600
|
+
category: "compliance",
|
|
1601
|
+
summary: "[PCI-DSS Req 4] All transmission of cardholder data MUST use TLS 1.2+. No fallback to plaintext.",
|
|
1602
|
+
keywords: ["pci", "tls", "encryption", "transit", "https"],
|
|
1603
|
+
severity: "critical",
|
|
1604
|
+
action: "Enforce TLS 1.2+ on all endpoints. Disable legacy protocols. Verify CSP and HSTS headers.",
|
|
1605
|
+
checklist_ref: "PCI-R4",
|
|
1606
|
+
file_patterns: ["**/config/**", "**/middleware/**", "**/security/**"],
|
|
1607
|
+
framework: "pci_dss"
|
|
1608
|
+
},
|
|
1609
|
+
{
|
|
1610
|
+
id_suffix: "pci_secure_systems",
|
|
1611
|
+
category: "compliance",
|
|
1612
|
+
summary: "[PCI-DSS Req 6] Applications handling payment data MUST address known vulnerabilities and follow secure coding practices.",
|
|
1613
|
+
keywords: ["pci", "secure-coding", "vulnerability", "patching", "code-review"],
|
|
1614
|
+
severity: "critical",
|
|
1615
|
+
action: "Run dependency vulnerability scans. Enforce code reviews on payment-related changes. Apply patches promptly.",
|
|
1616
|
+
checklist_ref: "PCI-R6",
|
|
1617
|
+
file_patterns: ["**/api/stripe/**", "**/api/payment*", "**/billing/**"],
|
|
1618
|
+
framework: "pci_dss"
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
id_suffix: "pci_access_restrict",
|
|
1622
|
+
category: "compliance",
|
|
1623
|
+
summary: "[PCI-DSS Req 7] Access to payment systems MUST be restricted by business need-to-know. Admin-only for billing config.",
|
|
1624
|
+
keywords: ["pci", "access-control", "need-to-know", "billing-admin"],
|
|
1625
|
+
severity: "critical",
|
|
1626
|
+
action: "Restrict billing/subscription admin endpoints to billing-role users. Audit access logs.",
|
|
1627
|
+
checklist_ref: "PCI-R7",
|
|
1628
|
+
file_patterns: ["**/api/stripe/**", "**/api/subscription/**", "**/admin/**"],
|
|
1629
|
+
framework: "pci_dss"
|
|
1630
|
+
},
|
|
1631
|
+
{
|
|
1632
|
+
id_suffix: "pci_audit_trail",
|
|
1633
|
+
category: "compliance",
|
|
1634
|
+
summary: "[PCI-DSS Req 10] All access to payment resources MUST generate audit trail entries with user, action, timestamp, and outcome.",
|
|
1635
|
+
keywords: ["pci", "audit-trail", "logging", "payment-events"],
|
|
1636
|
+
severity: "critical",
|
|
1637
|
+
action: "Log all payment-related API calls with user ID, action, and result. Retain logs for 12 months.",
|
|
1638
|
+
checklist_ref: "PCI-R10",
|
|
1639
|
+
file_patterns: ["**/api/stripe/**", "**/api/payment*", "**/logger/**"],
|
|
1640
|
+
framework: "pci_dss"
|
|
1641
|
+
}
|
|
1642
|
+
]
|
|
1643
|
+
},
|
|
1644
|
+
// ── HIPAA (triggered by health data patterns) ─────────────────────────────
|
|
1645
|
+
{
|
|
1646
|
+
trigger: (eco, ctx) => {
|
|
1647
|
+
const healthLibs = /fhir|hl7|dicom|health|medical|epic-sdk|cerner|athena/i;
|
|
1648
|
+
const hasDep = eco.all_dependencies.some((d) => healthLibs.test(d));
|
|
1649
|
+
const descMatch = ctx?.productDescription ? /\b(hipaa|phi|protected.health|patient.data|medical.record|ehr|electronic.health|healthcare|telehealth|telemedicine)\b/i.test(ctx.productDescription) : false;
|
|
1650
|
+
const entityMatch = ctx?.existingEntityNames?.some((n) => /health|patient|medical|clinical|hipaa|phi|diagnosis|prescription/i.test(n)) ?? false;
|
|
1651
|
+
return hasDep || descMatch || entityMatch;
|
|
1652
|
+
},
|
|
1653
|
+
constraints: [
|
|
1654
|
+
{
|
|
1655
|
+
id_suffix: "hipaa_phi_encryption",
|
|
1656
|
+
category: "compliance",
|
|
1657
|
+
summary: "[HIPAA \xA7164.312(a)] PHI MUST be encrypted at rest (AES-256) and in transit (TLS 1.2+). Access requires unique user identification.",
|
|
1658
|
+
keywords: ["hipaa", "phi", "encryption", "at-rest", "transit", "aes"],
|
|
1659
|
+
severity: "critical",
|
|
1660
|
+
action: "Encrypt all PHI fields at rest. Enforce TLS. Implement unique user authentication for PHI access.",
|
|
1661
|
+
checklist_ref: "HIPAA-312a",
|
|
1662
|
+
file_patterns: ["**/api/**", "**/models/**", "**/db/**", "**/crypto/**"],
|
|
1663
|
+
framework: "hipaa"
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
id_suffix: "hipaa_access_controls",
|
|
1667
|
+
category: "compliance",
|
|
1668
|
+
summary: "[HIPAA \xA7164.312(d)] Access to PHI MUST be controlled by role. Emergency access procedure must exist.",
|
|
1669
|
+
keywords: ["hipaa", "access-control", "role", "phi", "emergency-access"],
|
|
1670
|
+
severity: "critical",
|
|
1671
|
+
action: "Implement role-based PHI access. Document break-glass procedure. Log all PHI access.",
|
|
1672
|
+
checklist_ref: "HIPAA-312d",
|
|
1673
|
+
file_patterns: ["**/api/**", "**/auth/**", "**/middleware/**"],
|
|
1674
|
+
framework: "hipaa"
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
id_suffix: "hipaa_audit_controls",
|
|
1678
|
+
category: "compliance",
|
|
1679
|
+
summary: "[HIPAA \xA7164.312(b)] All PHI access, modification, and deletion MUST be logged with immutable audit trails.",
|
|
1680
|
+
keywords: ["hipaa", "audit", "phi-access", "immutable-log", "tracking"],
|
|
1681
|
+
severity: "critical",
|
|
1682
|
+
action: "Log every PHI read, write, and delete with user, timestamp, and resource ID. Logs must be tamper-evident.",
|
|
1683
|
+
checklist_ref: "HIPAA-312b",
|
|
1684
|
+
file_patterns: ["**/api/**", "**/logger/**", "**/audit/**"],
|
|
1685
|
+
framework: "hipaa"
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
id_suffix: "hipaa_minimum_necessary",
|
|
1689
|
+
category: "compliance",
|
|
1690
|
+
summary: "[HIPAA \xA7164.502(b)] Applications MUST apply the minimum necessary standard \u2014 only access/disclose the PHI required for the task.",
|
|
1691
|
+
keywords: ["hipaa", "minimum-necessary", "data-minimization", "phi", "scope"],
|
|
1692
|
+
severity: "critical",
|
|
1693
|
+
action: "API responses must return only needed PHI fields. No bulk PHI dumps. Filter by purpose of use.",
|
|
1694
|
+
checklist_ref: "HIPAA-502b",
|
|
1695
|
+
file_patterns: ["**/api/**", "**/models/**", "**/queries/**"],
|
|
1696
|
+
framework: "hipaa"
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
id_suffix: "hipaa_baa_required",
|
|
1700
|
+
category: "compliance",
|
|
1701
|
+
summary: "[HIPAA \xA7164.502(e)] Business Associate Agreements MUST be in place with all third-party services that process PHI.",
|
|
1702
|
+
keywords: ["hipaa", "baa", "business-associate", "vendor", "subprocessor"],
|
|
1703
|
+
severity: "critical",
|
|
1704
|
+
action: "Verify BAAs exist with cloud providers, AI services, and any SaaS that touches PHI. Maintain BAA inventory.",
|
|
1705
|
+
checklist_ref: "HIPAA-502e",
|
|
1706
|
+
file_patterns: ["**/integrations/**", "**/ai/**", "**/config/**"],
|
|
1707
|
+
framework: "hipaa"
|
|
1708
|
+
}
|
|
1709
|
+
]
|
|
1710
|
+
},
|
|
1711
|
+
// ── FedRAMP (triggered by government/federal indicators) ──────────────────
|
|
1712
|
+
{
|
|
1713
|
+
trigger: (eco, ctx) => {
|
|
1714
|
+
const govLibs = /govcloud|fedramp|fips|nist|cac-auth|piv/i;
|
|
1715
|
+
const hasDep = eco.all_dependencies.some((d) => govLibs.test(d));
|
|
1716
|
+
const hasGovInfra = eco.infrastructure.some((i) => /govcloud|gov-/i.test(i));
|
|
1717
|
+
const descMatch = ctx?.productDescription ? /\b(fedramp|federal|government|gov.?cloud|fips|nist.800|ato|fisma|stateramp|dod.?il[2-6])\b/i.test(ctx.productDescription) : false;
|
|
1718
|
+
const entityMatch = ctx?.existingEntityNames?.some((n) => /federal|government|fedramp|fips|nist|fisma/i.test(n)) ?? false;
|
|
1719
|
+
return hasDep || hasGovInfra || descMatch || entityMatch;
|
|
1720
|
+
},
|
|
1721
|
+
constraints: [
|
|
1722
|
+
{
|
|
1723
|
+
id_suffix: "fedramp_fips_crypto",
|
|
1724
|
+
category: "compliance",
|
|
1725
|
+
summary: "[FedRAMP/FIPS 140-2] All cryptographic operations MUST use FIPS 140-2 validated modules. No custom or non-validated crypto.",
|
|
1726
|
+
keywords: ["fedramp", "fips", "fips-140", "cryptography", "validated"],
|
|
1727
|
+
severity: "critical",
|
|
1728
|
+
action: "Use FIPS-validated crypto libraries. Enable FIPS mode in Node.js/OpenSSL. Verify no custom crypto implementations.",
|
|
1729
|
+
checklist_ref: "FEDRAMP-SC13",
|
|
1730
|
+
file_patterns: ["**/crypto/**", "**/auth/**", "**/security/**"],
|
|
1731
|
+
framework: "fedramp"
|
|
1732
|
+
},
|
|
1733
|
+
{
|
|
1734
|
+
id_suffix: "fedramp_continuous_monitoring",
|
|
1735
|
+
category: "compliance",
|
|
1736
|
+
summary: "[FedRAMP CA-7] Continuous monitoring MUST be implemented: vulnerability scans, config drift detection, and incident alerting.",
|
|
1737
|
+
keywords: ["fedramp", "continuous-monitoring", "conmon", "vulnerability-scan", "drift"],
|
|
1738
|
+
severity: "critical",
|
|
1739
|
+
action: "Implement automated vulnerability scanning, configuration drift detection, and monthly security reporting.",
|
|
1740
|
+
checklist_ref: "FEDRAMP-CA7",
|
|
1741
|
+
file_patterns: ["**/.github/**", "**/ci/**", "**/monitoring/**", "**/security/**"],
|
|
1742
|
+
framework: "fedramp"
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
id_suffix: "fedramp_incident_response",
|
|
1746
|
+
category: "compliance",
|
|
1747
|
+
summary: "[FedRAMP IR-6] Security incidents MUST be reported to US-CERT within required timeframes. Incident handling plan required.",
|
|
1748
|
+
keywords: ["fedramp", "incident-response", "us-cert", "reporting", "breach"],
|
|
1749
|
+
severity: "critical",
|
|
1750
|
+
action: "Document incident response plan with US-CERT reporting procedures. Implement automated incident detection.",
|
|
1751
|
+
checklist_ref: "FEDRAMP-IR6",
|
|
1752
|
+
file_patterns: ["**/error*", "**/monitoring/**", "**/sentry/**"],
|
|
1753
|
+
framework: "fedramp"
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
id_suffix: "fedramp_supply_chain",
|
|
1757
|
+
category: "compliance",
|
|
1758
|
+
summary: "[FedRAMP SA-12] Supply chain risks MUST be assessed. All dependencies must be vetted and integrity-verified.",
|
|
1759
|
+
keywords: ["fedramp", "supply-chain", "sbom", "integrity", "provenance"],
|
|
1760
|
+
severity: "critical",
|
|
1761
|
+
action: "Generate and maintain SBOM. Verify lockfile integrity. Vet all third-party dependencies for provenance.",
|
|
1762
|
+
checklist_ref: "FEDRAMP-SA12",
|
|
1763
|
+
file_patterns: ["**/package*.json", "**/requirements*.txt", "**/*lock*"],
|
|
1764
|
+
framework: "fedramp"
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
id_suffix: "fedramp_boundary_protection",
|
|
1768
|
+
category: "compliance",
|
|
1769
|
+
summary: "[FedRAMP SC-7] System boundary MUST be defined and enforced. All data flows must be documented and authorized.",
|
|
1770
|
+
keywords: ["fedramp", "boundary", "network", "data-flow", "authorization"],
|
|
1771
|
+
severity: "critical",
|
|
1772
|
+
action: "Document system boundary diagram. Restrict egress to authorized endpoints. Log all cross-boundary data flows.",
|
|
1773
|
+
checklist_ref: "FEDRAMP-SC7",
|
|
1774
|
+
file_patterns: ["**/api/**", "**/proxy/**", "**/integrations/**", "**/config/**"],
|
|
1775
|
+
framework: "fedramp"
|
|
1776
|
+
}
|
|
1777
|
+
]
|
|
1778
|
+
},
|
|
1779
|
+
// ── GDPR & CCPA (Privacy by Design) ───────────────────────────────────────
|
|
1780
|
+
{
|
|
1781
|
+
trigger: (eco, ctx) => {
|
|
1782
|
+
const privacyLibs = /posthog|segment|analytics|gtag|google-analytics|mixpanel|amplitude|auth0|clerk|next-auth|passport|cookie-consent|cookiebot/i;
|
|
1783
|
+
const hasDep = eco.all_dependencies.some((d) => privacyLibs.test(d));
|
|
1784
|
+
const descMatch = ctx?.productDescription ? /\b(gdpr|ccpa|cpra|privacy|user.?data|tracking|cookie|consent|eu|california|data.?subject|right.to.erasure|data.?portability)\b/i.test(ctx.productDescription) : false;
|
|
1785
|
+
const entityMatch = ctx?.existingEntityNames?.some((n) => /privacy|gdpr|ccpa|consent|tracking|analytics|cookie/i.test(n)) ?? false;
|
|
1786
|
+
return hasDep || descMatch || entityMatch;
|
|
1787
|
+
},
|
|
1788
|
+
constraints: [
|
|
1789
|
+
{
|
|
1790
|
+
id_suffix: "gdpr_right_to_erasure",
|
|
1791
|
+
category: "compliance",
|
|
1792
|
+
summary: "[GDPR Art. 17] Users MUST be able to request complete deletion of their data. Erasure must cascade across all services and backups.",
|
|
1793
|
+
keywords: ["gdpr", "right-to-erasure", "deletion", "cascade", "data-subject"],
|
|
1794
|
+
severity: "critical",
|
|
1795
|
+
action: "Implement automated hard-delete cascading across Firestore, storage, analytics, and third-party services. Verify no orphaned references.",
|
|
1796
|
+
checklist_ref: "GDPR-A17",
|
|
1797
|
+
file_patterns: ["**/api/user*", "**/api/account*", "**/api/delete*", "**/models/**"],
|
|
1798
|
+
framework: "gdpr_ccpa"
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
id_suffix: "gdpr_data_portability",
|
|
1802
|
+
category: "compliance",
|
|
1803
|
+
summary: "[GDPR Art. 20] Users MUST be able to export their data in a structured, machine-readable format (JSON/CSV).",
|
|
1804
|
+
keywords: ["gdpr", "data-portability", "export", "machine-readable", "data-subject"],
|
|
1805
|
+
severity: "critical",
|
|
1806
|
+
action: "Build automated data export endpoints returning user data as JSON/CSV. Include all PII and user-generated content.",
|
|
1807
|
+
checklist_ref: "GDPR-A20",
|
|
1808
|
+
file_patterns: ["**/api/export*", "**/api/user*", "**/api/account*"],
|
|
1809
|
+
framework: "gdpr_ccpa"
|
|
1810
|
+
},
|
|
1811
|
+
{
|
|
1812
|
+
id_suffix: "gdpr_consent_gating",
|
|
1813
|
+
category: "compliance",
|
|
1814
|
+
summary: "[GDPR Art. 7 / CCPA \xA71798.120] Cookie and session tracking MUST be gated behind explicit user consent. No pre-checked boxes.",
|
|
1815
|
+
keywords: ["gdpr", "ccpa", "consent", "cookie", "tracking", "opt-in"],
|
|
1816
|
+
severity: "critical",
|
|
1817
|
+
action: "Implement consent banner with granular opt-in. Block analytics/tracking scripts until consent. Record consent timestamps.",
|
|
1818
|
+
checklist_ref: "GDPR-A7",
|
|
1819
|
+
file_patterns: ["**/components/**cookie*", "**/analytics/**", "**/tracking/**", "**/middleware/**"],
|
|
1820
|
+
framework: "gdpr_ccpa"
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
id_suffix: "gdpr_data_residency",
|
|
1824
|
+
category: "compliance",
|
|
1825
|
+
summary: "[GDPR Art. 44-49] EU user data MUST be stored in EU regions unless adequate safeguards (SCCs/DPF) are documented.",
|
|
1826
|
+
keywords: ["gdpr", "data-residency", "eu", "transfer", "scc", "adequacy"],
|
|
1827
|
+
severity: "warning",
|
|
1828
|
+
action: "Configure cloud regions for EU data. Document cross-border transfer mechanisms (SCCs or DPF certification).",
|
|
1829
|
+
checklist_ref: "GDPR-A44",
|
|
1830
|
+
file_patterns: ["**/config/**", "**/firebase/**", "**/infrastructure/**"],
|
|
1831
|
+
framework: "gdpr_ccpa"
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
id_suffix: "gdpr_pii_log_anonymization",
|
|
1835
|
+
category: "compliance",
|
|
1836
|
+
summary: "[GDPR Art. 25] PII MUST be anonymized or pseudonymized in all logs, error reports, and analytics pipelines.",
|
|
1837
|
+
keywords: ["gdpr", "anonymization", "pseudonymization", "pii", "logs", "masking"],
|
|
1838
|
+
severity: "critical",
|
|
1839
|
+
action: "Strip or hash PII from log outputs, error payloads (Sentry, etc.), and analytics events. Audit existing log statements.",
|
|
1840
|
+
checklist_ref: "GDPR-A25",
|
|
1841
|
+
file_patterns: ["**/logger/**", "**/api/**", "**/sentry/**", "**/monitoring/**"],
|
|
1842
|
+
framework: "gdpr_ccpa"
|
|
1843
|
+
}
|
|
1844
|
+
]
|
|
1845
|
+
},
|
|
1846
|
+
// ── OWASP LLM Top 10 & AI Safety ─────────────────────────────────────────
|
|
1847
|
+
{
|
|
1848
|
+
trigger: (eco, ctx) => {
|
|
1849
|
+
const aiLibs = /openai|anthropic|langchain|llamaindex|llama.?index|pinecone|weaviate|chroma|qdrant|milvus|@google-cloud\/vertexai|@google\/generative/i;
|
|
1850
|
+
const hasDep = eco.all_dependencies.some((d) => aiLibs.test(d));
|
|
1851
|
+
const hasAiLib = eco.ai_libs.length > 0;
|
|
1852
|
+
const descMatch = ctx?.productDescription ? /\b(rag|agent|llm|prompt|embedding|vector.?db|retrieval|generative.?ai|chatbot|copilot)\b/i.test(ctx.productDescription) : false;
|
|
1853
|
+
return hasDep || hasAiLib || descMatch;
|
|
1854
|
+
},
|
|
1855
|
+
constraints: [
|
|
1856
|
+
{
|
|
1857
|
+
id_suffix: "owasp_llm01_prompt_injection",
|
|
1858
|
+
category: "security",
|
|
1859
|
+
summary: "[OWASP LLM01] Prompt injection defenses MUST be implemented. Strict input sanitization boundaries between user content and system prompts.",
|
|
1860
|
+
keywords: ["owasp-llm", "prompt-injection", "sanitization", "boundary", "jailbreak"],
|
|
1861
|
+
severity: "critical",
|
|
1862
|
+
action: "Separate user input from system prompts. Sanitize special characters. Implement prompt firewalls. Test with known injection payloads.",
|
|
1863
|
+
checklist_ref: "LLM01",
|
|
1864
|
+
file_patterns: ["**/ai/**", "**/llm/**", "**/agents/**", "**/orchestrator/**", "**/prompts/**"],
|
|
1865
|
+
framework: "owasp_llm"
|
|
1866
|
+
},
|
|
1867
|
+
{
|
|
1868
|
+
id_suffix: "owasp_llm02_insecure_output",
|
|
1869
|
+
category: "security",
|
|
1870
|
+
summary: "[OWASP LLM02] LLM outputs MUST be treated as untrusted. Apply XSS prevention, output encoding, and content filtering before rendering.",
|
|
1871
|
+
keywords: ["owasp-llm", "output-handling", "xss", "encoding", "content-filter"],
|
|
1872
|
+
severity: "critical",
|
|
1873
|
+
action: "Sanitize all LLM outputs before rendering in HTML. Apply Content-Security-Policy. Never use dangerouslySetInnerHTML with raw model output.",
|
|
1874
|
+
checklist_ref: "LLM02",
|
|
1875
|
+
file_patterns: ["**/components/**", "**/ai/**", "**/llm/**", "**/render*"],
|
|
1876
|
+
framework: "owasp_llm"
|
|
1877
|
+
},
|
|
1878
|
+
{
|
|
1879
|
+
id_suffix: "owasp_llm08_excessive_agency",
|
|
1880
|
+
category: "security",
|
|
1881
|
+
summary: "[OWASP LLM08] AI agents MUST have strict RBAC on tool calls. No agent should execute destructive actions without human approval.",
|
|
1882
|
+
keywords: ["owasp-llm", "excessive-agency", "rbac", "tool-use", "human-in-the-loop"],
|
|
1883
|
+
severity: "critical",
|
|
1884
|
+
action: "Enforce per-tool permission checks. Require human approval for destructive operations. Log all agent tool invocations.",
|
|
1885
|
+
checklist_ref: "LLM08",
|
|
1886
|
+
file_patterns: ["**/agents/**", "**/tools/**", "**/mcp/**", "**/orchestrator/**"],
|
|
1887
|
+
framework: "owasp_llm"
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
id_suffix: "owasp_llm_data_compartment",
|
|
1891
|
+
category: "security",
|
|
1892
|
+
summary: "[OWASP LLM06] RAG contexts MUST be compartmentalized per-user/per-tenant. User A's data must never appear in User B's retrieval context.",
|
|
1893
|
+
keywords: ["owasp-llm", "data-poisoning", "rag", "compartmentalization", "tenant-isolation"],
|
|
1894
|
+
severity: "critical",
|
|
1895
|
+
action: "Filter vector search results by user/org ID. Namespace embeddings per tenant. Audit retrieval pipelines for cross-tenant leakage.",
|
|
1896
|
+
checklist_ref: "LLM06",
|
|
1897
|
+
file_patterns: ["**/ai/**", "**/rag/**", "**/embeddings/**", "**/vector*"],
|
|
1898
|
+
framework: "owasp_llm"
|
|
1899
|
+
}
|
|
1900
|
+
]
|
|
1901
|
+
},
|
|
1902
|
+
// ── GLBA (Gramm-Leach-Bliley Act) & Financial Privacy ────────────────────
|
|
1903
|
+
{
|
|
1904
|
+
trigger: (eco, ctx) => {
|
|
1905
|
+
const finLibs = /plaid|mx|yodlee|finicity|teller|akoya|dwolla|synapse|marqeta|galileo|lithic|unit/i;
|
|
1906
|
+
const hasDep = eco.all_dependencies.some((d) => finLibs.test(d));
|
|
1907
|
+
const descMatch = ctx?.productDescription ? /\b(kyc|aml|loan|bank|wealth|fintech|lending|mortgage|credit.?score|underwriting|npi|non.?public.?personal)\b/i.test(ctx.productDescription) : false;
|
|
1908
|
+
const entityMatch = ctx?.existingEntityNames?.some((n) => /bank|loan|kyc|lending|financial|wealth|fintech/i.test(n)) ?? false;
|
|
1909
|
+
return hasDep || descMatch || entityMatch;
|
|
1910
|
+
},
|
|
1911
|
+
constraints: [
|
|
1912
|
+
{
|
|
1913
|
+
id_suffix: "glba_npi_isolation",
|
|
1914
|
+
category: "compliance",
|
|
1915
|
+
summary: "[GLBA \xA76802] Non-Public Personal Information (NPI) MUST be isolated in dedicated stores with field-level encryption and access logging.",
|
|
1916
|
+
keywords: ["glba", "npi", "financial-privacy", "isolation", "field-encryption"],
|
|
1917
|
+
severity: "critical",
|
|
1918
|
+
action: "Store NPI in dedicated encrypted collections. Implement field-level encryption for SSN, account numbers. Log all NPI access.",
|
|
1919
|
+
checklist_ref: "GLBA-6802",
|
|
1920
|
+
file_patterns: ["**/api/**", "**/models/**", "**/db/**", "**/crypto/**"],
|
|
1921
|
+
framework: "glba"
|
|
1922
|
+
},
|
|
1923
|
+
{
|
|
1924
|
+
id_suffix: "glba_mfa_enforcement",
|
|
1925
|
+
category: "compliance",
|
|
1926
|
+
summary: "[GLBA Safeguards Rule] MFA MUST be enforced at the API gateway level for all endpoints that access or modify financial data.",
|
|
1927
|
+
keywords: ["glba", "mfa", "multi-factor", "authentication", "gateway"],
|
|
1928
|
+
severity: "critical",
|
|
1929
|
+
action: "Enforce MFA step-up for financial data endpoints. Verify MFA status in middleware before granting access to NPI.",
|
|
1930
|
+
checklist_ref: "GLBA-MFA",
|
|
1931
|
+
file_patterns: ["**/api/**", "**/auth/**", "**/middleware/**"],
|
|
1932
|
+
framework: "glba"
|
|
1933
|
+
},
|
|
1934
|
+
{
|
|
1935
|
+
id_suffix: "glba_intrusion_detection",
|
|
1936
|
+
category: "compliance",
|
|
1937
|
+
summary: "[GLBA Safeguards Rule] Intrusion detection logging MUST capture unauthorized access attempts to financial systems and trigger alerts.",
|
|
1938
|
+
keywords: ["glba", "intrusion-detection", "ids", "alerting", "unauthorized-access"],
|
|
1939
|
+
severity: "critical",
|
|
1940
|
+
action: "Log failed auth attempts, anomalous data access patterns, and privilege escalation. Set up real-time alerting.",
|
|
1941
|
+
checklist_ref: "GLBA-IDS",
|
|
1942
|
+
file_patterns: ["**/auth/**", "**/api/**", "**/monitoring/**", "**/logger/**"],
|
|
1943
|
+
framework: "glba"
|
|
1944
|
+
},
|
|
1945
|
+
{
|
|
1946
|
+
id_suffix: "glba_retention_worm",
|
|
1947
|
+
category: "compliance",
|
|
1948
|
+
summary: "[GLBA/SEC 17a-4] Financial records MUST be retained for 7 years in immutable (WORM) storage. No early deletion permitted.",
|
|
1949
|
+
keywords: ["glba", "retention", "worm", "immutable", "7-year", "archival"],
|
|
1950
|
+
severity: "critical",
|
|
1951
|
+
action: "Configure WORM-compliant storage for financial records. Implement retention policies preventing deletion before 7 years.",
|
|
1952
|
+
checklist_ref: "GLBA-RET",
|
|
1953
|
+
file_patterns: ["**/storage/**", "**/archive/**", "**/config/**", "**/logger/**"],
|
|
1954
|
+
framework: "glba"
|
|
1955
|
+
}
|
|
1956
|
+
]
|
|
1957
|
+
},
|
|
1958
|
+
// ── CSA Cloud Controls Matrix (cloud deployment) ───────────────────────────
|
|
1959
|
+
{
|
|
1960
|
+
trigger: (eco, ctx) => {
|
|
1961
|
+
const cloudInfra = /aws|gcp|azure|firebase|vercel|supabase|google-cloud|@aws-sdk|@azure|@google-cloud/i;
|
|
1962
|
+
const hasCloudInfra = eco.infrastructure.some((i) => cloudInfra.test(i));
|
|
1963
|
+
const hasCloudDep = eco.all_dependencies.some((d) => cloudInfra.test(d));
|
|
1964
|
+
const hasIac = eco.iac_tools.some((t) => /terraform|docker|kubernetes|pulumi|cloudformation/i.test(t));
|
|
1965
|
+
return hasCloudInfra || hasCloudDep || hasIac;
|
|
1966
|
+
},
|
|
1967
|
+
constraints: [
|
|
1968
|
+
{
|
|
1969
|
+
id_suffix: "csa_ccm_iam",
|
|
1970
|
+
category: "compliance",
|
|
1971
|
+
summary: "[CSA CCM IAM-01] Identity and access management MUST enforce unique credentials, MFA for privileged access, and least-privilege.",
|
|
1972
|
+
keywords: ["csa", "ccm", "iam", "access-control", "mfa", "least-privilege"],
|
|
1973
|
+
severity: "critical",
|
|
1974
|
+
action: "Implement IAM with unique credentials per user. Enforce MFA for admin roles. Audit permissions for least-privilege.",
|
|
1975
|
+
checklist_ref: "CCM-IAM-01",
|
|
1976
|
+
file_patterns: ["**/auth/**", "**/api/**", "**/middleware/**"],
|
|
1977
|
+
framework: "csa_ccm"
|
|
1978
|
+
},
|
|
1979
|
+
{
|
|
1980
|
+
id_suffix: "csa_ccm_encryption",
|
|
1981
|
+
category: "compliance",
|
|
1982
|
+
summary: "[CSA CCM DSI-01] Data MUST be encrypted in transit (TLS 1.2+) and sensitive data encrypted at rest.",
|
|
1983
|
+
keywords: ["csa", "ccm", "encryption", "tls", "at-rest", "transit"],
|
|
1984
|
+
severity: "critical",
|
|
1985
|
+
action: "Enforce HTTPS everywhere. Encrypt PII and sensitive fields at rest. Verify cloud storage encryption settings.",
|
|
1986
|
+
checklist_ref: "CCM-DSI-01",
|
|
1987
|
+
file_patterns: ["**/config/**", "**/storage/**", "**/database/**"],
|
|
1988
|
+
framework: "csa_ccm"
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
id_suffix: "csa_ccm_logging",
|
|
1992
|
+
category: "compliance",
|
|
1993
|
+
summary: "[CSA CCM LOG-01] Security-relevant events MUST be logged with sufficient detail for audit and incident response.",
|
|
1994
|
+
keywords: ["csa", "ccm", "logging", "audit", "security-events", "monitoring"],
|
|
1995
|
+
severity: "critical",
|
|
1996
|
+
action: "Log auth events, data access, config changes, and admin actions. Ensure logs are tamper-evident and retained.",
|
|
1997
|
+
checklist_ref: "CCM-LOG-01",
|
|
1998
|
+
file_patterns: ["**/api/**", "**/auth/**", "**/logger/**", "**/monitoring/**"],
|
|
1999
|
+
framework: "csa_ccm"
|
|
2000
|
+
},
|
|
2001
|
+
{
|
|
2002
|
+
id_suffix: "csa_ccm_incident",
|
|
2003
|
+
category: "compliance",
|
|
2004
|
+
summary: "[CSA CCM IVS-01] Security incident management MUST include detection, response, and remediation procedures.",
|
|
2005
|
+
keywords: ["csa", "ccm", "incident-response", "breach", "remediation", "alerting"],
|
|
2006
|
+
severity: "warning",
|
|
2007
|
+
action: "Document incident response plan. Implement alerting for security events. Define escalation and remediation workflows.",
|
|
2008
|
+
checklist_ref: "CCM-IVS-01",
|
|
2009
|
+
file_patterns: ["**/error*", "**/monitoring/**", "**/sentry/**"],
|
|
2010
|
+
framework: "csa_ccm"
|
|
2011
|
+
},
|
|
2012
|
+
{
|
|
2013
|
+
id_suffix: "csa_ccm_supply_chain",
|
|
2014
|
+
category: "compliance",
|
|
2015
|
+
summary: "[CSA CCM SEF-01] Supply chain risks MUST be assessed. Dependencies must be vetted and integrity-verified.",
|
|
2016
|
+
keywords: ["csa", "ccm", "supply-chain", "sbom", "dependencies", "integrity"],
|
|
2017
|
+
severity: "warning",
|
|
2018
|
+
action: "Maintain dependency inventory. Use lockfiles and verify checksums. Consider SBOM generation for cloud deployments.",
|
|
2019
|
+
checklist_ref: "CCM-SEF-01",
|
|
2020
|
+
file_patterns: ["**/package.json", "**/package-lock*", "**/yarn.lock", "**/pnpm-lock*"],
|
|
2021
|
+
framework: "csa_ccm"
|
|
2022
|
+
}
|
|
2023
|
+
]
|
|
2024
|
+
},
|
|
2025
|
+
// ── FERPA & COPPA (EdTech & Minors) ───────────────────────────────────────
|
|
2026
|
+
{
|
|
2027
|
+
trigger: (eco, ctx) => {
|
|
2028
|
+
const edLibs = /clever|canvas-api|schoology|google-classroom|classlink|sis-api|lti/i;
|
|
2029
|
+
const hasDep = eco.all_dependencies.some((d) => edLibs.test(d));
|
|
2030
|
+
const descMatch = ctx?.productDescription ? /\b(student|school|child|minor|classroom|edtech|education|teacher|k.?12|ferpa|coppa|parental.?consent|lms)\b/i.test(ctx.productDescription) : false;
|
|
2031
|
+
const entityMatch = ctx?.existingEntityNames?.some((n) => /student|school|classroom|education|child|minor|teacher/i.test(n)) ?? false;
|
|
2032
|
+
return hasDep || descMatch || entityMatch;
|
|
2033
|
+
},
|
|
2034
|
+
constraints: [
|
|
2035
|
+
{
|
|
2036
|
+
id_suffix: "ferpa_coppa_parental_consent",
|
|
2037
|
+
category: "compliance",
|
|
2038
|
+
summary: "[COPPA \xA7312.5 / FERPA \xA799.30] Mandatory parental/guardian consent API gates MUST exist before collecting data from minors (<13).",
|
|
2039
|
+
keywords: ["coppa", "ferpa", "parental-consent", "minor", "guardian", "age-gate"],
|
|
2040
|
+
severity: "critical",
|
|
2041
|
+
action: "Implement verifiable parental consent flow. Block data collection until consent is recorded. Provide consent withdrawal mechanism.",
|
|
2042
|
+
checklist_ref: "COPPA-312",
|
|
2043
|
+
file_patterns: ["**/api/auth/**", "**/api/signup*", "**/api/consent*", "**/middleware/**"],
|
|
2044
|
+
framework: "ferpa_coppa"
|
|
2045
|
+
},
|
|
2046
|
+
{
|
|
2047
|
+
id_suffix: "ferpa_coppa_no_profiling",
|
|
2048
|
+
category: "compliance",
|
|
2049
|
+
summary: "[COPPA \xA7312.3] Behavioral profiling, ad-tracking scripts, and targeted advertising MUST be completely banned for minor users.",
|
|
2050
|
+
keywords: ["coppa", "profiling", "ad-tracking", "behavioral", "advertising", "minor"],
|
|
2051
|
+
severity: "critical",
|
|
2052
|
+
action: "Audit all analytics/tracking scripts. Block ad networks and behavioral profiling for accounts flagged as minors. Strip tracking from student sessions.",
|
|
2053
|
+
checklist_ref: "COPPA-NOPROF",
|
|
2054
|
+
file_patterns: ["**/analytics/**", "**/tracking/**", "**/components/**", "**/middleware/**"],
|
|
2055
|
+
framework: "ferpa_coppa"
|
|
2056
|
+
},
|
|
2057
|
+
{
|
|
2058
|
+
id_suffix: "ferpa_coppa_data_destruction",
|
|
2059
|
+
category: "compliance",
|
|
2060
|
+
summary: "[FERPA \xA799.4 / COPPA \xA7312.10] Student/minor data MUST be automatically destroyed when the account expires or the school year ends.",
|
|
2061
|
+
keywords: ["ferpa", "data-destruction", "expiration", "retention", "school-year"],
|
|
2062
|
+
severity: "critical",
|
|
2063
|
+
action: "Implement automated data lifecycle. Schedule deletion of student data at account expiry. Notify administrators before destruction.",
|
|
2064
|
+
checklist_ref: "FERPA-DEST",
|
|
2065
|
+
file_patterns: ["**/api/user*", "**/cron/**", "**/jobs/**", "**/models/**"],
|
|
2066
|
+
framework: "ferpa_coppa"
|
|
2067
|
+
},
|
|
2068
|
+
{
|
|
2069
|
+
id_suffix: "ferpa_coppa_age_gating",
|
|
2070
|
+
category: "compliance",
|
|
2071
|
+
summary: "[COPPA \xA7312.2] Age-gating logic MUST be implemented at signup to detect minors and route them through compliant consent flows.",
|
|
2072
|
+
keywords: ["coppa", "age-gate", "age-verification", "minor-detection", "signup"],
|
|
2073
|
+
severity: "critical",
|
|
2074
|
+
action: "Add age verification at signup. Route users under 13 to parental consent flow. Block account creation without verified age.",
|
|
2075
|
+
checklist_ref: "COPPA-AGE",
|
|
2076
|
+
file_patterns: ["**/api/auth/**", "**/api/signup*", "**/components/signup*"],
|
|
2077
|
+
framework: "ferpa_coppa"
|
|
2078
|
+
}
|
|
2079
|
+
]
|
|
2080
|
+
},
|
|
2081
|
+
// ── iOS App Store Review Guidelines (mobile iOS apps) ──────────────────────
|
|
2082
|
+
{
|
|
2083
|
+
trigger: (eco, ctx) => {
|
|
2084
|
+
const iosSignals = /ios|swift|swiftui|xcode|uikit|storekit|appstore|testflight|cocoapods|xcframework/i;
|
|
2085
|
+
const hasLang = eco.languages.some((l) => /swift|objective-c|objc/i.test(l));
|
|
2086
|
+
const hasFramework = eco.frameworks.some((f) => iosSignals.test(f));
|
|
2087
|
+
const hasDep = eco.all_dependencies.some((d) => iosSignals.test(d));
|
|
2088
|
+
const descMatch = ctx?.productDescription ? /\b(ios|iphone|ipad|app\s*store|testflight|storekit|in-app purchase|apple sign in)\b/i.test(ctx.productDescription) : false;
|
|
2089
|
+
const entityMatch = ctx?.existingEntityNames?.some((n) => /\b(ios|mobile|iphone|ipad|app store|iap|storekit)\b/i.test(n)) ?? false;
|
|
2090
|
+
return hasLang || hasFramework || hasDep || descMatch || entityMatch;
|
|
2091
|
+
},
|
|
2092
|
+
constraints: [
|
|
2093
|
+
{
|
|
2094
|
+
id_suffix: "ios_app_store_privacy_disclosure",
|
|
2095
|
+
category: "compliance",
|
|
2096
|
+
summary: "[iOS App Store 5.1] Apps collecting user data MUST provide accurate privacy disclosures and clear in-app data handling flows.",
|
|
2097
|
+
keywords: ["ios", "app-store", "privacy", "disclosure", "tracking", "app-privacy"],
|
|
2098
|
+
severity: "critical",
|
|
2099
|
+
action: "Document data collection in App Privacy labels and align runtime behavior. Gate tracking behind explicit consent where required.",
|
|
2100
|
+
checklist_ref: "IOS-5.1",
|
|
2101
|
+
file_patterns: ["**/ios/**", "**/*.swift", "**/privacy*", "**/tracking/**", "**/analytics/**"],
|
|
2102
|
+
framework: "ios_app_store"
|
|
2103
|
+
},
|
|
2104
|
+
{
|
|
2105
|
+
id_suffix: "ios_app_store_iap_policy",
|
|
2106
|
+
category: "compliance",
|
|
2107
|
+
summary: "[iOS App Store 3.1] Digital goods/services sold in-app MUST use Apple's In-App Purchase flows where required.",
|
|
2108
|
+
keywords: ["ios", "app-store", "iap", "storekit", "payments", "digital-goods"],
|
|
2109
|
+
severity: "warning",
|
|
2110
|
+
action: "Use StoreKit for digital purchases in iOS app surfaces. Avoid bypass payment links that violate App Store rules.",
|
|
2111
|
+
checklist_ref: "IOS-3.1",
|
|
2112
|
+
file_patterns: ["**/ios/**", "**/*.swift", "**/billing/**", "**/payment/**", "**/storekit/**"],
|
|
2113
|
+
framework: "ios_app_store"
|
|
2114
|
+
},
|
|
2115
|
+
{
|
|
2116
|
+
id_suffix: "ios_app_store_account_deletion",
|
|
2117
|
+
category: "compliance",
|
|
2118
|
+
summary: "[iOS App Store 5.1.1(v)] If account creation exists, apps MUST provide in-app account deletion with data handling consistent with policy.",
|
|
2119
|
+
keywords: ["ios", "app-store", "account-deletion", "user-account", "privacy-rights"],
|
|
2120
|
+
severity: "warning",
|
|
2121
|
+
action: "Expose account deletion in-app for iOS users and ensure backend deletion flow is implemented and verifiable.",
|
|
2122
|
+
checklist_ref: "IOS-5.1.1",
|
|
2123
|
+
file_patterns: ["**/ios/**", "**/*.swift", "**/api/account*", "**/api/user*", "**/settings/**"],
|
|
2124
|
+
framework: "ios_app_store"
|
|
2125
|
+
}
|
|
2126
|
+
]
|
|
2127
|
+
}
|
|
2128
|
+
];
|
|
2129
|
+
function buildBlueprintConstraints(ecosystem, context) {
|
|
2130
|
+
const results = [...UNIVERSAL_CONSTRAINTS, ...SCALABILITY_CONSTRAINTS, ...RELIABILITY_CONSTRAINTS, ...IAC_CONSTRAINTS, ...SOC2_CONSTRAINTS];
|
|
2131
|
+
for (const rule of BLUEPRINT_RULES) {
|
|
2132
|
+
if (rule.trigger(ecosystem, context)) {
|
|
2133
|
+
results.push(...rule.constraints);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
return results;
|
|
2137
|
+
}
|
|
2138
|
+
function getActiveFrameworks(ecosystem, context) {
|
|
2139
|
+
const constraints = buildBlueprintConstraints(ecosystem, context);
|
|
2140
|
+
const frameworks = /* @__PURE__ */ new Set();
|
|
2141
|
+
for (const c of constraints) {
|
|
2142
|
+
frameworks.add(c.framework);
|
|
2143
|
+
}
|
|
2144
|
+
return [...frameworks].sort();
|
|
2145
|
+
}
|
|
2146
|
+
function graphNeedsBlueprintSeeding(existingConstraintIds) {
|
|
2147
|
+
return !existingConstraintIds.some((id) => id.startsWith("constraint:blueprint:"));
|
|
2148
|
+
}
|
|
2149
|
+
var SECURITY_SYSTEM_PROMPT = `You are a senior security engineer performing a structured code audit. You MUST work through the checklist below systematically. For each checklist item, either report a finding or confirm it was checked and found acceptable.
|
|
2150
|
+
|
|
2151
|
+
## AUDIT CHECKLIST
|
|
2152
|
+
|
|
2153
|
+
Work through every category. For each, check the provided source files and ecosystem data.
|
|
2154
|
+
|
|
2155
|
+
### A. Authentication & Authorization (OWASP A01, A07)
|
|
2156
|
+
A1. Are all API routes / server endpoints protected by auth middleware? List any unprotected routes.
|
|
2157
|
+
A2. Is there role-based or attribute-based access control? Are authorization checks per-resource, not just per-route?
|
|
2158
|
+
A3. Are session tokens / JWTs validated on every request? Is token expiry enforced?
|
|
2159
|
+
A4. Are passwords hashed with a strong algorithm (bcrypt/scrypt/argon2), not MD5/SHA?
|
|
2160
|
+
A5. Is there brute-force protection (rate limiting on login, account lockout)?
|
|
2161
|
+
A6. Are OAuth/SSO flows using state parameters to prevent CSRF?
|
|
2162
|
+
A7. Are auth tokens stored in httpOnly secure cookies (not localStorage/sessionStorage)?
|
|
2163
|
+
A8. Do login, signup, and password reset flows avoid revealing whether an account exists (user enumeration)?
|
|
2164
|
+
A9. Are session/JWT lifetimes capped (<=7 days for JWTs)? Is refresh token rotation implemented?
|
|
2165
|
+
|
|
2166
|
+
### B. Injection & Input Validation (OWASP A03)
|
|
2167
|
+
B1. Are user inputs validated/sanitized before use in queries, commands, or templates?
|
|
2168
|
+
B2. Is there parameterized query usage (no string concatenation for SQL/Firestore)?
|
|
2169
|
+
B3. Are file uploads validated for type, size, and content?
|
|
2170
|
+
B4. Is there protection against NoSQL injection (especially for Firestore/MongoDB)?
|
|
2171
|
+
B5. Is untrusted data deserialized safely? Check for pickle.load, yaml.load, eval, new Function on user input.
|
|
2172
|
+
B6. Are user-supplied file paths sanitized to prevent directory traversal (../ sequences)?
|
|
2173
|
+
|
|
2174
|
+
### C. Data Protection (OWASP A02, A04)
|
|
2175
|
+
C1. Is PII encrypted at rest? Are sensitive fields (SSN, credit card) masked in logs?
|
|
2176
|
+
C2. Are API responses stripped of internal fields (stack traces, internal IDs, user emails of other users)?
|
|
2177
|
+
C3. Is data access scoped per-user/per-org? Can User A access User B's data?
|
|
2178
|
+
C4. Are .env files, secrets, and API keys excluded from version control?
|
|
2179
|
+
C5. Is HTTPS enforced? Are security headers set (HSTS, CSP, X-Frame-Options)?
|
|
2180
|
+
C6. Are default credentials, example configs with real-looking values, or scaffolding secrets still present?
|
|
2181
|
+
C7. Is debug mode, verbose logging, or dev tools (DEBUG=*, React DevTools, source maps) disabled in production?
|
|
2182
|
+
C8. Is there a secret rotation schedule? Are long-lived API keys (>90 days) flagged?
|
|
2183
|
+
|
|
2184
|
+
### D. API Security (OWASP A05, A06, A08)
|
|
2185
|
+
D1. Is there rate limiting on sensitive endpoints (auth, payment, data export)?
|
|
2186
|
+
D2. Is CSRF protection enabled on state-changing endpoints?
|
|
2187
|
+
D3. Are error responses generic (no stack traces, internal paths, or SQL errors leaked)?
|
|
2188
|
+
D4. Is there request size limiting to prevent payload abuse?
|
|
2189
|
+
D5. Are CORS origins explicitly whitelisted (not wildcard)?
|
|
2190
|
+
D6. Do API endpoints return only needed fields (no over-fetching of user records, internal IDs, or other users' data)?
|
|
2191
|
+
D7. Do sensitive actions (account deletion, email change, role escalation) require re-authentication or a confirmation step?
|
|
2192
|
+
D8. Is payment/billing logic validated server-side? Can prices or quantities be tampered with client-side?
|
|
2193
|
+
D9. Are redirect URLs validated against an allowlist? Can open redirects be exploited for phishing?
|
|
2194
|
+
D10. Are webhook signatures verified before processing payment or event data?
|
|
2195
|
+
D11. Are auth/session/API tokens kept out of URL query params (including callback/returnUrl redirects) and transported via headers or httpOnly cookies instead?
|
|
2196
|
+
D12. Are secrets (revalidate/API/webhook/etc.) kept out of URL query params and accepted only via headers or signed bodies?
|
|
2197
|
+
D13. Are side-effecting endpoints using non-GET methods, and are cookie-auth GET fallbacks protected with strict same-origin checks?
|
|
2198
|
+
|
|
2199
|
+
### E. Security Rules & Infrastructure
|
|
2200
|
+
E1. For Firestore/database rules: do they enforce per-user data isolation?
|
|
2201
|
+
E2. Are Cloud Function / serverless permissions least-privilege?
|
|
2202
|
+
E3. Are admin/internal APIs separated from public APIs?
|
|
2203
|
+
E4. Is there audit logging for sensitive operations (data access, auth events, config changes)?
|
|
2204
|
+
E5. Are critical actions (deletions, role changes, payments, exports) logged with actor, timestamp, and resource?
|
|
2205
|
+
E6. Are backups automated and regularly tested with restore verification?
|
|
2206
|
+
E7. Are test and production environments fully separated? Do test webhooks avoid touching production systems?
|
|
2207
|
+
|
|
2208
|
+
### F. Dependency & Supply Chain
|
|
2209
|
+
F1. Are there known-vulnerable dependencies (check versions against ecosystem data)?
|
|
2210
|
+
F2. Are lockfiles committed? Is there integrity checking?
|
|
2211
|
+
F3. Are AI/LLM API keys scoped with minimal permissions?
|
|
2212
|
+
F4. Are AI API costs capped in code (max_tokens, request budgets) and/or provider dashboards?
|
|
2213
|
+
|
|
2214
|
+
### G. Framework-Specific (check based on detected ecosystem)
|
|
2215
|
+
- **Next.js**: Are server-side-only modules imported in API routes (not client bundles)? Is getServerSideProps/getStaticProps leaking secrets to props?
|
|
2216
|
+
- **Firebase**: Are Firestore rules tested? Is Firebase Admin SDK only used server-side?
|
|
2217
|
+
- **React**: Is dangerouslySetInnerHTML used safely? Are user inputs sanitized before rendering?
|
|
2218
|
+
- **Express/Node**: Is helmet configured? Are cookies httpOnly + secure + sameSite?
|
|
2219
|
+
|
|
2220
|
+
### H. Compliance Framework Signals
|
|
2221
|
+
Flag if the codebase contains signals that would require specific compliance frameworks:
|
|
2222
|
+
- **PCI-DSS**: Stripe, Braintree, Adyen, or any payment/billing/checkout code
|
|
2223
|
+
- **HIPAA**: Health/medical data, FHIR/HL7 libraries, patient records, PHI handling
|
|
2224
|
+
- **FedRAMP**: Government contracts, FIPS requirements, .gov integrations, GovCloud
|
|
2225
|
+
- **GDPR/CCPA**: Analytics libraries (PostHog, Segment, GA), auth providers (Auth0, Clerk), EU/California user data, tracking/cookies
|
|
2226
|
+
- **OWASP LLM**: OpenAI, Anthropic, LangChain, vector DBs (Pinecone, Weaviate), RAG pipelines, AI agents
|
|
2227
|
+
- **GLBA**: Plaid, banking SDKs, KYC/AML, lending/mortgage/wealth management code
|
|
2228
|
+
- **FERPA/COPPA**: EdTech integrations (Clever, Canvas), student/minor data, classroom/school references
|
|
2229
|
+
- **iOS App Store**: iOS/Swift codepaths, App Store/TestFlight distribution, StoreKit/IAP, mobile app privacy policies
|
|
2230
|
+
|
|
2231
|
+
For each detected signal, note:
|
|
2232
|
+
- H1. Which framework(s) apply and why
|
|
2233
|
+
- H2. Whether the code currently meets key requirements of that framework
|
|
2234
|
+
- H3. Critical gaps that need immediate attention
|
|
2235
|
+
|
|
2236
|
+
### I. Scalability & Performance
|
|
2237
|
+
I1. Are there N+1 query patterns \u2014 fetching related records in loops instead of using JOINs or batch loading?
|
|
2238
|
+
I2. Are database columns used in WHERE, ORDER BY, and JOIN clauses properly indexed?
|
|
2239
|
+
I3. Are there O(n\xB2) nested loops over collections that could use Map/Set lookups?
|
|
2240
|
+
I4. Are database connections, event listeners, timers, and streams properly cleaned up (no memory leaks)?
|
|
2241
|
+
I5. Are unbounded queries paginated? Could any endpoint load an entire table into memory?
|
|
2242
|
+
I6. Is CPU-intensive or blocking I/O offloaded from the main thread/event loop?
|
|
2243
|
+
I7. Is connection pooling used for database and HTTP clients?
|
|
2244
|
+
I8. Is concurrency bounded for workers, parallel API calls, and queue consumers?
|
|
2245
|
+
|
|
2246
|
+
### J. Reliability & Code Quality
|
|
2247
|
+
J1. Do all catch blocks log or re-throw errors? Are there any empty catch blocks or swallowed promises?
|
|
2248
|
+
J2. Are external API responses validated against a schema before use?
|
|
2249
|
+
J3. Are edge cases handled (null, undefined, empty arrays, unauthenticated users, network timeouts)?
|
|
2250
|
+
J4. Are there god functions (>150 lines mixing multiple concerns)?
|
|
2251
|
+
J5. Is shared state managed from a single source of truth, or is there state duplication/drift?
|
|
2252
|
+
J6. Are all imported packages verified to exist in the registry? Any phantom/hallucinated imports?
|
|
2253
|
+
|
|
2254
|
+
### K. Infrastructure as Code (IaC)
|
|
2255
|
+
K1. Are Terraform/IAM resources using default or overly permissive policies (Action: "*", Resource: "*")?
|
|
2256
|
+
K2. Are storage buckets or blobs publicly accessible? Is ACL set to "public-read" without justification?
|
|
2257
|
+
K3. Are security groups or firewall rules open to 0.0.0.0/0 on non-HTTPS ports?
|
|
2258
|
+
K4. Are databases and storage encrypted at rest? Is encryption explicitly enabled in the IaC config?
|
|
2259
|
+
K5. Are Dockerfiles running as root? Is a non-root USER specified?
|
|
2260
|
+
K6. Are Docker images using \`latest\` tag instead of pinned version tags?
|
|
2261
|
+
K7. Are secrets or credentials passed as build args or environment variables in Docker/Compose files?
|
|
2262
|
+
K8. Are Kubernetes pods running with privileged: true or missing CPU/memory resource limits?
|
|
2263
|
+
|
|
2264
|
+
## OUTPUT FORMAT
|
|
2265
|
+
|
|
2266
|
+
Return a JSON object with exactly these fields:
|
|
2267
|
+
- productName (string): Name of the product/project.
|
|
2268
|
+
- productBrief (string): 1-2 paragraph summary of security posture, referencing the most critical findings.
|
|
2269
|
+
- risks (array of {title, description, category, severity, checklist_ref}): Risks found. severity: low|medium|high|critical. checklist_ref: the checklist ID (e.g. "A1", "C3", "I1", "K5"). category should be one of: auth, injection, data_protection, api_security, infrastructure, dependency, framework_specific, scalability, performance, reliability, code_quality, iac.
|
|
2270
|
+
- assumptions (array of {statement, category, confidence, importance}): Security assumptions the code makes implicitly. confidence/importance: 0-1.
|
|
2271
|
+
- competitors (array of {name, description, threat_level}): Any referenced tools or services. Return [] if none.
|
|
2272
|
+
- marketContext (string): Brief context about the security landscape for this type of application.
|
|
2273
|
+
- targetUsers (string): Who uses this product, from a security perspective.
|
|
2274
|
+
- referenceClasses (string[]): Security frameworks or standards that apply (e.g., "OWASP Top 10 2021", "SOC 2 Type II").
|
|
2275
|
+
- constraints (object?): Resource constraints \u2014 team, budget_usd, deadline_days, must_ship_scope.
|
|
2276
|
+
- checklist_summary (object): Keys are checklist IDs (A1-A8, B1-B6, C1-C8, D1-D13, E1-E7, F1-F4, G-*, H1-H3, I1-I8, J1-J6, K1-K8), values are "pass"|"fail"|"warn"|"not_applicable". This forces systematic coverage.
|
|
2277
|
+
- compliance_signals (array of {framework: "pci_dss"|"hipaa"|"fedramp"|"gdpr_ccpa"|"owasp_llm"|"glba"|"ferpa_coppa"|"csa_ccm"|"ios_app_store", signal: string, confidence: number}?): Detected compliance framework signals. Return [] if none.
|
|
2278
|
+
|
|
2279
|
+
Be concrete and specific. Reference file paths and line numbers where possible. If a checklist item cannot be assessed from the provided files, mark it "not_applicable" and note why. Cover ALL sections A through K.`;
|
|
2280
|
+
var FULL_SYSTEM_PROMPT = `You are a product analyst reviewing a codebase. Given file contents, an ecosystem fingerprint, and existing constraints, extract structured product context.
|
|
2281
|
+
|
|
2282
|
+
Return a JSON object with exactly these fields:
|
|
2283
|
+
- productName (string): Name of the product/project.
|
|
2284
|
+
- productBrief (string): 2-3 paragraph summary of the product, its architecture, and purpose.
|
|
2285
|
+
- risks (array of {title, description, category, severity}): Identified risks including security, architecture, scalability, and business risks. severity: low|medium|high|critical.
|
|
2286
|
+
- assumptions (array of {statement, category, confidence, importance}): Technical and business assumptions.
|
|
2287
|
+
- competitors (array of {name, description, threat_level}): Referenced alternatives or competing tools.
|
|
2288
|
+
- marketContext (string): Market/industry context for this product.
|
|
2289
|
+
- targetUsers (string): Who the product serves.
|
|
2290
|
+
- referenceClasses (string[]): Analogous products or frameworks.
|
|
2291
|
+
- constraints (object?): Resource constraints \u2014 team, budget_usd, deadline_days, must_ship_scope.
|
|
2292
|
+
|
|
2293
|
+
Be thorough but concise. Reference specific files where relevant.`;
|
|
2294
|
+
function getSystemPrompt(mode) {
|
|
2295
|
+
return mode === "security" ? SECURITY_SYSTEM_PROMPT : FULL_SYSTEM_PROMPT;
|
|
2296
|
+
}
|
|
2297
|
+
function buildUserPrompt(opts) {
|
|
2298
|
+
const parts = [];
|
|
2299
|
+
if (opts.taskDescription) {
|
|
2300
|
+
parts.push(`## Task
|
|
2301
|
+
${opts.taskDescription}`);
|
|
2302
|
+
}
|
|
2303
|
+
const iacLine = opts.ecosystem.iac_tools?.length ? `
|
|
2304
|
+
- IaC tools: ${opts.ecosystem.iac_tools.join(", ")}` : "";
|
|
2305
|
+
parts.push(`## Ecosystem
|
|
2306
|
+
- Languages: ${opts.ecosystem.languages.join(", ") || "unknown"}
|
|
2307
|
+
- Frameworks: ${opts.ecosystem.frameworks.join(", ") || "none detected"}
|
|
2308
|
+
- ORMs: ${opts.ecosystem.orms.join(", ") || "none"}
|
|
2309
|
+
- Infrastructure: ${opts.ecosystem.infrastructure.join(", ") || "none"}
|
|
2310
|
+
- Security libraries: ${opts.ecosystem.security_libs.join(", ") || "none detected"}
|
|
2311
|
+
- AI/LLM libraries: ${opts.ecosystem.ai_libs.join(", ") || "none"}
|
|
2312
|
+
- Testing: ${opts.ecosystem.testing.join(", ") || "none detected"}${iacLine}`);
|
|
2313
|
+
if (opts.scaContext) {
|
|
2314
|
+
parts.push(opts.scaContext);
|
|
2315
|
+
}
|
|
2316
|
+
if (opts.sensitiveData.fields.length > 0) {
|
|
2317
|
+
const byClass = Object.entries(opts.sensitiveData.by_class).filter(([, count]) => count > 0).map(([cls, count]) => `${cls}: ${count}`).join(", ");
|
|
2318
|
+
const topFields = opts.sensitiveData.fields.slice(0, 20).map((f) => ` - ${f.file}:${f.line} \u2014 ${f.field} (${f.classification})`).join("\n");
|
|
2319
|
+
parts.push(`## Sensitive Data Found
|
|
2320
|
+
${byClass}
|
|
2321
|
+
${topFields}`);
|
|
2322
|
+
}
|
|
2323
|
+
if (opts.existingConstraints.length > 0) {
|
|
2324
|
+
parts.push(`## Existing Constraints (from product graph)
|
|
2325
|
+
${opts.existingConstraints.slice(0, 30).join("\n")}`);
|
|
2326
|
+
}
|
|
2327
|
+
if (opts.existingGaps.length > 0) {
|
|
2328
|
+
parts.push(`## Constraint Graph Gaps
|
|
2329
|
+
${opts.existingGaps.join("\n")}`);
|
|
2330
|
+
}
|
|
2331
|
+
parts.push(`## Source Files (${opts.fileContents.length})`);
|
|
2332
|
+
for (const f of opts.fileContents) {
|
|
2333
|
+
parts.push(`### ${f.path}
|
|
2334
|
+
\`\`\`
|
|
2335
|
+
${f.content}
|
|
2336
|
+
\`\`\``);
|
|
2337
|
+
}
|
|
2338
|
+
parts.push("Extract the structured context from the above. Return valid JSON only.");
|
|
2339
|
+
return parts.join("\n\n");
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
// ../mcp/dist/mcp/src/tools/sca-scanner.js
|
|
2343
|
+
var OSV_BATCH_URL = "https://api.osv.dev/v1/querybatch";
|
|
2344
|
+
var BATCH_SIZE = 100;
|
|
2345
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
2346
|
+
var ECOSYSTEM_MAP = {
|
|
2347
|
+
npm: "npm",
|
|
2348
|
+
PyPI: "PyPI",
|
|
2349
|
+
Go: "Go",
|
|
2350
|
+
RubyGems: "RubyGems",
|
|
2351
|
+
"crates.io": "crates.io"
|
|
2352
|
+
};
|
|
2353
|
+
function cvssToSeverity(score) {
|
|
2354
|
+
if (score >= 9)
|
|
2355
|
+
return "critical";
|
|
2356
|
+
if (score >= 7)
|
|
2357
|
+
return "high";
|
|
2358
|
+
if (score >= 4)
|
|
2359
|
+
return "medium";
|
|
2360
|
+
return "low";
|
|
2361
|
+
}
|
|
2362
|
+
function extractSeverity(vuln) {
|
|
2363
|
+
if (vuln.severity?.length) {
|
|
2364
|
+
for (const s of vuln.severity) {
|
|
2365
|
+
if (s.type === "CVSS_V3" || s.type === "CVSS_V4") {
|
|
2366
|
+
const score = parseFloat(s.score);
|
|
2367
|
+
if (!isNaN(score))
|
|
2368
|
+
return { severity: cvssToSeverity(score), cvss: score };
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
const dbSev = vuln.database_specific?.severity?.toUpperCase();
|
|
2373
|
+
if (dbSev === "CRITICAL")
|
|
2374
|
+
return { severity: "critical" };
|
|
2375
|
+
if (dbSev === "HIGH")
|
|
2376
|
+
return { severity: "high" };
|
|
2377
|
+
if (dbSev === "MODERATE" || dbSev === "MEDIUM")
|
|
2378
|
+
return { severity: "medium" };
|
|
2379
|
+
if (dbSev === "LOW")
|
|
2380
|
+
return { severity: "low" };
|
|
2381
|
+
return { severity: "medium" };
|
|
2382
|
+
}
|
|
2383
|
+
function extractFixedVersion(vuln) {
|
|
2384
|
+
for (const aff of vuln.affected ?? []) {
|
|
2385
|
+
for (const range of aff.ranges ?? []) {
|
|
2386
|
+
for (const event of range.events ?? []) {
|
|
2387
|
+
if (event.fixed)
|
|
2388
|
+
return event.fixed;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
return void 0;
|
|
2393
|
+
}
|
|
2394
|
+
async function queryOsvBatch(queries) {
|
|
2395
|
+
const controller = new AbortController();
|
|
2396
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
2397
|
+
try {
|
|
2398
|
+
const res = await fetch(OSV_BATCH_URL, {
|
|
2399
|
+
method: "POST",
|
|
2400
|
+
headers: { "Content-Type": "application/json" },
|
|
2401
|
+
body: JSON.stringify({ queries }),
|
|
2402
|
+
signal: controller.signal
|
|
2403
|
+
});
|
|
2404
|
+
if (!res.ok) {
|
|
2405
|
+
console.warn(`[sca] OSV.dev returned ${res.status}: ${res.statusText}`);
|
|
2406
|
+
return { results: queries.map(() => ({ vulns: [] })) };
|
|
2407
|
+
}
|
|
2408
|
+
return await res.json();
|
|
2409
|
+
} catch (err) {
|
|
2410
|
+
console.warn("[sca] OSV.dev request failed:", err.message);
|
|
2411
|
+
return { results: queries.map(() => ({ vulns: [] })) };
|
|
2412
|
+
} finally {
|
|
2413
|
+
clearTimeout(timeout);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
async function scanDependencies(deps) {
|
|
2417
|
+
if (deps.length === 0)
|
|
2418
|
+
return [];
|
|
2419
|
+
const prodDeps = deps.filter((d) => !d.dev);
|
|
2420
|
+
const depsToScan = prodDeps.length > 0 ? prodDeps : deps;
|
|
2421
|
+
const queries = depsToScan.map((dep) => ({
|
|
2422
|
+
query: {
|
|
2423
|
+
package: { name: dep.name, ecosystem: ECOSYSTEM_MAP[dep.ecosystem] },
|
|
2424
|
+
version: dep.version
|
|
2425
|
+
},
|
|
2426
|
+
dep
|
|
2427
|
+
}));
|
|
2428
|
+
const findings = [];
|
|
2429
|
+
const seenVulnIds = /* @__PURE__ */ new Set();
|
|
2430
|
+
for (let i = 0; i < queries.length; i += BATCH_SIZE) {
|
|
2431
|
+
const batch = queries.slice(i, i + BATCH_SIZE);
|
|
2432
|
+
const response = await queryOsvBatch(batch.map((b) => b.query));
|
|
2433
|
+
for (let j = 0; j < batch.length; j++) {
|
|
2434
|
+
const vulns = response.results[j]?.vulns ?? [];
|
|
2435
|
+
const dep = batch[j].dep;
|
|
2436
|
+
for (const vuln of vulns) {
|
|
2437
|
+
if (seenVulnIds.has(vuln.id))
|
|
2438
|
+
continue;
|
|
2439
|
+
seenVulnIds.add(vuln.id);
|
|
2440
|
+
const { severity, cvss } = extractSeverity(vuln);
|
|
2441
|
+
const fixedVersion = extractFixedVersion(vuln);
|
|
2442
|
+
findings.push({
|
|
2443
|
+
id: vuln.id,
|
|
2444
|
+
package_name: dep.name,
|
|
2445
|
+
package_version: dep.version,
|
|
2446
|
+
ecosystem: dep.ecosystem,
|
|
2447
|
+
severity,
|
|
2448
|
+
cvss_score: cvss,
|
|
2449
|
+
summary: (vuln.summary || vuln.details || "No description available").slice(0, 300),
|
|
2450
|
+
fixed_version: fixedVersion,
|
|
2451
|
+
references: (vuln.references ?? []).map((r) => r.url).slice(0, 3)
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
findings.sort((a, b) => {
|
|
2457
|
+
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
2458
|
+
return order[a.severity] - order[b.severity];
|
|
2459
|
+
});
|
|
2460
|
+
return findings;
|
|
2461
|
+
}
|
|
2462
|
+
function formatScaContext(findings, maxEntries = 10) {
|
|
2463
|
+
if (findings.length === 0)
|
|
2464
|
+
return "";
|
|
2465
|
+
const lines = [
|
|
2466
|
+
`## Known Dependency Vulnerabilities (${findings.length} found)`,
|
|
2467
|
+
""
|
|
2468
|
+
];
|
|
2469
|
+
for (const f of findings.slice(0, maxEntries)) {
|
|
2470
|
+
const fix = f.fixed_version ? ` Fix: upgrade to ${f.fixed_version}.` : "";
|
|
2471
|
+
lines.push(`- **[${f.severity.toUpperCase()}]** ${f.package_name}@${f.package_version}: ${f.id} \u2014 ${f.summary}${fix}`);
|
|
2472
|
+
}
|
|
2473
|
+
if (findings.length > maxEntries) {
|
|
2474
|
+
lines.push(`- ...and ${findings.length - maxEntries} more vulnerabilities`);
|
|
2475
|
+
}
|
|
2476
|
+
return lines.join("\n");
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// ../mcp/dist/mcp/src/tools/security-scan-free.js
|
|
2480
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
2481
|
+
import { join as join2 } from "node:path";
|
|
2482
|
+
function buildGenericGraphId(uid, projectRoot) {
|
|
2483
|
+
const slug = projectRoot.split("/").filter(Boolean).pop()?.replace(/[^a-z0-9]+/gi, "-").toLowerCase().slice(0, 40) ?? "unknown";
|
|
2484
|
+
return `freeaudit:${uid}:${slug}`;
|
|
2485
|
+
}
|
|
2486
|
+
var SECURITY_PATH_PATTERNS = [
|
|
2487
|
+
/auth/i,
|
|
2488
|
+
/login/i,
|
|
2489
|
+
/session/i,
|
|
2490
|
+
/token/i,
|
|
2491
|
+
/jwt/i,
|
|
2492
|
+
/oauth/i,
|
|
2493
|
+
/api\//i,
|
|
2494
|
+
/routes?\//i,
|
|
2495
|
+
/endpoint/i,
|
|
2496
|
+
/handler/i,
|
|
2497
|
+
/middleware/i,
|
|
2498
|
+
/guard/i,
|
|
2499
|
+
/interceptor/i,
|
|
2500
|
+
/policy/i,
|
|
2501
|
+
/firestore\.rules$/i,
|
|
2502
|
+
/\.rules$/i,
|
|
2503
|
+
/security/i,
|
|
2504
|
+
/config/i,
|
|
2505
|
+
/env/i,
|
|
2506
|
+
/secret/i,
|
|
2507
|
+
/credential/i,
|
|
2508
|
+
/encrypt/i,
|
|
2509
|
+
/decrypt/i,
|
|
2510
|
+
/hash/i,
|
|
2511
|
+
/crypto/i,
|
|
2512
|
+
/schema/i,
|
|
2513
|
+
/model/i,
|
|
2514
|
+
/migration/i,
|
|
2515
|
+
/prisma/i,
|
|
2516
|
+
/drizzle/i,
|
|
2517
|
+
/payment/i,
|
|
2518
|
+
/billing/i,
|
|
2519
|
+
/stripe/i,
|
|
2520
|
+
/webhook/i,
|
|
2521
|
+
/redirect/i,
|
|
2522
|
+
/callback/i,
|
|
2523
|
+
/returnurl/i,
|
|
2524
|
+
/mcp-auth/i,
|
|
2525
|
+
/checkout-link/i,
|
|
2526
|
+
// Scalability & reliability patterns
|
|
2527
|
+
/\/db\//i,
|
|
2528
|
+
/queries?\//i,
|
|
2529
|
+
/repositor/i,
|
|
2530
|
+
/worker/i,
|
|
2531
|
+
/queue/i,
|
|
2532
|
+
/\/jobs?\//i,
|
|
2533
|
+
/cron/i,
|
|
2534
|
+
/store/i,
|
|
2535
|
+
/\/state\//i,
|
|
2536
|
+
/reducer/i,
|
|
2537
|
+
/\/context\//i,
|
|
2538
|
+
/services?\//i,
|
|
2539
|
+
/hooks?\//i,
|
|
2540
|
+
// IaC patterns
|
|
2541
|
+
/\.tf$/i,
|
|
2542
|
+
/Dockerfile/i,
|
|
2543
|
+
/docker-compose/i,
|
|
2544
|
+
/\/terraform\//i,
|
|
2545
|
+
/\/infra\//i,
|
|
2546
|
+
/\/deploy\//i,
|
|
2547
|
+
/cloudformation/i,
|
|
2548
|
+
/\/k8s\//i,
|
|
2549
|
+
/\/helm\//i
|
|
2550
|
+
];
|
|
2551
|
+
var READABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2552
|
+
".ts",
|
|
2553
|
+
".tsx",
|
|
2554
|
+
".js",
|
|
2555
|
+
".jsx",
|
|
2556
|
+
".py",
|
|
2557
|
+
".go",
|
|
2558
|
+
".rs",
|
|
2559
|
+
".java",
|
|
2560
|
+
".rb",
|
|
2561
|
+
".rules",
|
|
2562
|
+
".json",
|
|
2563
|
+
".yaml",
|
|
2564
|
+
".yml",
|
|
2565
|
+
".toml",
|
|
2566
|
+
".env",
|
|
2567
|
+
".conf",
|
|
2568
|
+
".tf",
|
|
2569
|
+
".hcl"
|
|
2570
|
+
]);
|
|
2571
|
+
function isReadableFile(filePath) {
|
|
2572
|
+
const ext = "." + filePath.split(".").pop()?.toLowerCase();
|
|
2573
|
+
if (READABLE_EXTENSIONS.has(ext))
|
|
2574
|
+
return true;
|
|
2575
|
+
const name = filePath.split("/").pop() ?? "";
|
|
2576
|
+
if (name === "Dockerfile" || /^Dockerfile\./i.test(name))
|
|
2577
|
+
return true;
|
|
2578
|
+
if (/^docker-compose/i.test(name))
|
|
2579
|
+
return true;
|
|
2580
|
+
return false;
|
|
2581
|
+
}
|
|
2582
|
+
function selectSecurityFiles(files, sensitiveFilePaths, maxFiles) {
|
|
2583
|
+
const scored = [];
|
|
2584
|
+
for (const entry of files) {
|
|
2585
|
+
if (!isReadableFile(entry.path))
|
|
2586
|
+
continue;
|
|
2587
|
+
let priority = 0;
|
|
2588
|
+
if (sensitiveFilePaths.has(entry.path))
|
|
2589
|
+
priority += 10;
|
|
2590
|
+
for (const pattern of SECURITY_PATH_PATTERNS) {
|
|
2591
|
+
if (pattern.test(entry.path)) {
|
|
2592
|
+
priority += 5;
|
|
2593
|
+
break;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
if (entry.path.includes("middleware"))
|
|
2597
|
+
priority += 3;
|
|
2598
|
+
if (entry.path.endsWith(".rules"))
|
|
2599
|
+
priority += 6;
|
|
2600
|
+
if (priority > 0)
|
|
2601
|
+
scored.push({ entry, priority });
|
|
2602
|
+
}
|
|
2603
|
+
scored.sort((a, b) => b.priority - a.priority);
|
|
2604
|
+
return scored.slice(0, maxFiles).map((s) => s.entry);
|
|
2605
|
+
}
|
|
2606
|
+
async function readFileContents(root, files, maxBytesPerFile) {
|
|
2607
|
+
const results = [];
|
|
2608
|
+
for (const file of files) {
|
|
2609
|
+
try {
|
|
2610
|
+
let content = await readFile2(join2(root, file.path), "utf-8");
|
|
2611
|
+
if (content.length > maxBytesPerFile) {
|
|
2612
|
+
content = content.slice(0, maxBytesPerFile) + "\n// ... [truncated]";
|
|
2613
|
+
}
|
|
2614
|
+
results.push({ path: file.path, content });
|
|
2615
|
+
} catch {
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
return results;
|
|
2619
|
+
}
|
|
2620
|
+
async function seedBlueprint(productId, ecosystem, deps, ctx) {
|
|
2621
|
+
if (!deps.upsertNodes || !deps.addEntity || !deps.addEdges || !deps.upsertBindings) {
|
|
2622
|
+
return { constraintsCreated: 0, frameworks: [] };
|
|
2623
|
+
}
|
|
2624
|
+
const now = /* @__PURE__ */ new Date();
|
|
2625
|
+
const sourceId = `blueprint_${Date.now()}`;
|
|
2626
|
+
const blueprintConstraints = buildBlueprintConstraints(ecosystem, ctx);
|
|
2627
|
+
const activeFrameworks = getActiveFrameworks(ecosystem, ctx);
|
|
2628
|
+
if (blueprintConstraints.length === 0) {
|
|
2629
|
+
return { constraintsCreated: 0, frameworks: activeFrameworks };
|
|
2630
|
+
}
|
|
2631
|
+
const constraintNodes = blueprintConstraints.map((bc) => ({
|
|
2632
|
+
id: `constraint:blueprint:${bc.id_suffix}`,
|
|
2633
|
+
category: bc.category,
|
|
2634
|
+
severity: bc.severity,
|
|
2635
|
+
summary: bc.summary,
|
|
2636
|
+
keywords: bc.keywords,
|
|
2637
|
+
source_id: sourceId,
|
|
2638
|
+
source_type: "premortem",
|
|
2639
|
+
ingested_at: now,
|
|
2640
|
+
action: bc.action,
|
|
2641
|
+
phase: "mvp"
|
|
2642
|
+
}));
|
|
2643
|
+
await deps.upsertNodes(productId, "premortem", sourceId, constraintNodes);
|
|
2644
|
+
const categorySet = new Set(blueprintConstraints.map((bc) => bc.category));
|
|
2645
|
+
const entities = [];
|
|
2646
|
+
const edges = [];
|
|
2647
|
+
const bindings = [];
|
|
2648
|
+
for (const category of categorySet) {
|
|
2649
|
+
const entityId = `component:blueprint_${category}`;
|
|
2650
|
+
entities.push({
|
|
2651
|
+
id: entityId,
|
|
2652
|
+
type: "component",
|
|
2653
|
+
name: `Security Blueprint: ${category}`,
|
|
2654
|
+
aliases: [category, "security", "blueprint"],
|
|
2655
|
+
description: `Proactive ${category} constraints from security blueprint`,
|
|
2656
|
+
source_id: sourceId,
|
|
2657
|
+
source_type: "premortem",
|
|
2658
|
+
ingested_at: now
|
|
2659
|
+
});
|
|
2660
|
+
const categoryConstraints = blueprintConstraints.filter((bc) => bc.category === category);
|
|
2661
|
+
for (const bc of categoryConstraints) {
|
|
2662
|
+
const constraintId = `constraint:blueprint:${bc.id_suffix}`;
|
|
2663
|
+
edges.push({
|
|
2664
|
+
id: `edge:blueprint:${entityId}:${constraintId}`,
|
|
2665
|
+
source_id: entityId,
|
|
2666
|
+
source_type: "component",
|
|
2667
|
+
target_id: constraintId,
|
|
2668
|
+
target_type: "constraint",
|
|
2669
|
+
edge_type: "BOUNDED_BY"
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
if (bc_filePatterns(category, ecosystem).length > 0) {
|
|
2673
|
+
bindings.push({
|
|
2674
|
+
id: `binding:blueprint:${category}`,
|
|
2675
|
+
entity_id: entityId,
|
|
2676
|
+
entity_type: "component",
|
|
2677
|
+
file_patterns: bc_filePatterns(category, ecosystem),
|
|
2678
|
+
confidence: 0.7,
|
|
2679
|
+
manual: false
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
for (const entity of entities)
|
|
2684
|
+
await deps.addEntity(productId, entity);
|
|
2685
|
+
if (edges.length > 0)
|
|
2686
|
+
await deps.addEdges(productId, edges);
|
|
2687
|
+
if (bindings.length > 0)
|
|
2688
|
+
await deps.upsertBindings(productId, bindings);
|
|
2689
|
+
return { constraintsCreated: constraintNodes.length, frameworks: activeFrameworks };
|
|
2690
|
+
}
|
|
2691
|
+
function bc_filePatterns(category, ecosystem) {
|
|
2692
|
+
const patterns = [];
|
|
2693
|
+
if (category === "security" || category === "compliance") {
|
|
2694
|
+
patterns.push("**/auth/**", "**/security/**", "**/middleware/**");
|
|
2695
|
+
if (ecosystem.frameworks.some((f) => /firebase|firestore/i.test(f)))
|
|
2696
|
+
patterns.push("**/firestore.rules");
|
|
2697
|
+
}
|
|
2698
|
+
if (category === "infrastructure")
|
|
2699
|
+
patterns.push("**/config/**", "**/deploy/**", "**/infra/**");
|
|
2700
|
+
if (category === "performance")
|
|
2701
|
+
patterns.push("**/api/**", "**/routes/**");
|
|
2702
|
+
return patterns;
|
|
2703
|
+
}
|
|
2704
|
+
async function ingestFindings(productId, extracted, fileContents, deps) {
|
|
2705
|
+
if (!deps.upsertNodes || !deps.addEntity || !deps.addEdges || !deps.upsertBindings)
|
|
2706
|
+
return 0;
|
|
2707
|
+
if (!extracted.risks?.length)
|
|
2708
|
+
return 0;
|
|
2709
|
+
const now = /* @__PURE__ */ new Date();
|
|
2710
|
+
const sourceId = `audit_${Date.now()}`;
|
|
2711
|
+
const actionableRisks = extracted.risks.filter((r) => r.checklist_ref && extracted.checklist_summary?.[r.checklist_ref] !== "pass");
|
|
2712
|
+
if (actionableRisks.length === 0)
|
|
2713
|
+
return 0;
|
|
2714
|
+
const domainRisks = /* @__PURE__ */ new Map();
|
|
2715
|
+
for (const risk of actionableRisks) {
|
|
2716
|
+
const domain = risk.category?.toLowerCase().replace(/[^a-z0-9]+/g, "_") || "general";
|
|
2717
|
+
const existing = domainRisks.get(domain) ?? [];
|
|
2718
|
+
existing.push(risk);
|
|
2719
|
+
domainRisks.set(domain, existing);
|
|
2720
|
+
}
|
|
2721
|
+
let created = 0;
|
|
2722
|
+
for (const [domain, risks] of domainRisks) {
|
|
2723
|
+
const entityId = `component:security_${domain}`;
|
|
2724
|
+
await deps.addEntity(productId, {
|
|
2725
|
+
id: entityId,
|
|
2726
|
+
type: "component",
|
|
2727
|
+
name: `Security: ${domain.replace(/_/g, " ")}`,
|
|
2728
|
+
aliases: [domain, "security", "audit"],
|
|
2729
|
+
source_id: sourceId,
|
|
2730
|
+
source_type: "premortem",
|
|
2731
|
+
ingested_at: now
|
|
2732
|
+
});
|
|
2733
|
+
const constraintNodes = [];
|
|
2734
|
+
const edges = [];
|
|
2735
|
+
for (let i = 0; i < risks.length; i++) {
|
|
2736
|
+
const risk = risks[i];
|
|
2737
|
+
const cid = `constraint:audit:${domain}:${i}`;
|
|
2738
|
+
constraintNodes.push({
|
|
2739
|
+
id: cid,
|
|
2740
|
+
category: "security",
|
|
2741
|
+
severity: risk.severity === "critical" ? "critical" : risk.severity === "high" ? "critical" : "warning",
|
|
2742
|
+
summary: risk.description?.slice(0, 200) || risk.title,
|
|
2743
|
+
keywords: [domain, "security", "audit", ...risk.category ? [risk.category] : []],
|
|
2744
|
+
source_id: sourceId,
|
|
2745
|
+
source_type: "premortem",
|
|
2746
|
+
ingested_at: now,
|
|
2747
|
+
action: `Address: ${risk.title}`
|
|
2748
|
+
});
|
|
2749
|
+
edges.push({
|
|
2750
|
+
id: `edge:audit:${entityId}:${cid}`,
|
|
2751
|
+
source_id: entityId,
|
|
2752
|
+
source_type: "component",
|
|
2753
|
+
target_id: cid,
|
|
2754
|
+
target_type: "constraint",
|
|
2755
|
+
edge_type: "BOUNDED_BY"
|
|
2756
|
+
});
|
|
2757
|
+
created++;
|
|
2758
|
+
}
|
|
2759
|
+
await deps.upsertNodes(productId, "premortem", sourceId, constraintNodes);
|
|
2760
|
+
if (edges.length > 0)
|
|
2761
|
+
await deps.addEdges(productId, edges);
|
|
2762
|
+
const auditedFiles = fileContents.map((f) => `**/${f.path.split("/").pop()}`);
|
|
2763
|
+
if (auditedFiles.length > 0) {
|
|
2764
|
+
await deps.upsertBindings(productId, [{
|
|
2765
|
+
id: `binding:audit:${domain}`,
|
|
2766
|
+
entity_id: entityId,
|
|
2767
|
+
entity_type: "component",
|
|
2768
|
+
file_patterns: auditedFiles.slice(0, 10),
|
|
2769
|
+
confidence: 0.8,
|
|
2770
|
+
manual: false
|
|
2771
|
+
}]);
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
return created;
|
|
2775
|
+
}
|
|
2776
|
+
async function ingestScaFindingsFree(graphId, findings, deps) {
|
|
2777
|
+
if (!deps.upsertNodes || !deps.addEntity || !deps.addEdges || !deps.upsertBindings)
|
|
2778
|
+
return;
|
|
2779
|
+
const now = /* @__PURE__ */ new Date();
|
|
2780
|
+
const sourceId = `sca_${Date.now()}`;
|
|
2781
|
+
const entityId = "component:dependencies";
|
|
2782
|
+
await deps.addEntity(graphId, {
|
|
2783
|
+
id: entityId,
|
|
2784
|
+
type: "component",
|
|
2785
|
+
name: "Dependencies (SCA)",
|
|
2786
|
+
aliases: ["dependencies", "sca", "packages"],
|
|
2787
|
+
description: `${findings.length} known vulnerability(s) detected`,
|
|
2788
|
+
source_id: sourceId,
|
|
2789
|
+
source_type: "premortem",
|
|
2790
|
+
ingested_at: now
|
|
2791
|
+
});
|
|
2792
|
+
const constraints = [];
|
|
2793
|
+
const edges = [];
|
|
2794
|
+
const MAX_SCA_CONSTRAINTS = 50;
|
|
2795
|
+
for (const f of findings.slice(0, MAX_SCA_CONSTRAINTS)) {
|
|
2796
|
+
const cid = `constraint:sca:${f.id.toLowerCase().replace(/[^a-z0-9-]/g, "_")}`;
|
|
2797
|
+
const fix = f.fixed_version ? ` Fix: upgrade to ${f.fixed_version}.` : "";
|
|
2798
|
+
constraints.push({
|
|
2799
|
+
id: cid,
|
|
2800
|
+
category: "dependency",
|
|
2801
|
+
severity: f.severity === "critical" || f.severity === "high" ? "critical" : "warning",
|
|
2802
|
+
summary: `[${f.id}] ${f.package_name}@${f.package_version}: ${f.summary.slice(0, 200)}${fix}`,
|
|
2803
|
+
keywords: [f.package_name, "cve", "vulnerability", "sca", "dependency"],
|
|
2804
|
+
source_id: sourceId,
|
|
2805
|
+
source_type: "premortem",
|
|
2806
|
+
ingested_at: now,
|
|
2807
|
+
action: f.fixed_version ? `Upgrade ${f.package_name} to ${f.fixed_version}` : `Review ${f.package_name}@${f.package_version} for ${f.id}`
|
|
2808
|
+
});
|
|
2809
|
+
edges.push({
|
|
2810
|
+
id: `edge:sca:${entityId}:${cid}`,
|
|
2811
|
+
source_id: entityId,
|
|
2812
|
+
source_type: "component",
|
|
2813
|
+
target_id: cid,
|
|
2814
|
+
target_type: "constraint",
|
|
2815
|
+
edge_type: "BOUNDED_BY"
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
await deps.upsertNodes(graphId, "premortem", sourceId, constraints);
|
|
2819
|
+
if (edges.length > 0)
|
|
2820
|
+
await deps.addEdges(graphId, edges);
|
|
2821
|
+
await deps.upsertBindings(graphId, [{
|
|
2822
|
+
id: "binding:sca:dependencies",
|
|
2823
|
+
entity_id: entityId,
|
|
2824
|
+
entity_type: "component",
|
|
2825
|
+
file_patterns: [
|
|
2826
|
+
"**/package-lock.json",
|
|
2827
|
+
"**/yarn.lock",
|
|
2828
|
+
"**/pnpm-lock.yaml",
|
|
2829
|
+
"**/requirements.txt",
|
|
2830
|
+
"**/poetry.lock",
|
|
2831
|
+
"**/go.sum",
|
|
2832
|
+
"**/Gemfile.lock"
|
|
2833
|
+
],
|
|
2834
|
+
confidence: 0.95,
|
|
2835
|
+
manual: false
|
|
2836
|
+
}]);
|
|
2837
|
+
}
|
|
2838
|
+
function buildExtractionPrompt(fileContents, ecosystem, sensitiveDataCount, scaContext) {
|
|
2839
|
+
const fileSummary = fileContents.map((f) => `### ${f.path}
|
|
2840
|
+
${f.content}`).join("\n\n");
|
|
2841
|
+
const system = `You are a senior engineering auditor. Analyze the provided source code and produce a structured assessment covering security, scalability, and reliability. Focus on ACTIVE ISSUES \u2014 real vulnerabilities, anti-patterns, or gaps in the code as written today:
|
|
2842
|
+
1. Authentication & authorization gaps
|
|
2843
|
+
2. Input validation & injection risks (including deserialization and path traversal)
|
|
2844
|
+
3. Data exposure & PII handling
|
|
2845
|
+
4. API security & rate limiting
|
|
2846
|
+
5. Dependency & configuration risks
|
|
2847
|
+
6. Scalability risks (N+1 queries, missing indexes, unbounded queries, blocking I/O, no connection pooling)
|
|
2848
|
+
7. Reliability risks (silent failures / empty catch blocks, unvalidated API contracts, edge case neglect, state drift)
|
|
2849
|
+
8. Infrastructure-as-Code misconfigurations (Terraform, Docker, Kubernetes)
|
|
2850
|
+
|
|
2851
|
+
IMPORTANT: You MUST return at least one risk, even for well-maintained codebases. If no critical/high issues exist, identify the single most impactful medium or low-severity improvement \u2014 e.g., missing security headers, suboptimal error handling, a missing index, or a hardening opportunity. There is always a most-important next thing to improve.
|
|
2852
|
+
Return ONLY valid JSON matching the schema.`;
|
|
2853
|
+
const scaSection = scaContext ? `
|
|
2854
|
+
|
|
2855
|
+
${scaContext}` : "";
|
|
2856
|
+
const iacLine = ecosystem.iac_tools?.length ? `
|
|
2857
|
+
IaC tools detected: ${ecosystem.iac_tools.join(", ")}` : "";
|
|
2858
|
+
const user = `Ecosystem: ${JSON.stringify(ecosystem)}${iacLine}
|
|
2859
|
+
Sensitive data locations found: ${sensitiveDataCount}${scaSection}
|
|
2860
|
+
|
|
2861
|
+
Source files:
|
|
2862
|
+
${fileSummary}
|
|
2863
|
+
|
|
2864
|
+
Produce a JSON object with:
|
|
2865
|
+
{
|
|
2866
|
+
"risks": [{ "title": string, "description": string, "category": "auth"|"injection"|"data_protection"|"api_security"|"infrastructure"|"dependency"|"iac"|"scalability"|"performance"|"reliability"|"code_quality", "severity": "critical"|"high"|"medium"|"low", "checklist_ref": string }],
|
|
2867
|
+
"checklist_summary": { [ref: string]: "pass"|"fail"|"warn"|"not_applicable" },
|
|
2868
|
+
"productName": string,
|
|
2869
|
+
"productBrief": string
|
|
2870
|
+
}`;
|
|
2871
|
+
return { system, user };
|
|
2872
|
+
}
|
|
2873
|
+
async function handleSecurityScan(genericGraphId, uid, args, deps) {
|
|
2874
|
+
const maxFileBytes = args.max_file_bytes ?? 5e4;
|
|
2875
|
+
let previousMetrics;
|
|
2876
|
+
try {
|
|
2877
|
+
const prevEntities = await deps.getAllEntities(genericGraphId);
|
|
2878
|
+
if (prevEntities.length > 0) {
|
|
2879
|
+
const [prevEdges, prevConstraints, prevBindings] = await Promise.all([
|
|
2880
|
+
deps.getAllEdges(genericGraphId),
|
|
2881
|
+
deps.getAllNodes(genericGraphId),
|
|
2882
|
+
deps.getAllBindings(genericGraphId)
|
|
2883
|
+
]);
|
|
2884
|
+
previousMetrics = computeGenericGraphMetrics(prevEntities, prevEdges, prevConstraints, prevBindings);
|
|
2885
|
+
}
|
|
2886
|
+
} catch {
|
|
2887
|
+
}
|
|
2888
|
+
const scan = await scanCodebase({ root: args.project_root });
|
|
2889
|
+
let scaFindings = [];
|
|
2890
|
+
if (scan.dependencies.length > 0) {
|
|
2891
|
+
try {
|
|
2892
|
+
scaFindings = await scanDependencies(scan.dependencies);
|
|
2893
|
+
} catch (e) {
|
|
2894
|
+
console.warn("[code_audit] SCA scan failed:", e.message);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
const sensitiveFilePaths = new Set(scan.sensitive_data.fields?.map((f) => f.file).filter(Boolean) ?? []);
|
|
2898
|
+
const selectedFiles = selectSecurityFiles(scan.files, sensitiveFilePaths, 15);
|
|
2899
|
+
const fileContents = await readFileContents(args.project_root, selectedFiles, maxFileBytes);
|
|
2900
|
+
const existingNodes = await deps.getAllNodes(genericGraphId);
|
|
2901
|
+
const existingIds = existingNodes.map((n) => n.id);
|
|
2902
|
+
let constraintsSeeded = 0;
|
|
2903
|
+
let frameworksLoaded = [];
|
|
2904
|
+
if (graphNeedsBlueprintSeeding(existingIds)) {
|
|
2905
|
+
const blueprintResult = await seedBlueprint(genericGraphId, scan.ecosystem, deps);
|
|
2906
|
+
constraintsSeeded = blueprintResult.constraintsCreated;
|
|
2907
|
+
frameworksLoaded = blueprintResult.frameworks.map((f) => String(f));
|
|
2908
|
+
}
|
|
2909
|
+
let findingsIngested = 0;
|
|
2910
|
+
let extractedContext = null;
|
|
2911
|
+
if (fileContents.length > 0) {
|
|
2912
|
+
const prompt = buildExtractionPrompt(fileContents, scan.ecosystem, scan.sensitive_data.fields?.length ?? 0, formatScaContext(scaFindings));
|
|
2913
|
+
try {
|
|
2914
|
+
const raw = await deps.generateStructuredContent({
|
|
2915
|
+
system: prompt.system,
|
|
2916
|
+
user: prompt.user,
|
|
2917
|
+
temperature: 0.1
|
|
2918
|
+
});
|
|
2919
|
+
extractedContext = JSON.parse(raw);
|
|
2920
|
+
findingsIngested = await ingestFindings(genericGraphId, extractedContext, fileContents, deps);
|
|
2921
|
+
} catch (e) {
|
|
2922
|
+
console.error("[security_scan] LLM extraction failed:", e.message);
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
if (scaFindings.length > 0 && deps.upsertNodes && deps.addEntity && deps.addEdges && deps.upsertBindings) {
|
|
2926
|
+
await ingestScaFindingsFree(genericGraphId, scaFindings, deps);
|
|
2927
|
+
findingsIngested += scaFindings.length;
|
|
2928
|
+
}
|
|
2929
|
+
const [entities, edges, constraints, bindings] = await Promise.all([
|
|
2930
|
+
deps.getAllEntities(genericGraphId),
|
|
2931
|
+
deps.getAllEdges(genericGraphId),
|
|
2932
|
+
deps.getAllNodes(genericGraphId),
|
|
2933
|
+
deps.getAllBindings(genericGraphId)
|
|
2934
|
+
]);
|
|
2935
|
+
const metrics = computeGenericGraphMetrics(entities, edges, constraints, bindings);
|
|
2936
|
+
if (deps.updateGraphMetadata) {
|
|
2937
|
+
await deps.updateGraphMetadata(genericGraphId, {
|
|
2938
|
+
metrics,
|
|
2939
|
+
type: "generic",
|
|
2940
|
+
uid,
|
|
2941
|
+
ecosystem: scan.ecosystem,
|
|
2942
|
+
project_root_hash: args.project_root,
|
|
2943
|
+
last_scan: /* @__PURE__ */ new Date()
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
const boundEntityIds = new Set(bindings.map((b) => b.entity_id));
|
|
2947
|
+
const totalEntities = entities.length;
|
|
2948
|
+
const healthyBindings = entities.filter((e) => boundEntityIds.has(e.id)).length;
|
|
2949
|
+
const allRisks = extractedContext?.risks ?? [];
|
|
2950
|
+
const criticalRisks = allRisks.filter((r) => r.severity === "critical" || r.severity === "high");
|
|
2951
|
+
const gatedGapDetails = allRisks.map((r) => ({
|
|
2952
|
+
title: r.title,
|
|
2953
|
+
description: r.description ?? "",
|
|
2954
|
+
severity: r.severity ?? "medium",
|
|
2955
|
+
category: r.category ?? "general"
|
|
2956
|
+
}));
|
|
2957
|
+
if (gatedGapDetails.length === 0 && metrics) {
|
|
2958
|
+
const pillars = [
|
|
2959
|
+
{
|
|
2960
|
+
name: "Reliability",
|
|
2961
|
+
score: metrics.reliability_readiness_pct ?? 100,
|
|
2962
|
+
category: "reliability",
|
|
2963
|
+
advice: "Improve error handling coverage, add retry logic for external calls, and ensure all async operations have proper failure modes."
|
|
2964
|
+
},
|
|
2965
|
+
{
|
|
2966
|
+
name: "Scalability",
|
|
2967
|
+
score: metrics.scalability_readiness_pct ?? 100,
|
|
2968
|
+
category: "scalability",
|
|
2969
|
+
advice: "Review database query patterns for N+1 issues, ensure connection pooling is configured, and add pagination to list endpoints."
|
|
2970
|
+
},
|
|
2971
|
+
{
|
|
2972
|
+
name: "Engineering",
|
|
2973
|
+
score: metrics.engineering_readiness_pct ?? 100,
|
|
2974
|
+
category: "code_quality",
|
|
2975
|
+
advice: "Increase constraint binding coverage by adding tests, improving input validation, and documenting non-obvious architectural decisions."
|
|
2976
|
+
},
|
|
2977
|
+
{
|
|
2978
|
+
name: "Security",
|
|
2979
|
+
score: metrics.security_readiness_pct ?? 100,
|
|
2980
|
+
category: "auth",
|
|
2981
|
+
advice: "Review authentication flows, tighten CORS policies, add security headers (CSP, HSTS), and ensure all sensitive endpoints have rate limiting."
|
|
2982
|
+
}
|
|
2983
|
+
];
|
|
2984
|
+
const weakest = pillars.sort((a, b) => a.score - b.score)[0];
|
|
2985
|
+
if (weakest.score < 100) {
|
|
2986
|
+
gatedGapDetails.push({
|
|
2987
|
+
title: `${weakest.name} readiness at ${weakest.score.toFixed(0)}% \u2014 room for improvement`,
|
|
2988
|
+
description: weakest.advice,
|
|
2989
|
+
severity: weakest.score < 40 ? "high" : weakest.score < 60 ? "medium" : "low",
|
|
2990
|
+
category: weakest.category
|
|
2991
|
+
});
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
return {
|
|
2995
|
+
genericGraphId,
|
|
2996
|
+
metrics,
|
|
2997
|
+
previousMetrics,
|
|
2998
|
+
ecosystem: {
|
|
2999
|
+
languages: scan.ecosystem.languages,
|
|
3000
|
+
frameworks: scan.ecosystem.frameworks,
|
|
3001
|
+
orms: scan.ecosystem.orms ?? []
|
|
3002
|
+
},
|
|
3003
|
+
sensitiveDataCount: scan.sensitive_data.fields?.length ?? 0,
|
|
3004
|
+
constraintsSeeded,
|
|
3005
|
+
findingsIngested,
|
|
3006
|
+
frameworksLoaded,
|
|
3007
|
+
bindingHealth: {
|
|
3008
|
+
healthy: healthyBindings,
|
|
3009
|
+
total: totalEntities,
|
|
3010
|
+
coverage_pct: totalEntities > 0 ? Math.round(healthyBindings / totalEntities * 100) : 0
|
|
3011
|
+
},
|
|
3012
|
+
securityGaps: criticalRisks.map((r) => `[${r.severity?.toUpperCase()}] ${r.title}`),
|
|
3013
|
+
gatedGapDetails,
|
|
3014
|
+
scaFindings: scaFindings.length > 0 ? {
|
|
3015
|
+
total: scaFindings.length,
|
|
3016
|
+
critical: scaFindings.filter((f) => f.severity === "critical").length,
|
|
3017
|
+
high: scaFindings.filter((f) => f.severity === "high").length,
|
|
3018
|
+
all: scaFindings.map((f) => ({
|
|
3019
|
+
id: f.id,
|
|
3020
|
+
package: `${f.package_name}@${f.package_version}`,
|
|
3021
|
+
severity: f.severity,
|
|
3022
|
+
fix: f.fixed_version ? `Upgrade to ${f.fixed_version}` : void 0
|
|
3023
|
+
})),
|
|
3024
|
+
top: scaFindings.slice(0, 5).map((f) => ({
|
|
3025
|
+
id: f.id,
|
|
3026
|
+
package: `${f.package_name}@${f.package_version}`,
|
|
3027
|
+
severity: f.severity,
|
|
3028
|
+
fix: f.fixed_version ? `Upgrade to ${f.fixed_version}` : void 0
|
|
3029
|
+
}))
|
|
3030
|
+
} : void 0,
|
|
3031
|
+
selectedFilePaths: selectedFiles.map((entry) => String(entry.path || "").trim()).filter(Boolean)
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
function deltaStr(current, previous) {
|
|
3035
|
+
if (previous === void 0 || current === void 0)
|
|
3036
|
+
return "";
|
|
3037
|
+
const diff = current - previous;
|
|
3038
|
+
if (diff === 0)
|
|
3039
|
+
return " (no change)";
|
|
3040
|
+
return diff > 0 ? ` (**+${diff}** since last scan)` : ` (**${diff}** since last scan)`;
|
|
3041
|
+
}
|
|
3042
|
+
function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline.ai", hiddenAuditDimensions = [], fullReport = false) {
|
|
3043
|
+
const m = result.metrics;
|
|
3044
|
+
const p = result.previousMetrics;
|
|
3045
|
+
const isRescan = !!p;
|
|
3046
|
+
const hiddenSet = new Set(hiddenAuditDimensions.map((d) => String(d).trim().toLowerCase()));
|
|
3047
|
+
const securityVisible = !hiddenSet.has("security");
|
|
3048
|
+
const inferFindingDimension = (category) => {
|
|
3049
|
+
const c = (category ?? "").toLowerCase();
|
|
3050
|
+
if (["reliability"].includes(c))
|
|
3051
|
+
return "reliability";
|
|
3052
|
+
if (["scalability", "performance"].includes(c))
|
|
3053
|
+
return "scalability";
|
|
3054
|
+
if (["compliance"].includes(c))
|
|
3055
|
+
return "compliance";
|
|
3056
|
+
if (["code_quality", "general"].includes(c))
|
|
3057
|
+
return "engineering";
|
|
3058
|
+
return "security";
|
|
3059
|
+
};
|
|
3060
|
+
const hasComplianceFrameworks = result.frameworksLoaded.length > 0;
|
|
3061
|
+
const complianceCurrent = hasComplianceFrameworks ? Math.round((m.nfr_coverage?.compliance ?? 0) * 100) : void 0;
|
|
3062
|
+
const compliancePrevious = hasComplianceFrameworks ? Math.round((p?.nfr_coverage?.compliance ?? 0) * 100) : void 0;
|
|
3063
|
+
const lines = [
|
|
3064
|
+
`# Cutline Code Audit`,
|
|
3065
|
+
``,
|
|
3066
|
+
`**Ecosystem:** ${result.ecosystem.languages.join(", ")} / ${result.ecosystem.frameworks.join(", ") || "none detected"}`,
|
|
3067
|
+
`**Sensitive data locations:** ${result.sensitiveDataCount}`
|
|
3068
|
+
];
|
|
3069
|
+
if (result.frameworksLoaded.length > 0) {
|
|
3070
|
+
lines.push(`**Compliance frameworks:** ${result.frameworksLoaded.join(", ")}`);
|
|
3071
|
+
}
|
|
3072
|
+
const scoreRows = [
|
|
3073
|
+
{
|
|
3074
|
+
key: "engineering",
|
|
3075
|
+
label: "Engineering",
|
|
3076
|
+
current: m.engineering_readiness_pct ?? 0,
|
|
3077
|
+
previous: p?.engineering_readiness_pct
|
|
3078
|
+
},
|
|
3079
|
+
{
|
|
3080
|
+
key: "security",
|
|
3081
|
+
label: "Security",
|
|
3082
|
+
current: m.security_readiness_pct ?? 0,
|
|
3083
|
+
previous: p?.security_readiness_pct
|
|
3084
|
+
},
|
|
3085
|
+
{
|
|
3086
|
+
key: "reliability",
|
|
3087
|
+
label: "Reliability",
|
|
3088
|
+
current: m.reliability_readiness_pct ?? 0,
|
|
3089
|
+
previous: p?.reliability_readiness_pct
|
|
3090
|
+
},
|
|
3091
|
+
{
|
|
3092
|
+
key: "scalability",
|
|
3093
|
+
label: "Scalability",
|
|
3094
|
+
current: m.scalability_readiness_pct ?? 0,
|
|
3095
|
+
previous: p?.scalability_readiness_pct
|
|
3096
|
+
},
|
|
3097
|
+
{
|
|
3098
|
+
key: "compliance",
|
|
3099
|
+
label: "Compliance",
|
|
3100
|
+
current: complianceCurrent,
|
|
3101
|
+
previous: compliancePrevious,
|
|
3102
|
+
na: !hasComplianceFrameworks
|
|
3103
|
+
}
|
|
3104
|
+
].filter((row) => !hiddenSet.has(row.key));
|
|
3105
|
+
lines.push(``, `## Readiness Scores`, ``, `| Pillar | Score |${isRescan ? " Change |" : ""}`, `|--------|-------|${isRescan ? "--------|" : ""}`);
|
|
3106
|
+
if (scoreRows.length > 0) {
|
|
3107
|
+
for (const row of scoreRows) {
|
|
3108
|
+
lines.push(`| ${row.label} | ${row.na ? "N/A" : `${row.current ?? 0}%`} |${isRescan ? row.na ? " (n/a) |" : deltaStr(row.current, row.previous) + " |" : ""}`);
|
|
3109
|
+
}
|
|
3110
|
+
lines.push(``);
|
|
3111
|
+
} else {
|
|
3112
|
+
lines.push(`| _Hidden by local audit visibility settings_ | - |${isRescan ? " - |" : ""}`, ``);
|
|
3113
|
+
}
|
|
3114
|
+
lines.push(`**Constraints mapped:** ${m.complexity_factors?.nfr_count ?? 0} (${m.complexity_factors?.critical_nfr_count ?? 0} critical)`, `**Binding coverage:** ${result.bindingHealth.coverage_pct}%`);
|
|
3115
|
+
if (securityVisible && result.scaFindings && result.scaFindings.total > 0) {
|
|
3116
|
+
const sca = result.scaFindings;
|
|
3117
|
+
lines.push(``, `## Dependency Vulnerabilities (SCA)`, ``, `**${sca.total} known vulnerabilities** found (${sca.critical} critical, ${sca.high} high)`, ``);
|
|
3118
|
+
const scaEntries = fullReport && sca.all?.length ? sca.all : sca.top;
|
|
3119
|
+
for (const v of scaEntries) {
|
|
3120
|
+
const fix = v.fix ? ` \u2192 ${v.fix}` : "";
|
|
3121
|
+
lines.push(`- **[${v.severity.toUpperCase()}]** ${v.package}: ${v.id}${fix}`);
|
|
3122
|
+
}
|
|
3123
|
+
if (!fullReport && sca.total > sca.top.length) {
|
|
3124
|
+
lines.push(`- ...and ${sca.total - sca.top.length} more`);
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
const visibleFindings = result.gatedGapDetails.filter((g) => !hiddenSet.has(inferFindingDimension(g.category)));
|
|
3128
|
+
const totalFindings = visibleFindings.length;
|
|
3129
|
+
const criticalCount = visibleFindings.filter((g) => g.severity === "critical" || g.severity === "high").length;
|
|
3130
|
+
if (totalFindings > 0) {
|
|
3131
|
+
if (fullReport) {
|
|
3132
|
+
lines.push(``, `## Findings`, ``);
|
|
3133
|
+
for (const finding of visibleFindings) {
|
|
3134
|
+
lines.push(`**[${finding.severity.toUpperCase()}] ${finding.title}**`, `*Category: ${finding.category}*`, finding.description || "Address this finding to improve your readiness scores.", ``);
|
|
3135
|
+
}
|
|
3136
|
+
lines.push(`> Re-run \`code_audit\` after fixes to measure score improvements.`);
|
|
3137
|
+
} else {
|
|
3138
|
+
const topFinding = visibleFindings[0];
|
|
3139
|
+
lines.push(``, `## #1 Finding \u2014 Fix This Now`, ``, `**[${topFinding.severity.toUpperCase()}] ${topFinding.title}**`, `*Category: ${topFinding.category}*`, ``, topFinding.description || "Address this finding to improve your readiness scores.", ``, `> Fix this issue, then re-run \`code_audit\` to see your scores improve.`);
|
|
3140
|
+
const remaining = visibleFindings.slice(1);
|
|
3141
|
+
if (remaining.length > 0) {
|
|
3142
|
+
lines.push(``, `## ${remaining.length} More Finding${remaining.length > 1 ? "s" : ""} Detected`);
|
|
3143
|
+
const teaserLimit = Math.min(remaining.length, 5);
|
|
3144
|
+
for (let i = 0; i < teaserLimit; i++) {
|
|
3145
|
+
lines.push(`- [${remaining[i].severity.toUpperCase()}] ${remaining[i].title}`);
|
|
3146
|
+
}
|
|
3147
|
+
if (remaining.length > teaserLimit) {
|
|
3148
|
+
lines.push(`- ... and **${remaining.length - teaserLimit} more**`);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
lines.push(``, `---`, ``);
|
|
3154
|
+
if (!fullReport && totalFindings > 1) {
|
|
3155
|
+
lines.push(`### Unlock Full Analysis`, ``, `You fixed one \u2014 **${totalFindings - 1} findings** remain (${criticalCount} critical/high).`, `Upgrade to Cutline Pro for:`, `- Full details and remediation for every finding`, `- Prioritized RGR remediation plans your coding agent can execute`, `- Product-specific deep dive with feature-level constraint mapping`, `- Continuous score tracking with this audit as a baseline prior`, ``, `\u2192 Run \`cutline-mcp upgrade\` or visit **https://thecutline.ai/upgrade**`);
|
|
3156
|
+
} else if (totalFindings >= 1) {
|
|
3157
|
+
lines.push(`### Next Steps`, ``, `Fix the finding above, then re-scan to see your scores improve.`, `When ready, run a **deep dive** for product-specific analysis \u2014`, `these generic scores will serve as a prior for your product graph.`);
|
|
3158
|
+
} else {
|
|
3159
|
+
lines.push(`### Next Steps`, ``, `No critical findings detected. Run a **deep dive** for product-specific`, `analysis with feature coverage and the generic scores as a prior.`);
|
|
3160
|
+
}
|
|
3161
|
+
if (reportId) {
|
|
3162
|
+
lines.push(``, `---`, ``, `**View & share this report:** ${publicSiteUrl}/report/${reportId}`);
|
|
3163
|
+
}
|
|
3164
|
+
lines.push(``, `> Scores reflect constraint binding health \u2014 how well your codebase maps to`, `> engineering guardrails. They improve as you address findings and re-scan.`);
|
|
3165
|
+
return lines.join("\n");
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
export {
|
|
3169
|
+
scanCodebase,
|
|
3170
|
+
buildBlueprintConstraints,
|
|
3171
|
+
getActiveFrameworks,
|
|
3172
|
+
graphNeedsBlueprintSeeding,
|
|
3173
|
+
getSystemPrompt,
|
|
3174
|
+
buildUserPrompt,
|
|
3175
|
+
scanDependencies,
|
|
3176
|
+
formatScaContext,
|
|
3177
|
+
buildGenericGraphId,
|
|
3178
|
+
handleSecurityScan,
|
|
3179
|
+
formatAuditOutput
|
|
3180
|
+
};
|