brainblast 0.6.0 → 0.6.2

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.
@@ -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-RUVILE2Y.js");
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-PZKZYBKF.js");
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);
@@ -4,6 +4,7 @@ import {
4
4
  renderDiffText,
5
5
  riskScore
6
6
  } from "./chunk-SC6RNNDW.js";
7
+ import "./chunk-3RG5ZIWI.js";
7
8
  export {
8
9
  diffVersions,
9
10
  renderDiffMd,
@@ -0,0 +1,11 @@
1
+ import {
2
+ checkDrift,
3
+ renderDriftText,
4
+ seedPackages
5
+ } from "./chunk-EAKU3L7F.js";
6
+ import "./chunk-3RG5ZIWI.js";
7
+ export {
8
+ checkDrift,
9
+ renderDriftText,
10
+ seedPackages
11
+ };
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
- 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 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, 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, renderTest, renderTrustGraphMd, rentExemptMinimum, resolveRules, riskScore, runChecker, runIncrementalScan, saveProgramCache, startWatch, submitTelemetry, telemetryFilePath, testKinds, validatePack, validatePackManifest };
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,
@@ -6,6 +6,7 @@ import {
6
6
  diffVersions,
7
7
  queryOsv
8
8
  } from "./chunk-SC6RNNDW.js";
9
+ import "./chunk-3RG5ZIWI.js";
9
10
 
10
11
  // src/mcp.ts
11
12
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -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.0",
3
+ "version": "0.6.2",
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": [