brainblast 0.7.0 → 0.7.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.
@@ -7,7 +7,7 @@ import {
7
7
  loadPack,
8
8
  resolveRules,
9
9
  walk
10
- } from "./chunk-CRYFCQYM.js";
10
+ } from "./chunk-IY52XKWL.js";
11
11
 
12
12
  // src/costAnalysis.ts
13
13
  import { Project, SyntaxKind } from "ts-morph";
@@ -913,6 +913,89 @@ function anchorIdlAccount(c, params) {
913
913
  };
914
914
  }
915
915
 
916
+ // src/checkers/anchorAccountMissingConstraint.ts
917
+ var DEFAULT_AUTHORITY_RE = /^(authority|admin|owner|payer|caller|signer|user|operator|manager|creator|deployer)$/i;
918
+ function anchorAccountMissingConstraint(c, p) {
919
+ const namePattern = p?.nameRegex ? new RegExp(p.nameRegex) : DEFAULT_AUTHORITY_RE;
920
+ const authorityFields = c.accountFields.filter((f) => namePattern.test(f.name));
921
+ if (authorityFields.length === 0) {
922
+ return {
923
+ result: "cant_tell",
924
+ detail: p?.absentDetail ?? `Handler '${c.fnName}' has no authority-named account fields matching the pattern; rule does not apply.`
925
+ };
926
+ }
927
+ const missing = authorityFields.filter(
928
+ (f) => f.typeName.includes("AccountInfo") && !f.attrText.includes("signer") && !f.typeName.includes("Signer<")
929
+ );
930
+ if (missing.length > 0) {
931
+ const names = missing.map((f) => `'${f.name}'`).join(", ");
932
+ return {
933
+ result: "fail",
934
+ detail: p?.failDetail ?? `Handler '${c.fnName}': authority-named field(s) ${names} use AccountInfo<'info> without a \`signer\` constraint. Anchor performs no signing check on AccountInfo \u2014 any key can be passed as the authority and privileged instructions will execute without signature validation. Fix: change the type to Signer<'info>, or add #[account(signer)] to the field's constraint.`
935
+ };
936
+ }
937
+ return {
938
+ result: "pass",
939
+ detail: p?.passDetail ?? `Handler '${c.fnName}': all authority-named fields use Signer<'info> or have a signer constraint.`
940
+ };
941
+ }
942
+
943
+ // src/checkers/anchorForbiddenAccountType.ts
944
+ var DEFAULT_FORBIDDEN = "UncheckedAccount";
945
+ function anchorForbiddenAccountType(c, p) {
946
+ const forbidden = p?.forbiddenType ?? DEFAULT_FORBIDDEN;
947
+ const flagged = c.accountFields.filter((f) => f.typeName.includes(forbidden));
948
+ if (flagged.length === 0) {
949
+ if (c.accountFields.length > 0) {
950
+ return {
951
+ result: "pass",
952
+ detail: p?.passDetail ?? `Handler '${c.fnName}' has no '${forbidden}' account fields.`
953
+ };
954
+ }
955
+ return {
956
+ result: "cant_tell",
957
+ detail: p?.absentDetail ?? `Handler '${c.fnName}' has no account fields to inspect; rule does not apply.`
958
+ };
959
+ }
960
+ const names = flagged.map((f) => `'${f.name}'`).join(", ");
961
+ return {
962
+ result: "fail",
963
+ detail: p?.failDetail ?? `Handler '${c.fnName}' uses ${forbidden}<'info> on account(s) ${names}. ${forbidden} performs no runtime validation \u2014 ownership, signer status, and data layout are entirely unchecked. Replace with a typed account: Account<'info, T> (program-owned data), Signer<'info> (must sign), SystemAccount<'info> (system-owned), or InterfaceAccount<'info, T> (Token-2022 compatible).`
964
+ };
965
+ }
966
+
967
+ // src/checkers/anchorBodyCallPattern.ts
968
+ function anchorBodyCallPattern(c, p) {
969
+ if (!p?.forbiddenPattern) {
970
+ return { result: "cant_tell", detail: "anchorBodyCallPattern: no forbiddenPattern param provided." };
971
+ }
972
+ const forbidden = new RegExp(p.forbiddenPattern);
973
+ const exempt = p?.exemptPattern ? new RegExp(p.exemptPattern) : null;
974
+ const hasForbidden = forbidden.test(c.fnBodyText);
975
+ if (!hasForbidden) {
976
+ if (c.fnBodyText.trim().length > 0) {
977
+ return {
978
+ result: "pass",
979
+ detail: p?.passDetail ?? `Handler '${c.fnName}' does not contain the forbidden pattern '${p.forbiddenPattern}'.`
980
+ };
981
+ }
982
+ return {
983
+ result: "cant_tell",
984
+ detail: p?.absentDetail ?? `Handler '${c.fnName}' has an empty body; rule does not apply.`
985
+ };
986
+ }
987
+ if (exempt && exempt.test(c.fnBodyText)) {
988
+ return {
989
+ result: "pass",
990
+ detail: p?.passDetail ?? `Handler '${c.fnName}' contains '${p.forbiddenPattern}' but also matches the exemption pattern \u2014 considered safe.`
991
+ };
992
+ }
993
+ return {
994
+ result: "fail",
995
+ detail: p?.failDetail ?? `Handler '${c.fnName}' body contains '${p.forbiddenPattern}', which is a known footgun pattern. Use the Anchor seeds + bump constraint on the Accounts struct instead of deriving PDAs at runtime.`
996
+ };
997
+ }
998
+
916
999
  // src/checkers/index.ts
917
1000
  var registry = {
918
1001
  "positional-arg-identity": positionalArgIdentity,
@@ -927,7 +1010,10 @@ var registry = {
927
1010
  "literal-multiplier-wrong-constant": literalMultiplierWrongConstant,
928
1011
  "forbidden-call-replacement": forbiddenCallReplacement,
929
1012
  "solana-mint-identity-mismatch": solanaMintIdentity,
930
- "anchor-account-matches-idl": anchorIdlAccount
1013
+ "anchor-account-matches-idl": anchorIdlAccount,
1014
+ "anchor-account-missing-constraint": anchorAccountMissingConstraint,
1015
+ "anchor-forbidden-account-type": anchorForbiddenAccountType,
1016
+ "anchor-body-call-pattern": anchorBodyCallPattern
931
1017
  };
932
1018
  function runChecker(kind, c, params) {
933
1019
  const fn = registry[kind];
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  submitTelemetry,
12
12
  telemetryFilePath,
13
13
  validatePack
14
- } from "./chunk-34VXOLJF.js";
14
+ } from "./chunk-B2M3TZSA.js";
15
15
  import {
16
16
  renderTrustGraphMd
17
17
  } from "./chunk-2UZGWXIX.js";
@@ -25,7 +25,7 @@ import {
25
25
  audit,
26
26
  getChangedRanges,
27
27
  resolveRules
28
- } from "./chunk-CRYFCQYM.js";
28
+ } from "./chunk-IY52XKWL.js";
29
29
  import "./chunk-2XJORJPQ.js";
30
30
  import "./chunk-O5Z4ZJHC.js";
31
31
  import "./chunk-XSVQSK53.js";
@@ -128,7 +128,7 @@ if (args[0] === "drift") {
128
128
  process.exit(0);
129
129
  }
130
130
  if (args[0] === "mcp") {
131
- const { startMcpServer } = await import("./mcp-ML2X44WE.js");
131
+ const { startMcpServer } = await import("./mcp-AM7MTCSZ.js");
132
132
  await startMcpServer();
133
133
  process.exit(0);
134
134
  }
package/dist/index.js CHANGED
@@ -54,7 +54,7 @@ import {
54
54
  submitTelemetry,
55
55
  telemetryFilePath,
56
56
  validatePack
57
- } from "./chunk-34VXOLJF.js";
57
+ } from "./chunk-B2M3TZSA.js";
58
58
  import {
59
59
  renderTrustGraphMd
60
60
  } from "./chunk-2UZGWXIX.js";
@@ -91,7 +91,7 @@ import {
91
91
  runChecker,
92
92
  testKinds,
93
93
  validatePackManifest
94
- } from "./chunk-CRYFCQYM.js";
94
+ } from "./chunk-IY52XKWL.js";
95
95
  import {
96
96
  CANONICAL_BY_MINT,
97
97
  CANONICAL_MINTS,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  audit,
3
3
  resolveRules
4
- } from "./chunk-CRYFCQYM.js";
4
+ } from "./chunk-IY52XKWL.js";
5
5
  import "./chunk-2XJORJPQ.js";
6
6
  import "./chunk-O5Z4ZJHC.js";
7
7
  import {
@@ -0,0 +1,28 @@
1
+ id: anchor-pda-find-program-address
2
+ severity: high
3
+ title: Anchor handler calls find_program_address at runtime instead of using seeds+bump constraint
4
+ component:
5
+ name: Anchor Framework
6
+ type: Blockchain
7
+ version: ">=0.26.0"
8
+ sourceUrl: https://www.anchor-lang.com/docs/the-accounts-struct#seeds-and-bump
9
+ detect:
10
+ lang: rust
11
+ modules:
12
+ - "@coral-xyz/anchor"
13
+ - "@project-serum/anchor"
14
+ nameRegex: ".*"
15
+ triggerCalls: []
16
+ check:
17
+ kind: anchor-body-call-pattern
18
+ params:
19
+ forbiddenPattern: "Pubkey::find_program_address|find_program_address\\s*\\("
20
+ test:
21
+ kind: anchor-program-test
22
+ params:
23
+ scenario: pda-canonical-bump-mismatch
24
+ description: >-
25
+ Initializes the PDA storing the canonical bump, then calls the handler with
26
+ a non-canonical bump seed. With find_program_address the handler may accept
27
+ this (re-deriving a different nonce), whereas the seeds+bump constraint
28
+ enforces the stored canonical bump and will reject it.
@@ -0,0 +1,27 @@
1
+ id: anchor-signer-constraint-missing
2
+ severity: critical
3
+ title: Anchor authority account missing signer constraint
4
+ component:
5
+ name: Anchor Framework
6
+ type: Blockchain
7
+ version: ">=0.26.0"
8
+ sourceUrl: https://www.anchor-lang.com/docs/the-accounts-struct#signer
9
+ detect:
10
+ lang: rust
11
+ modules:
12
+ - "@coral-xyz/anchor"
13
+ - "@project-serum/anchor"
14
+ nameRegex: ".*"
15
+ triggerCalls: []
16
+ check:
17
+ kind: anchor-account-missing-constraint
18
+ params:
19
+ nameRegex: "^(authority|admin|owner|payer|caller|signer|user|operator|manager|creator|deployer)$"
20
+ test:
21
+ kind: anchor-program-test
22
+ params:
23
+ scenario: unauthorized-caller
24
+ description: >-
25
+ Calls the instruction with a key that does not match the expected authority
26
+ and has not signed the transaction. Must be rejected with a signing error.
27
+ If it succeeds, the authority check is absent and privilege escalation is possible.
@@ -0,0 +1,27 @@
1
+ id: anchor-unchecked-account-type
2
+ severity: high
3
+ title: Anchor instruction uses UncheckedAccount — no runtime validation
4
+ component:
5
+ name: Anchor Framework
6
+ type: Blockchain
7
+ version: ">=0.26.0"
8
+ sourceUrl: https://www.anchor-lang.com/docs/the-accounts-struct#uncheckedaccount
9
+ detect:
10
+ lang: rust
11
+ modules:
12
+ - "@coral-xyz/anchor"
13
+ - "@project-serum/anchor"
14
+ nameRegex: ".*"
15
+ triggerCalls: []
16
+ check:
17
+ kind: anchor-forbidden-account-type
18
+ params:
19
+ forbiddenType: "UncheckedAccount"
20
+ test:
21
+ kind: anchor-program-test
22
+ params:
23
+ scenario: arbitrary-account-substitution
24
+ description: >-
25
+ Passes an account owned by an arbitrary program (not the expected program ID)
26
+ as the UncheckedAccount field. Must be rejected. If it succeeds, an attacker
27
+ can substitute any account and the instruction will operate on attacker-controlled data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainblast",
3
- "version": "0.7.0",
3
+ "version": "0.7.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": [