brainblast 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-EAKU3L7F.js +183 -0
- package/dist/cli.js +27 -2
- package/dist/{diff-PZKZYBKF.js → diff-KIR4PCBC.js} +1 -0
- package/dist/drift-JQ7DDZIF.js +11 -0
- package/dist/index.d.ts +36 -1
- package/dist/index.js +9 -0
- package/dist/{mcp-RUVILE2Y.js → mcp-AFYJQ7K6.js} +1 -0
- package/dist/rules/jsonwebtoken-algorithm-pinned.yaml +36 -0
- package/dist/rules/open-redirect.yaml +32 -0
- package/dist/rules/prisma-raw-injection.yaml +35 -0
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
__require
|
|
10
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-3RG5ZIWI.js";
|
|
4
|
+
|
|
5
|
+
// src/drift.ts
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
var BASELINE_PATH = (dir) => join(dir, ".agent-research", "drift-baseline.json");
|
|
9
|
+
function pkgKey(pkg) {
|
|
10
|
+
return `${pkg.ecosystem}:${pkg.name}@${pkg.version}`;
|
|
11
|
+
}
|
|
12
|
+
function loadBaseline(dir) {
|
|
13
|
+
const path = BASELINE_PATH(dir);
|
|
14
|
+
if (!existsSync(path)) return null;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function saveBaseline(dir, baseline) {
|
|
22
|
+
const outDir = join(dir, ".agent-research");
|
|
23
|
+
mkdirSync(outDir, { recursive: true });
|
|
24
|
+
writeFileSync(BASELINE_PATH(dir), JSON.stringify(baseline, null, 2));
|
|
25
|
+
}
|
|
26
|
+
async function queryOsvBatch(pkgs) {
|
|
27
|
+
const results = /* @__PURE__ */ new Map();
|
|
28
|
+
if (pkgs.length === 0) return results;
|
|
29
|
+
for (let i = 0; i < pkgs.length; i += 1e3) {
|
|
30
|
+
const batch = pkgs.slice(i, i + 1e3);
|
|
31
|
+
const body = JSON.stringify({
|
|
32
|
+
queries: batch.map((p) => ({
|
|
33
|
+
version: p.version,
|
|
34
|
+
package: { name: p.name, ecosystem: p.ecosystem }
|
|
35
|
+
}))
|
|
36
|
+
});
|
|
37
|
+
let res;
|
|
38
|
+
try {
|
|
39
|
+
res = await fetch("https://api.osv.dev/v1/querybatch", {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: { "Content-Type": "application/json" },
|
|
42
|
+
body,
|
|
43
|
+
signal: AbortSignal.timeout(3e4)
|
|
44
|
+
});
|
|
45
|
+
} catch (e) {
|
|
46
|
+
throw new Error(`OSV batch request failed: ${e.message ?? String(e)}`);
|
|
47
|
+
}
|
|
48
|
+
if (!res.ok) throw new Error(`OSV batch API error: ${res.status} ${res.statusText}`);
|
|
49
|
+
const data = await res.json();
|
|
50
|
+
for (let j = 0; j < batch.length; j++) {
|
|
51
|
+
const pkg = batch[j];
|
|
52
|
+
const vulns = data.results[j]?.vulns ?? [];
|
|
53
|
+
const advisories = vulns.map((v) => {
|
|
54
|
+
const id = v["id"];
|
|
55
|
+
const severities = v["severity"] ?? [];
|
|
56
|
+
let severity = "high";
|
|
57
|
+
for (const sev of severities) {
|
|
58
|
+
if (sev.type === "CVSS_V3") {
|
|
59
|
+
const score = parseFloat(sev.score);
|
|
60
|
+
if (!isNaN(score)) {
|
|
61
|
+
severity = score >= 9 ? "critical" : score >= 7 ? "high" : score >= 4 ? "medium" : "low";
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const dbSpec = v["database_specific"] ?? {};
|
|
67
|
+
if (severity === "high") {
|
|
68
|
+
const ghsa = (dbSpec["severity"] ?? "").toUpperCase();
|
|
69
|
+
severity = { CRITICAL: "critical", HIGH: "high", MODERATE: "medium", LOW: "low" }[ghsa] ?? "high";
|
|
70
|
+
}
|
|
71
|
+
const rawSummary = v["summary"] ?? "";
|
|
72
|
+
const rawDetails = v["details"] ?? "";
|
|
73
|
+
return { id, severity, summary: rawSummary || rawDetails.slice(0, 200), url: `https://osv.dev/vulnerability/${id}` };
|
|
74
|
+
});
|
|
75
|
+
results.set(pkgKey(pkg), advisories);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return results;
|
|
79
|
+
}
|
|
80
|
+
function seedPackages(dir) {
|
|
81
|
+
const { execFileSync } = __require("child_process");
|
|
82
|
+
const scriptPaths = [
|
|
83
|
+
join(dir, "scripts", "seed-inventory.sh"),
|
|
84
|
+
join(__dirname, "..", "..", "scripts", "seed-inventory.sh")
|
|
85
|
+
];
|
|
86
|
+
for (const script of scriptPaths) {
|
|
87
|
+
if (existsSync(script)) {
|
|
88
|
+
try {
|
|
89
|
+
const out = execFileSync("sh", [script, dir], { encoding: "utf8", timeout: 3e4 });
|
|
90
|
+
return JSON.parse(out);
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
async function checkDrift(dir, opts = {}) {
|
|
98
|
+
const pkgs = opts.packages ?? seedPackages(dir);
|
|
99
|
+
const baseline = loadBaseline(dir);
|
|
100
|
+
const currentMap = await queryOsvBatch(pkgs);
|
|
101
|
+
const currentIds = {};
|
|
102
|
+
for (const pkg of pkgs) {
|
|
103
|
+
const key = pkgKey(pkg);
|
|
104
|
+
currentIds[key] = (currentMap.get(key) ?? []).map((a) => a.id);
|
|
105
|
+
}
|
|
106
|
+
const newAdvisories = [];
|
|
107
|
+
const resolvedAdvisories = [];
|
|
108
|
+
if (baseline) {
|
|
109
|
+
for (const pkg of pkgs) {
|
|
110
|
+
const key = pkgKey(pkg);
|
|
111
|
+
const prev = new Set(baseline.advisoryIds[key] ?? []);
|
|
112
|
+
const curr = currentMap.get(key) ?? [];
|
|
113
|
+
for (const adv of curr) {
|
|
114
|
+
if (!prev.has(adv.id)) {
|
|
115
|
+
newAdvisories.push({ ...adv, package: pkg.name, ecosystem: pkg.ecosystem, version: pkg.version });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const currIds = new Set(curr.map((a) => a.id));
|
|
119
|
+
for (const prevId of prev) {
|
|
120
|
+
if (!currIds.has(prevId)) {
|
|
121
|
+
resolvedAdvisories.push({
|
|
122
|
+
id: prevId,
|
|
123
|
+
severity: "low",
|
|
124
|
+
summary: "Advisory no longer reported by OSV",
|
|
125
|
+
url: `https://osv.dev/vulnerability/${prevId}`,
|
|
126
|
+
package: pkg.name,
|
|
127
|
+
ecosystem: pkg.ecosystem,
|
|
128
|
+
version: pkg.version
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (opts.updateBaseline || !baseline) {
|
|
135
|
+
saveBaseline(dir, {
|
|
136
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
137
|
+
packages: pkgs.length,
|
|
138
|
+
advisoryIds: currentIds
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
newAdvisories,
|
|
143
|
+
resolvedAdvisories,
|
|
144
|
+
baselineExists: !!baseline,
|
|
145
|
+
baselineDate: baseline?.createdAt ?? null,
|
|
146
|
+
packagesChecked: pkgs.length
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function renderDriftText(result) {
|
|
150
|
+
const lines = [];
|
|
151
|
+
if (!result.baselineExists) {
|
|
152
|
+
lines.push(`brainblast drift: baseline created \u2014 ${result.packagesChecked} packages indexed.`);
|
|
153
|
+
lines.push("Re-run without --update-baseline to detect new advisories.");
|
|
154
|
+
return lines.join("\n");
|
|
155
|
+
}
|
|
156
|
+
lines.push(`brainblast drift: checked ${result.packagesChecked} packages against baseline from ${result.baselineDate?.split("T")[0] ?? "unknown"}`);
|
|
157
|
+
if (result.newAdvisories.length === 0 && result.resolvedAdvisories.length === 0) {
|
|
158
|
+
lines.push(" No new advisories. Dependency risk profile unchanged.");
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
}
|
|
161
|
+
if (result.newAdvisories.length > 0) {
|
|
162
|
+
lines.push(`
|
|
163
|
+
NEW (${result.newAdvisories.length}):`);
|
|
164
|
+
for (const a of result.newAdvisories) {
|
|
165
|
+
lines.push(` [${a.severity.toUpperCase()}] ${a.package}@${a.version} \u2014 ${a.id}: ${a.summary}`);
|
|
166
|
+
lines.push(` ${a.url}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (result.resolvedAdvisories.length > 0) {
|
|
170
|
+
lines.push(`
|
|
171
|
+
RESOLVED (${result.resolvedAdvisories.length}):`);
|
|
172
|
+
for (const a of result.resolvedAdvisories) {
|
|
173
|
+
lines.push(` ${a.package}@${a.version} \u2014 ${a.id}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return lines.join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
seedPackages,
|
|
181
|
+
checkDrift,
|
|
182
|
+
renderDriftText
|
|
183
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
getChangedRanges,
|
|
24
24
|
resolveRules
|
|
25
25
|
} from "./chunk-A56IF3UX.js";
|
|
26
|
+
import "./chunk-3RG5ZIWI.js";
|
|
26
27
|
|
|
27
28
|
// src/cli.ts
|
|
28
29
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -113,8 +114,12 @@ if (args[0] === "diff") {
|
|
|
113
114
|
await runDiff(args.slice(1));
|
|
114
115
|
process.exit(0);
|
|
115
116
|
}
|
|
117
|
+
if (args[0] === "drift") {
|
|
118
|
+
await runDrift(args.slice(1));
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
116
121
|
if (args[0] === "mcp") {
|
|
117
|
-
const { startMcpServer } = await import("./mcp-
|
|
122
|
+
const { startMcpServer } = await import("./mcp-AFYJQ7K6.js");
|
|
118
123
|
await startMcpServer();
|
|
119
124
|
process.exit(0);
|
|
120
125
|
}
|
|
@@ -429,6 +434,26 @@ Warning: could not create branch/commit: ${e.message ?? e}`);
|
|
|
429
434
|
}
|
|
430
435
|
}
|
|
431
436
|
}
|
|
437
|
+
async function runDrift(argv) {
|
|
438
|
+
const updateBaseline = argv.includes("--update-baseline");
|
|
439
|
+
const jsonOut = argv.includes("--json");
|
|
440
|
+
const targetDir2 = argv.find((a) => !a.startsWith("--")) ?? process.cwd();
|
|
441
|
+
const { checkDrift, renderDriftText } = await import("./drift-JQ7DDZIF.js");
|
|
442
|
+
let result;
|
|
443
|
+
try {
|
|
444
|
+
result = await checkDrift(targetDir2, { updateBaseline });
|
|
445
|
+
} catch (e) {
|
|
446
|
+
console.error(`brainblast drift: ${e.message ?? String(e)}`);
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
if (jsonOut) {
|
|
450
|
+
console.log(JSON.stringify(result, null, 2));
|
|
451
|
+
if (result.newAdvisories.length > 0) process.exit(1);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
console.log(renderDriftText(result));
|
|
455
|
+
if (result.newAdvisories.length > 0) process.exit(1);
|
|
456
|
+
}
|
|
432
457
|
function splitPkgVersion(arg) {
|
|
433
458
|
if (arg.startsWith("@")) {
|
|
434
459
|
const rest = arg.slice(1);
|
|
@@ -485,7 +510,7 @@ async function runDiff(argv) {
|
|
|
485
510
|
console.error(" e.g.: brainblast diff lodash@4.17.20 lodash@4.17.21");
|
|
486
511
|
process.exit(2);
|
|
487
512
|
}
|
|
488
|
-
const { diffVersions, renderDiffText, renderDiffMd, riskScore } = await import("./diff-
|
|
513
|
+
const { diffVersions, renderDiffText, renderDiffMd, riskScore } = await import("./diff-KIR4PCBC.js");
|
|
489
514
|
let result;
|
|
490
515
|
try {
|
|
491
516
|
result = await diffVersions(ecosystem, pkgName, fromVersion, toVersion);
|
package/dist/index.d.ts
CHANGED
|
@@ -520,4 +520,39 @@ declare function riskScore(result: DiffResult): number;
|
|
|
520
520
|
declare function renderDiffText(result: DiffResult): string;
|
|
521
521
|
declare function renderDiffMd(result: DiffResult): string;
|
|
522
522
|
|
|
523
|
-
|
|
523
|
+
interface DriftPackage {
|
|
524
|
+
name: string;
|
|
525
|
+
version: string;
|
|
526
|
+
ecosystem: string;
|
|
527
|
+
source: string;
|
|
528
|
+
}
|
|
529
|
+
interface DriftAdvisory extends OsvAdvisory {
|
|
530
|
+
package: string;
|
|
531
|
+
ecosystem: string;
|
|
532
|
+
version: string;
|
|
533
|
+
}
|
|
534
|
+
interface DriftBaseline {
|
|
535
|
+
createdAt: string;
|
|
536
|
+
packages: number;
|
|
537
|
+
/** Map from "ecosystem:name@version" to array of advisory IDs. */
|
|
538
|
+
advisoryIds: Record<string, string[]>;
|
|
539
|
+
}
|
|
540
|
+
interface DriftResult {
|
|
541
|
+
newAdvisories: DriftAdvisory[];
|
|
542
|
+
resolvedAdvisories: DriftAdvisory[];
|
|
543
|
+
baselineExists: boolean;
|
|
544
|
+
baselineDate: string | null;
|
|
545
|
+
packagesChecked: number;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Seed packages from lockfiles in the project directory.
|
|
549
|
+
* Shells out to scripts/seed-inventory.sh for parity with the skill.
|
|
550
|
+
*/
|
|
551
|
+
declare function seedPackages(dir: string): DriftPackage[];
|
|
552
|
+
declare function checkDrift(dir: string, opts?: {
|
|
553
|
+
updateBaseline?: boolean;
|
|
554
|
+
packages?: DriftPackage[];
|
|
555
|
+
}): Promise<DriftResult>;
|
|
556
|
+
declare function renderDriftText(result: DriftResult): string;
|
|
557
|
+
|
|
558
|
+
export { type AccountFlow, type AuditRef, type BuildOpts, type Candidate, type ChangedRanges, type CheckOutcome, type CheckResult, type CheckResultKind, type Checker, type ConfigCandidate, type ConfigChecker, type CostReport, DEFAULT_REGISTRY_URL, DEFAULT_TTL_HOURS, type DiffResult, type DriftAdvisory, type DriftBaseline, type DriftPackage, type DriftResult, type GraduationEvent, type OnChainProgram, type OsvAdvisory, PACK_MANIFEST_FILE, type PackInitOptions, type PackManifest, type PackRuleValidation, type PackValidateResult, type ParityNote, type ParsedDiff, type PriorityFeePosture, type ProgramCache, type ProgramCacheEntry, type Recoverability, type Rule, type RustAccountField, type RustCandidate, type RustChecker, type Severity, type TelemetrySubmitResult, type TrustGraph, type UpgradeAuthority, type UpgradeAuthorityKind, type UpgradeAuthoritySource, type VerifiedBuildState, type WatchEvent, type WatchOptions, analyzeCosts, applyDiffToFile, audit, auditWithRule, base58Decode, base58Encode, buildTrustGraph, rules as bundledRules, cacheSize, checkDrift, checkerKinds, defaultCachePath, diffVersions, fileChanged, findCandidates, findConfigCandidates, generateTestForResult, getCacheEntry, getCacheEntryMeta, getChangedRanges, getRepoHash, getUserHash, getWorkingTreeChanges, initPack, isEntryExpired, isTelemetryEnabled, isValidSolanaAddress, lamportsToSol, loadDirectory, loadPack, loadPacksFromDir, loadProgramCache, loadRules, parseDiff, putCacheEntry, queryOsv, rangeChanged, recordGraduationEvents, renderCostReportMd, renderDiffMd, renderDiffText, renderDriftText, renderTest, renderTrustGraphMd, rentExemptMinimum, resolveRules, riskScore, runChecker, runIncrementalScan, saveProgramCache, seedPackages, startWatch, submitTelemetry, telemetryFilePath, testKinds, validatePack, validatePackManifest };
|
package/dist/index.js
CHANGED
|
@@ -60,6 +60,12 @@ import {
|
|
|
60
60
|
renderDiffText,
|
|
61
61
|
riskScore
|
|
62
62
|
} from "./chunk-SC6RNNDW.js";
|
|
63
|
+
import {
|
|
64
|
+
checkDrift,
|
|
65
|
+
renderDriftText,
|
|
66
|
+
seedPackages
|
|
67
|
+
} from "./chunk-EAKU3L7F.js";
|
|
68
|
+
import "./chunk-3RG5ZIWI.js";
|
|
63
69
|
|
|
64
70
|
// src/generate.ts
|
|
65
71
|
import { writeFileSync, mkdirSync } from "fs";
|
|
@@ -87,6 +93,7 @@ export {
|
|
|
87
93
|
buildTrustGraph,
|
|
88
94
|
rules as bundledRules,
|
|
89
95
|
cacheSize,
|
|
96
|
+
checkDrift,
|
|
90
97
|
checkerKinds,
|
|
91
98
|
defaultCachePath,
|
|
92
99
|
diffVersions,
|
|
@@ -118,6 +125,7 @@ export {
|
|
|
118
125
|
renderCostReportMd,
|
|
119
126
|
renderDiffMd,
|
|
120
127
|
renderDiffText,
|
|
128
|
+
renderDriftText,
|
|
121
129
|
renderTest,
|
|
122
130
|
renderTrustGraphMd,
|
|
123
131
|
rentExemptMinimum,
|
|
@@ -126,6 +134,7 @@ export {
|
|
|
126
134
|
runChecker,
|
|
127
135
|
runIncrementalScan,
|
|
128
136
|
saveProgramCache,
|
|
137
|
+
seedPackages,
|
|
129
138
|
startWatch,
|
|
130
139
|
submitTelemetry,
|
|
131
140
|
telemetryFilePath,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Pure-data rule (facts). Binds to vetted templates by `check.kind`/`test.kind`.
|
|
2
|
+
id: jsonwebtoken-algorithm-pinned
|
|
3
|
+
severity: critical
|
|
4
|
+
title: jsonwebtoken verify() must pin the accepted algorithm(s)
|
|
5
|
+
component:
|
|
6
|
+
name: jsonwebtoken
|
|
7
|
+
type: Auth
|
|
8
|
+
version: unversioned
|
|
9
|
+
sourceUrl: https://github.com/auth0/node-jsonwebtoken/blob/master/README.md#jwtverifytoken-secretorpublickey-options-callback
|
|
10
|
+
detect:
|
|
11
|
+
lang: typescript
|
|
12
|
+
modules: [jsonwebtoken]
|
|
13
|
+
# Functions that import jsonwebtoken and contain verification logic.
|
|
14
|
+
nameRegex: "auth|jwt|token|verify|middleware"
|
|
15
|
+
triggerCalls: [verify]
|
|
16
|
+
requiresImport: true
|
|
17
|
+
check:
|
|
18
|
+
kind: required-call-with-options
|
|
19
|
+
params:
|
|
20
|
+
verifyCalls: [verify]
|
|
21
|
+
decodeCalls: [decode]
|
|
22
|
+
requiredProps:
|
|
23
|
+
- [algorithms]
|
|
24
|
+
passDetail: >-
|
|
25
|
+
jwt.verify() pins the accepted algorithm(s) via the 'algorithms' option —
|
|
26
|
+
algorithm-confusion attacks are blocked.
|
|
27
|
+
missingPropsDetail: >-
|
|
28
|
+
jwt.verify() is called without an 'algorithms' option. An attacker who controls
|
|
29
|
+
the token header can switch to 'none' (no signature) or to an asymmetric algorithm
|
|
30
|
+
using the public key as the HMAC secret, bypassing signature verification entirely.
|
|
31
|
+
Always pass algorithms: ['HS256'] (or whichever algorithm you use).
|
|
32
|
+
decodeOnlyDetail: >-
|
|
33
|
+
Token is decoded with jwt.decode() (no signature verification). Any token —
|
|
34
|
+
forged or expired — is accepted. Use jwt.verify() with a pinned algorithms list.
|
|
35
|
+
test:
|
|
36
|
+
kind: none
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Pure-data rule (facts). Binds to vetted templates by `check.kind`/`test.kind`.
|
|
2
|
+
id: open-redirect
|
|
3
|
+
severity: high
|
|
4
|
+
title: Untrusted request input used as a redirect destination
|
|
5
|
+
component:
|
|
6
|
+
name: Node.js HTTP (Express / Next.js)
|
|
7
|
+
type: API
|
|
8
|
+
version: unversioned
|
|
9
|
+
sourceUrl: https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
|
|
10
|
+
detect:
|
|
11
|
+
lang: typescript
|
|
12
|
+
modules: []
|
|
13
|
+
# Trigger on any function that calls res.redirect / router.redirect / NextResponse.redirect.
|
|
14
|
+
nameRegex: "(?!)"
|
|
15
|
+
triggerCalls:
|
|
16
|
+
- redirect
|
|
17
|
+
- setHeader
|
|
18
|
+
requiresImport: false
|
|
19
|
+
check:
|
|
20
|
+
kind: taint-to-sink
|
|
21
|
+
params:
|
|
22
|
+
sources:
|
|
23
|
+
- name: request-input
|
|
24
|
+
# req.query / req.params are the most common entry points for open-redirect attacks.
|
|
25
|
+
# req.body included for completeness (e.g. POST-based login redirects).
|
|
26
|
+
pattern: "\\b(req|request)\\.(query|params|body|headers)\\b"
|
|
27
|
+
sinkCalls:
|
|
28
|
+
- redirect
|
|
29
|
+
- setHeader
|
|
30
|
+
maxHops: 2
|
|
31
|
+
test:
|
|
32
|
+
kind: none
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Pure-data rule (facts). Binds to vetted templates by `check.kind`/`test.kind`.
|
|
2
|
+
id: prisma-raw-injection
|
|
3
|
+
severity: critical
|
|
4
|
+
title: Untrusted request input flows into a Prisma raw query
|
|
5
|
+
component:
|
|
6
|
+
name: Prisma ORM
|
|
7
|
+
type: SDK
|
|
8
|
+
version: unversioned
|
|
9
|
+
sourceUrl: https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries#sql-injection-prevention
|
|
10
|
+
detect:
|
|
11
|
+
lang: typescript
|
|
12
|
+
modules: ["@prisma/client", "prisma"]
|
|
13
|
+
# Never matches by name alone — only the sink triggerCalls below select candidates.
|
|
14
|
+
nameRegex: "(?!)"
|
|
15
|
+
triggerCalls:
|
|
16
|
+
- $queryRaw
|
|
17
|
+
- $executeRaw
|
|
18
|
+
- $queryRawUnsafe
|
|
19
|
+
- $executeRawUnsafe
|
|
20
|
+
requiresImport: false
|
|
21
|
+
check:
|
|
22
|
+
kind: taint-to-sink
|
|
23
|
+
params:
|
|
24
|
+
sources:
|
|
25
|
+
- name: request-input
|
|
26
|
+
# req.body / req.query / req.params / req.headers — canonical untrusted HTTP input.
|
|
27
|
+
pattern: "\\b(req|request)\\.(body|query|params|headers)\\b"
|
|
28
|
+
sinkCalls:
|
|
29
|
+
- $queryRaw
|
|
30
|
+
- $executeRaw
|
|
31
|
+
- $queryRawUnsafe
|
|
32
|
+
- $executeRawUnsafe
|
|
33
|
+
maxHops: 2
|
|
34
|
+
test:
|
|
35
|
+
kind: none
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainblast",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deterministic auditor for catastrophic AI-integration bugs: scan a repo, find the silent money/auth traps, and generate the behavioral test that proves they're fixed.",
|
|
6
6
|
"keywords": [
|