brainblast 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2,11 +2,9 @@
2
2
  import {
3
3
  analyzeCosts,
4
4
  applyDiffToFile,
5
- audit,
6
5
  buildTrustGraph,
7
6
  cacheSize,
8
7
  defaultCachePath,
9
- getChangedRanges,
10
8
  initPack,
11
9
  isTelemetryEnabled,
12
10
  isValidSolanaAddress,
@@ -15,12 +13,16 @@ import {
15
13
  recordGraduationEvents,
16
14
  renderCostReportMd,
17
15
  renderTrustGraphMd,
18
- resolveRules,
19
16
  startWatch,
20
17
  submitTelemetry,
21
18
  telemetryFilePath,
22
19
  validatePack
23
- } from "./chunk-LHP6HFMS.js";
20
+ } from "./chunk-ZZ6LBZV5.js";
21
+ import {
22
+ audit,
23
+ getChangedRanges,
24
+ resolveRules
25
+ } from "./chunk-A56IF3UX.js";
24
26
 
25
27
  // src/cli.ts
26
28
  import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
@@ -107,6 +109,15 @@ function parsePackDirs(argv) {
107
109
  if (!value || value.startsWith("--")) return [];
108
110
  return value.split(",").map((s) => s.trim()).filter(Boolean);
109
111
  }
112
+ if (args[0] === "diff") {
113
+ await runDiff(args.slice(1));
114
+ process.exit(0);
115
+ }
116
+ if (args[0] === "mcp") {
117
+ const { startMcpServer } = await import("./mcp-RUVILE2Y.js");
118
+ await startMcpServer();
119
+ process.exit(0);
120
+ }
110
121
  if (args[0] === "trust-graph") {
111
122
  await runTrustGraph(args.slice(1));
112
123
  process.exit(0);
@@ -418,3 +429,87 @@ Warning: could not create branch/commit: ${e.message ?? e}`);
418
429
  }
419
430
  }
420
431
  }
432
+ function splitPkgVersion(arg) {
433
+ if (arg.startsWith("@")) {
434
+ const rest = arg.slice(1);
435
+ const at2 = rest.lastIndexOf("@");
436
+ if (at2 < 0) return [arg, ""];
437
+ return [`@${rest.slice(0, at2)}`, rest.slice(at2 + 1)];
438
+ }
439
+ const at = arg.lastIndexOf("@");
440
+ if (at <= 0) return [arg, ""];
441
+ return [arg.slice(0, at), arg.slice(at + 1)];
442
+ }
443
+ function guessEcosystem(name) {
444
+ if (name.includes("/") && !name.startsWith("@")) return "Go";
445
+ return "npm";
446
+ }
447
+ async function runDiff(argv) {
448
+ const flag = (n) => {
449
+ const i = argv.indexOf(`--${n}`);
450
+ return i >= 0 ? argv[i + 1] : void 0;
451
+ };
452
+ const ecoFlag = flag("ecosystem");
453
+ const fromFlag = flag("from");
454
+ const toFlag = flag("to");
455
+ const jsonOut = argv.includes("--json");
456
+ const skipValues = new Set([ecoFlag, fromFlag, toFlag].filter(Boolean));
457
+ const positional = argv.filter((a) => !a.startsWith("--") && !skipValues.has(a));
458
+ let pkgName;
459
+ let fromVersion;
460
+ let toVersion;
461
+ let ecosystem;
462
+ if (positional.length === 2 && positional[0].includes("@") && positional[1].includes("@")) {
463
+ const [n1, v1] = splitPkgVersion(positional[0]);
464
+ const [n2, v2] = splitPkgVersion(positional[1]);
465
+ if (n1 !== n2) {
466
+ console.error(`error: package names must match ('${n1}' vs '${n2}')`);
467
+ process.exit(2);
468
+ }
469
+ if (!v1 || !v2) {
470
+ console.error("error: could not parse versions from arguments");
471
+ process.exit(2);
472
+ }
473
+ pkgName = n1;
474
+ fromVersion = v1;
475
+ toVersion = v2;
476
+ ecosystem = ecoFlag ?? guessEcosystem(pkgName);
477
+ } else if (positional.length >= 1 && fromFlag && toFlag) {
478
+ pkgName = positional[0];
479
+ fromVersion = fromFlag;
480
+ toVersion = toFlag;
481
+ ecosystem = ecoFlag ?? guessEcosystem(pkgName);
482
+ } else {
483
+ console.error("usage: brainblast diff <pkg>@<from> <pkg>@<to> [--ecosystem <eco>]");
484
+ console.error(" or: brainblast diff <pkg> --from <v1> --to <v2> [--ecosystem <eco>]");
485
+ console.error(" e.g.: brainblast diff lodash@4.17.20 lodash@4.17.21");
486
+ process.exit(2);
487
+ }
488
+ const { diffVersions, renderDiffText, renderDiffMd, riskScore } = await import("./diff-PZKZYBKF.js");
489
+ let result;
490
+ try {
491
+ result = await diffVersions(ecosystem, pkgName, fromVersion, toVersion);
492
+ } catch (e) {
493
+ console.error(`brainblast diff: ${e.message ?? String(e)}`);
494
+ process.exit(1);
495
+ }
496
+ if (jsonOut) {
497
+ console.log(JSON.stringify(result, null, 2));
498
+ return;
499
+ }
500
+ console.log(renderDiffText(result));
501
+ const score = riskScore(result);
502
+ if (score > 0) {
503
+ console.error(`
504
+ Upgrade INCREASES risk (score: +${score}). Review introduced advisories before bumping.`);
505
+ process.exit(1);
506
+ } else if (score < 0) {
507
+ console.log(`
508
+ Upgrade DECREASES risk (score: ${score}). Upgrade recommended.`);
509
+ } else if (result.unchanged.length > 0) {
510
+ console.log(`
511
+ Risk profile unchanged (${result.unchanged.length} advisory${result.unchanged.length !== 1 ? "ies" : ""} persist in both versions).`);
512
+ } else {
513
+ console.log("\nNo known advisories for either version.");
514
+ }
515
+ }
@@ -0,0 +1,12 @@
1
+ import {
2
+ diffVersions,
3
+ renderDiffMd,
4
+ renderDiffText,
5
+ riskScore
6
+ } from "./chunk-SC6RNNDW.js";
7
+ export {
8
+ diffVersions,
9
+ renderDiffMd,
10
+ renderDiffText,
11
+ riskScore
12
+ };
package/dist/index.d.ts CHANGED
@@ -495,4 +495,29 @@ declare function isEntryExpired(entry: ProgramCacheEntry, ttlHoursOverride?: num
495
495
  */
496
496
  declare function cacheSize(cache: ProgramCache, ttlHoursOverride?: number): number;
497
497
 
498
- 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 GraduationEvent, type OnChainProgram, 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, fileChanged, findCandidates, findConfigCandidates, generateTestForResult, getCacheEntry, getCacheEntryMeta, getChangedRanges, getRepoHash, getUserHash, getWorkingTreeChanges, initPack, isEntryExpired, isTelemetryEnabled, isValidSolanaAddress, lamportsToSol, loadDirectory, loadPack, loadPacksFromDir, loadProgramCache, loadRules, parseDiff, putCacheEntry, rangeChanged, recordGraduationEvents, renderCostReportMd, renderTest, renderTrustGraphMd, rentExemptMinimum, resolveRules, runChecker, runIncrementalScan, saveProgramCache, startWatch, submitTelemetry, telemetryFilePath, testKinds, validatePack, validatePackManifest };
498
+ interface OsvAdvisory {
499
+ id: string;
500
+ severity: "critical" | "high" | "medium" | "low";
501
+ summary: string;
502
+ url: string;
503
+ }
504
+ declare function queryOsv(ecosystem: string, name: string, version: string): Promise<OsvAdvisory[]>;
505
+
506
+ interface DiffResult {
507
+ package: string;
508
+ ecosystem: string;
509
+ fromVersion: string;
510
+ toVersion: string;
511
+ /** Advisories present in fromVersion that are gone in toVersion. */
512
+ resolved: OsvAdvisory[];
513
+ /** Advisories that appear in toVersion but were not in fromVersion. */
514
+ introduced: OsvAdvisory[];
515
+ /** Advisories present in both versions. */
516
+ unchanged: OsvAdvisory[];
517
+ }
518
+ declare function diffVersions(ecosystem: string, packageName: string, fromVersion: string, toVersion: string): Promise<DiffResult>;
519
+ declare function riskScore(result: DiffResult): number;
520
+ declare function renderDiffText(result: DiffResult): string;
521
+ declare function renderDiffMd(result: DiffResult): string;
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 };
package/dist/index.js CHANGED
@@ -1,56 +1,65 @@
1
1
  import {
2
2
  DEFAULT_REGISTRY_URL,
3
3
  DEFAULT_TTL_HOURS,
4
- PACK_MANIFEST_FILE,
5
4
  analyzeCosts,
6
5
  applyDiffToFile,
7
- audit,
8
- auditWithRule,
9
6
  base58Decode,
10
7
  base58Encode,
11
8
  buildTrustGraph,
12
9
  cacheSize,
13
- checkerKinds,
14
10
  defaultCachePath,
15
- fileChanged,
16
- findCandidates,
17
- findConfigCandidates,
18
11
  getCacheEntry,
19
12
  getCacheEntryMeta,
20
- getChangedRanges,
21
13
  getRepoHash,
22
14
  getUserHash,
23
- getWorkingTreeChanges,
24
15
  initPack,
25
16
  isEntryExpired,
26
17
  isTelemetryEnabled,
27
18
  isValidSolanaAddress,
28
19
  lamportsToSol,
29
20
  loadDirectory,
30
- loadPack,
31
- loadPacksFromDir,
32
21
  loadProgramCache,
33
- loadRules,
34
22
  parseDiff,
35
23
  putCacheEntry,
36
- rangeChanged,
37
24
  recordGraduationEvents,
38
25
  renderCostReportMd,
39
- renderTest,
40
26
  renderTrustGraphMd,
41
27
  rentExemptMinimum,
42
- resolveRules,
43
- rules,
44
- runChecker,
45
28
  runIncrementalScan,
46
29
  saveProgramCache,
47
30
  startWatch,
48
31
  submitTelemetry,
49
32
  telemetryFilePath,
33
+ validatePack
34
+ } from "./chunk-ZZ6LBZV5.js";
35
+ import {
36
+ PACK_MANIFEST_FILE,
37
+ audit,
38
+ auditWithRule,
39
+ checkerKinds,
40
+ fileChanged,
41
+ findCandidates,
42
+ findConfigCandidates,
43
+ getChangedRanges,
44
+ getWorkingTreeChanges,
45
+ loadPack,
46
+ loadPacksFromDir,
47
+ loadRules,
48
+ rangeChanged,
49
+ renderTest,
50
+ resolveRules,
51
+ rules,
52
+ runChecker,
50
53
  testKinds,
51
- validatePack,
52
54
  validatePackManifest
53
- } from "./chunk-LHP6HFMS.js";
55
+ } from "./chunk-A56IF3UX.js";
56
+ import {
57
+ diffVersions,
58
+ queryOsv,
59
+ renderDiffMd,
60
+ renderDiffText,
61
+ riskScore
62
+ } from "./chunk-SC6RNNDW.js";
54
63
 
55
64
  // src/generate.ts
56
65
  import { writeFileSync, mkdirSync } from "fs";
@@ -80,6 +89,7 @@ export {
80
89
  cacheSize,
81
90
  checkerKinds,
82
91
  defaultCachePath,
92
+ diffVersions,
83
93
  fileChanged,
84
94
  findCandidates,
85
95
  findConfigCandidates,
@@ -102,13 +112,17 @@ export {
102
112
  loadRules,
103
113
  parseDiff,
104
114
  putCacheEntry,
115
+ queryOsv,
105
116
  rangeChanged,
106
117
  recordGraduationEvents,
107
118
  renderCostReportMd,
119
+ renderDiffMd,
120
+ renderDiffText,
108
121
  renderTest,
109
122
  renderTrustGraphMd,
110
123
  rentExemptMinimum,
111
124
  resolveRules,
125
+ riskScore,
112
126
  runChecker,
113
127
  runIncrementalScan,
114
128
  saveProgramCache,
@@ -0,0 +1,172 @@
1
+ import {
2
+ audit,
3
+ resolveRules
4
+ } from "./chunk-A56IF3UX.js";
5
+ import {
6
+ diffVersions,
7
+ queryOsv
8
+ } from "./chunk-SC6RNNDW.js";
9
+
10
+ // src/mcp.ts
11
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import {
14
+ CallToolRequestSchema,
15
+ ListToolsRequestSchema
16
+ } from "@modelcontextprotocol/sdk/types.js";
17
+ var VERSION = "0.6.0";
18
+ var TOOLS = [
19
+ {
20
+ name: "brainblast_audit",
21
+ description: "Run the brainblast deterministic static auditor on a local project directory. Returns per-rule check results (pass / fail / cant_tell) and severity totals. Catches catastrophic AI-integration traps: Stripe raw-body signing, JWT audience/issuer, SPL token-program identity, command-injection sinks, committed secrets, and more.",
22
+ inputSchema: {
23
+ type: "object",
24
+ properties: {
25
+ dir: {
26
+ type: "string",
27
+ description: "Absolute path to the project directory to audit. Defaults to the current working directory if omitted."
28
+ },
29
+ packs: {
30
+ type: "array",
31
+ items: { type: "string" },
32
+ description: "Optional list of absolute paths to pluggable rule-pack directories (each must contain a brainblast-pack.yaml manifest)."
33
+ }
34
+ },
35
+ required: []
36
+ }
37
+ },
38
+ {
39
+ name: "brainblast_diff",
40
+ description: "Compare the OSV security-advisory risk profile between two versions of a package. Shows which advisories were introduced by the upgrade, which were resolved, and which are present in both versions. Returns a risk score: positive means the upgrade increases risk, negative means it decreases risk.",
41
+ inputSchema: {
42
+ type: "object",
43
+ properties: {
44
+ ecosystem: {
45
+ type: "string",
46
+ description: "OSV ecosystem name \u2014 e.g. npm, PyPI, crates.io, Go, RubyGems, Packagist, Maven, NuGet."
47
+ },
48
+ package: { type: "string", description: "Package / module name." },
49
+ from_version: { type: "string", description: "The version currently in use (upgrading from)." },
50
+ to_version: { type: "string", description: "The candidate version (upgrading to)." }
51
+ },
52
+ required: ["ecosystem", "package", "from_version", "to_version"]
53
+ }
54
+ },
55
+ {
56
+ name: "brainblast_osv_check",
57
+ description: "Query OSV.dev for all known security advisories affecting a specific package at a specific version. Returns severity (critical/high/medium/low), advisory ID, human-readable summary, and advisory URL. An empty array means no advisories found \u2014 not that the package is safe.",
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: {
61
+ ecosystem: {
62
+ type: "string",
63
+ description: "OSV ecosystem \u2014 e.g. npm, PyPI, crates.io, Go, RubyGems."
64
+ },
65
+ package: { type: "string", description: "Package name." },
66
+ version: { type: "string", description: "Exact version string to check." }
67
+ },
68
+ required: ["ecosystem", "package", "version"]
69
+ }
70
+ }
71
+ ];
72
+ async function startMcpServer() {
73
+ const server = new Server(
74
+ { name: "brainblast", version: VERSION },
75
+ { capabilities: { tools: {} } }
76
+ );
77
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
78
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
79
+ const { name, arguments: args = {} } = req.params;
80
+ if (name === "brainblast_audit") {
81
+ const { dir = process.cwd(), packs = [] } = args;
82
+ try {
83
+ const rules = resolveRules(dir, packs);
84
+ const { checks, report } = audit(dir, rules);
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text",
89
+ text: JSON.stringify(
90
+ {
91
+ dir,
92
+ rulesLoaded: rules.length,
93
+ checks,
94
+ riskTotals: report.riskTotals
95
+ },
96
+ null,
97
+ 2
98
+ )
99
+ }
100
+ ]
101
+ };
102
+ } catch (e) {
103
+ return {
104
+ content: [
105
+ {
106
+ type: "text",
107
+ text: `Error running audit: ${e.message ?? String(e)}`
108
+ }
109
+ ],
110
+ isError: true
111
+ };
112
+ }
113
+ }
114
+ if (name === "brainblast_osv_check") {
115
+ const { ecosystem, package: pkg, version } = args;
116
+ try {
117
+ const advisories = await queryOsv(ecosystem, pkg, version);
118
+ return {
119
+ content: [
120
+ {
121
+ type: "text",
122
+ text: JSON.stringify(advisories, null, 2)
123
+ }
124
+ ]
125
+ };
126
+ } catch (e) {
127
+ return {
128
+ content: [
129
+ {
130
+ type: "text",
131
+ text: `OSV query failed: ${e.message ?? String(e)}`
132
+ }
133
+ ],
134
+ isError: true
135
+ };
136
+ }
137
+ }
138
+ if (name === "brainblast_diff") {
139
+ const { ecosystem, package: pkg, from_version, to_version } = args;
140
+ try {
141
+ const result = await diffVersions(ecosystem, pkg, from_version, to_version);
142
+ return {
143
+ content: [
144
+ {
145
+ type: "text",
146
+ text: JSON.stringify(result, null, 2)
147
+ }
148
+ ]
149
+ };
150
+ } catch (e) {
151
+ return {
152
+ content: [
153
+ {
154
+ type: "text",
155
+ text: `Diff failed: ${e.message ?? String(e)}`
156
+ }
157
+ ],
158
+ isError: true
159
+ };
160
+ }
161
+ }
162
+ return {
163
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
164
+ isError: true
165
+ };
166
+ });
167
+ const transport = new StdioServerTransport();
168
+ await server.connect(transport);
169
+ }
170
+ export {
171
+ startMcpServer
172
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainblast",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
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": [
@@ -63,6 +63,7 @@
63
63
  "typecheck": "tsc --noEmit -p tsconfig.json"
64
64
  },
65
65
  "dependencies": {
66
+ "@modelcontextprotocol/sdk": "^1.29.0",
66
67
  "tree-sitter": "^0.22.4",
67
68
  "tree-sitter-rust": "^0.24.0",
68
69
  "ts-morph": "^23",