brainblast 0.7.5 → 0.7.6

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.
Files changed (50) hide show
  1. package/dist/{chunk-HFYBK2VA.js → chunk-A3U6JDMN.js} +64 -12
  2. package/dist/{chunk-S7X53LWF.js → chunk-CSYGLMZR.js} +19 -19
  3. package/dist/{chunk-V4XS5DKD.js → chunk-Q5DMQN67.js} +19 -5
  4. package/dist/cli.js +60 -24
  5. package/dist/feeConfigs-S4E5GQ7Q.js +19 -0
  6. package/dist/index.d.ts +21 -13
  7. package/dist/index.js +19 -15
  8. package/dist/{mcp-LITBQHBF.js → mcp-O45PSOVU.js} +1 -1
  9. package/dist/packs/README.md +33 -0
  10. package/dist/packs/jito-bundle-zero-tip/README.md +33 -0
  11. package/dist/packs/jito-bundle-zero-tip/brainblast-pack.yaml +5 -0
  12. package/dist/packs/jito-bundle-zero-tip/fixtures/jito-bundle-zero-tip/fixed/bundle.ts +13 -0
  13. package/dist/packs/jito-bundle-zero-tip/fixtures/jito-bundle-zero-tip/vulnerable/bundle.ts +16 -0
  14. package/dist/packs/jito-bundle-zero-tip/rules/jito-bundle-zero-tip.yaml +54 -0
  15. package/dist/packs/jupiter-quote-zero-slippage/brainblast-pack.yaml +5 -0
  16. package/dist/packs/jupiter-quote-zero-slippage/fixtures/jupiter-quote-zero-slippage/fixed/arb.ts +17 -0
  17. package/dist/packs/jupiter-quote-zero-slippage/fixtures/jupiter-quote-zero-slippage/vulnerable/arb.ts +17 -0
  18. package/dist/packs/jupiter-quote-zero-slippage/rules/jupiter-quote-zero-slippage.yaml +51 -0
  19. package/dist/packs/metaplex-nft-royalty-zero/README.md +39 -0
  20. package/dist/packs/metaplex-nft-royalty-zero/brainblast-pack.yaml +5 -0
  21. package/dist/packs/metaplex-nft-royalty-zero/fixtures/metaplex-nft-royalty-zero/fixed/mint.ts +11 -0
  22. package/dist/packs/metaplex-nft-royalty-zero/fixtures/metaplex-nft-royalty-zero/vulnerable/mint.ts +11 -0
  23. package/dist/packs/metaplex-nft-royalty-zero/rules/metaplex-nft-royalty-zero.yaml +39 -0
  24. package/dist/packs/meteora-dlmm-zero-min-out/README.md +32 -0
  25. package/dist/packs/meteora-dlmm-zero-min-out/brainblast-pack.yaml +5 -0
  26. package/dist/packs/meteora-dlmm-zero-min-out/fixtures/meteora-dlmm-zero-min-out/fixed/swap.ts +19 -0
  27. package/dist/packs/meteora-dlmm-zero-min-out/fixtures/meteora-dlmm-zero-min-out/vulnerable/swap.ts +19 -0
  28. package/dist/packs/meteora-dlmm-zero-min-out/rules/meteora-dlmm-zero-min-out.yaml +47 -0
  29. package/dist/packs/pyth-price-unchecked-staleness/README.md +34 -0
  30. package/dist/packs/pyth-price-unchecked-staleness/brainblast-pack.yaml +5 -0
  31. package/dist/packs/pyth-price-unchecked-staleness/fixtures/pyth-price-unchecked-staleness/fixed/price.ts +14 -0
  32. package/dist/packs/pyth-price-unchecked-staleness/fixtures/pyth-price-unchecked-staleness/vulnerable/price.ts +14 -0
  33. package/dist/packs/pyth-price-unchecked-staleness/rules/pyth-price-unchecked-staleness.yaml +47 -0
  34. package/dist/packs/raydium-compute-zero-slippage/README.md +43 -0
  35. package/dist/packs/raydium-compute-zero-slippage/brainblast-pack.yaml +5 -0
  36. package/dist/packs/raydium-compute-zero-slippage/fixtures/raydium-compute-zero-slippage/fixed/swap.ts +12 -0
  37. package/dist/packs/raydium-compute-zero-slippage/fixtures/raydium-compute-zero-slippage/vulnerable/swap.ts +12 -0
  38. package/dist/packs/raydium-compute-zero-slippage/rules/raydium-compute-zero-slippage.yaml +40 -0
  39. package/dist/packs/solana-sendtx-unconfirmed/README.md +34 -0
  40. package/dist/packs/solana-sendtx-unconfirmed/brainblast-pack.yaml +5 -0
  41. package/dist/packs/solana-sendtx-unconfirmed/fixtures/solana-sendtx-unconfirmed/fixed/payment.ts +10 -0
  42. package/dist/packs/solana-sendtx-unconfirmed/fixtures/solana-sendtx-unconfirmed/vulnerable/payment.ts +10 -0
  43. package/dist/packs/solana-sendtx-unconfirmed/rules/solana-sendtx-unconfirmed.yaml +40 -0
  44. package/dist/packs/spl-transfer-not-checked-in-payout/brainblast-pack.yaml +5 -0
  45. package/dist/packs/spl-transfer-not-checked-in-payout/fixtures/spl-transfer-not-checked-in-payout/fixed/payout-processor.ts +29 -0
  46. package/dist/packs/spl-transfer-not-checked-in-payout/fixtures/spl-transfer-not-checked-in-payout/vulnerable/payout-processor.ts +22 -0
  47. package/dist/packs/spl-transfer-not-checked-in-payout/rules/spl-transfer-not-checked-in-payout.yaml +49 -0
  48. package/dist/rules/metaplex-seller-fee-zero.yaml +2 -2
  49. package/package.json +1 -1
  50. package/dist/tokenEconomics-HBF3DYNH.js +0 -19
@@ -0,0 +1,43 @@
1
+ # raydium-compute-zero-slippage
2
+
3
+ **Severity:** HIGH
4
+
5
+ ## What's the trap?
6
+
7
+ `@raydium-io/raydium-sdk-v2`'s `computeAmountOut()` takes a `slippage` parameter (a decimal fraction, e.g. `0.5` = 0.5%). When `slippage: 0`, the SDK sets `minAmountOut === amountOut` — the swap will only succeed if the received amount is exactly equal to the computed output with zero tolerance.
8
+
9
+ In practice this means:
10
+
11
+ - Any price movement between compute and on-chain execution causes the tx to fail (not protected, just fails)
12
+ - A sandwich attack can front-run the swap and move the price; at `slippage: 0` there is no minimum-out floor, so the swap either fails with no protection or (depending on pool type) executes at a worse effective rate
13
+
14
+ AI-generated swap bots frequently default to `slippage: 0` to "avoid slippage" without understanding that they're actually removing all minimum-output guarantees.
15
+
16
+ ## Why it's silent
17
+
18
+ `computeAmountOut()` succeeds with `slippage: 0`. The route is computed, the tx is built, and the swap may execute. The trap only becomes apparent at the wallet level when MEV bots consistently extract value from the unprotected path.
19
+
20
+ ## The fix
21
+
22
+ ```typescript
23
+ // BEFORE (vulnerable — no minimum output protection)
24
+ return raydium.liquidity.computeAmountOut({
25
+ poolInfo: pool.poolInfo,
26
+ amountIn,
27
+ mintInfo: pool.mintInfos,
28
+ slippage: 0, // ← trap
29
+ });
30
+
31
+ // AFTER (fixed — 0.5% tolerance)
32
+ return raydium.liquidity.computeAmountOut({
33
+ poolInfo: pool.poolInfo,
34
+ amountIn,
35
+ mintInfo: pool.mintInfos,
36
+ slippage: 0.5, // 0.5% — adjust to suit trade size and volatility
37
+ });
38
+ ```
39
+
40
+ ## References
41
+
42
+ - [Raydium SDK v2 GitHub](https://github.com/raydium-io/raydium-sdk-v2)
43
+ - `ComputeAmountOutParam.slippage: number` — confirmed in `src/raydium/liquidity/type.ts`
@@ -0,0 +1,5 @@
1
+ id: raydium-compute-zero-slippage
2
+ name: Raydium computeAmountOut with zero slippage
3
+ version: 0.1.0
4
+ author: DSB-117
5
+ description: Detects computeAmountOut called with slippage 0 enabling sandwich attacks
@@ -0,0 +1,12 @@
1
+ import { Raydium } from "@raydium-io/raydium-sdk-v2";
2
+
3
+ export async function getSwapOutput(raydium: Raydium, poolId: string, amountIn: bigint) {
4
+ const pool = await raydium.liquidity.getPoolInfoFromRpc(poolId);
5
+ // FIXED: 0.5% slippage tolerance enforces a minimum output floor
6
+ return raydium.liquidity.computeAmountOut({
7
+ poolInfo: pool.poolInfo,
8
+ amountIn,
9
+ mintInfo: pool.mintInfos,
10
+ slippage: 0.5,
11
+ });
12
+ }
@@ -0,0 +1,12 @@
1
+ import { Raydium } from "@raydium-io/raydium-sdk-v2";
2
+
3
+ export async function getSwapOutput(raydium: Raydium, poolId: string, amountIn: bigint) {
4
+ const pool = await raydium.liquidity.getPoolInfoFromRpc(poolId);
5
+ // VULNERABLE: slippage: 0 means minAmountOut === amountOut — no sandwich protection
6
+ return raydium.liquidity.computeAmountOut({
7
+ poolInfo: pool.poolInfo,
8
+ amountIn,
9
+ mintInfo: pool.mintInfos,
10
+ slippage: 0,
11
+ });
12
+ }
@@ -0,0 +1,40 @@
1
+ # Auto-synthesized from a research Finding (proof-as-classifier loop).
2
+ # Pure data — every kind below resolves to a HUMAN-VETTED template in core.
3
+ id: raydium-compute-zero-slippage
4
+ severity: high
5
+ title: "Raydium computeAmountOut called with slippage: 0 — no minimum output
6
+ protection against sandwich attacks"
7
+ component:
8
+ name: "@raydium-io/raydium-sdk-v2"
9
+ type: Blockchain
10
+ version: ">=0.1.0"
11
+ sourceUrl: https://github.com/raydium-io/raydium-sdk-v2
12
+ detect:
13
+ modules:
14
+ - "@raydium-io/raydium-sdk-v2"
15
+ nameRegex: swap|arb|trade|exchange|buy|sell
16
+ triggerCalls:
17
+ - computeAmountOut
18
+ - computeAmountIn
19
+ check:
20
+ kind: object-arg-property-forbidden-literal
21
+ params:
22
+ call: computeAmountOut
23
+ argIndex: 0
24
+ propName: slippage
25
+ forbiddenValue: 0
26
+ passDetail: computeAmountOut is called with a nonzero slippage value — the swap
27
+ has minimum output protection against sandwich attacks.
28
+ failDetail: "computeAmountOut is called with slippage: 0, which sets
29
+ minAmountOut equal to amountOut with zero tolerance. Any price movement
30
+ between compute and execution — including a sandwich attack — will cause
31
+ the swap to execute at a worse price with no revert protection. Set
32
+ slippage to a nonzero value (e.g. 0.5 = 0.5%) to enforce a minAmountOut
33
+ floor."
34
+ absentCallDetail: Handler is in the swap scope but does not call
35
+ computeAmountOut(); this rule does not apply here.
36
+ absentArgDetail: computeAmountOut does not include a slippage literal in its
37
+ options object; the slippage tolerance cannot be confirmed statically.
38
+ Verify the resolved value is nonzero.
39
+ test:
40
+ kind: none
@@ -0,0 +1,34 @@
1
+ # solana-sendtx-unconfirmed
2
+
3
+ **Severity:** HIGH
4
+
5
+ ## What's the trap?
6
+
7
+ `@solana/web3.js` exposes two ways to send a transaction:
8
+
9
+ | Call | Behaviour |
10
+ |------|-----------|
11
+ | `connection.sendTransaction(tx, signers)` | Submits to the cluster and returns a signature **immediately** — fire-and-forget. |
12
+ | `sendAndConfirmTransaction(connection, tx, signers)` | Waits until the transaction is **confirmed** before returning; throws if it fails or is dropped. |
13
+
14
+ AI code generators routinely emit `sendTransaction()` because it matches the shape of "send a thing and get a result." The transaction returns a signature that *looks* like success, but the transaction may still be dropped due to network congestion, a validator restart, or blockhash expiry.
15
+
16
+ ## Why it's silent
17
+
18
+ `sendTransaction()` resolves as soon as the RPC node accepts the transaction — not when it lands on-chain. The returned signature is valid regardless of whether the transaction was ever included in a block. Code that credits a user, debits inventory, or records a transfer immediately after this call will think it succeeded even when the on-chain state was never changed.
19
+
20
+ ## The fix
21
+
22
+ ```typescript
23
+ // BEFORE (vulnerable)
24
+ const sig = await connection.sendTransaction(tx, [signer]);
25
+
26
+ // AFTER (fixed)
27
+ import { sendAndConfirmTransaction } from "@solana/web3.js";
28
+ const sig = await sendAndConfirmTransaction(connection, tx, [signer]);
29
+ ```
30
+
31
+ ## References
32
+
33
+ - [Solana web3.js docs — sendAndConfirmTransaction](https://solana-labs.github.io/solana-web3.js/)
34
+ - Solana Cookbook: "Sending Transactions" — always confirm before crediting
@@ -0,0 +1,5 @@
1
+ id: solana-sendtx-unconfirmed
2
+ name: Solana sendTransaction without confirmation
3
+ version: 0.1.0
4
+ author: DSB-117
5
+ description: Detects connection.sendTransaction() used in value-bearing paths without confirmation — transactions may silently drop on congestion or blockhash expiry
@@ -0,0 +1,10 @@
1
+ import { Connection, Transaction, Keypair, sendAndConfirmTransaction } from "@solana/web3.js";
2
+
3
+ export async function sendPayment(
4
+ connection: Connection,
5
+ tx: Transaction,
6
+ signer: Keypair,
7
+ ): Promise<string> {
8
+ // FIXED: blocks until the transaction is confirmed by the cluster
9
+ return sendAndConfirmTransaction(connection, tx, [signer]);
10
+ }
@@ -0,0 +1,10 @@
1
+ import { Connection, Transaction, Keypair } from "@solana/web3.js";
2
+
3
+ export async function sendPayment(
4
+ connection: Connection,
5
+ tx: Transaction,
6
+ signer: Keypair,
7
+ ): Promise<string> {
8
+ // VULNERABLE: fire-and-forget — no confirmation that the transaction landed
9
+ return connection.sendTransaction(tx, [signer]);
10
+ }
@@ -0,0 +1,40 @@
1
+ # Auto-synthesized from a research Finding (proof-as-classifier loop).
2
+ # Pure data — every kind below resolves to a HUMAN-VETTED template in core.
3
+ id: solana-sendtx-unconfirmed
4
+ severity: high
5
+ title: sendTransaction used instead of sendAndConfirmTransaction — transaction
6
+ may silently drop
7
+ component:
8
+ name: "@solana/web3.js"
9
+ type: Blockchain
10
+ version: ">=1.0.0"
11
+ sourceUrl: https://solana-labs.github.io/solana-web3.js/
12
+ detect:
13
+ modules:
14
+ - "@solana/web3.js"
15
+ nameRegex: send|transfer|execute|submit|payout|swap
16
+ triggerCalls:
17
+ - sendTransaction
18
+ - sendAndConfirmTransaction
19
+ check:
20
+ kind: forbidden-call-replacement
21
+ params:
22
+ forbiddenCalls:
23
+ - sendTransaction
24
+ saferCalls:
25
+ - sendAndConfirmTransaction
26
+ passDetail: Handler calls sendAndConfirmTransaction, which waits for the cluster
27
+ to confirm the transaction before returning and propagates any on-chain
28
+ error as a thrown exception.
29
+ failDetail: Handler calls connection.sendTransaction() but not
30
+ sendAndConfirmTransaction(). sendTransaction() submits the transaction and
31
+ returns a signature immediately, with no confirmation that it landed. If
32
+ the transaction is dropped (congestion, validator restart, blockhash
33
+ expiry) the code continues as if it succeeded — tokens are not moved but
34
+ the caller assumes they were. Use sendAndConfirmTransaction(connection,
35
+ tx, signers) to block until confirmation.
36
+ absentDetail: Handler is in the send/transfer scope but calls neither
37
+ sendTransaction nor sendAndConfirmTransaction; this rule does not apply
38
+ here.
39
+ test:
40
+ kind: none
@@ -0,0 +1,5 @@
1
+ id: spl-transfer-not-checked-in-payout
2
+ name: SPL transfer not checked in payout
3
+ version: 0.1.0
4
+ author: DSB-117
5
+ description: Flags SPL token payouts that use createTransferInstruction instead of createTransferCheckedInstruction
@@ -0,0 +1,29 @@
1
+ import { Connection, Keypair, Transaction } from "@solana/web3.js";
2
+ import { createTransferCheckedInstruction, getAssociatedTokenAddress } from "@solana/spl-token";
3
+
4
+ const TOKEN_DECIMALS = 6;
5
+
6
+ export async function executeSolanaPayout(
7
+ connection: Connection,
8
+ payer: Keypair,
9
+ mintAddress: string,
10
+ destinationOwner: string,
11
+ amountTokens: number,
12
+ ) {
13
+ const sourceAta = await getAssociatedTokenAddress(mintAddress as any, payer.publicKey);
14
+ const destinationAta = await getAssociatedTokenAddress(mintAddress as any, destinationOwner as any);
15
+
16
+ const amount = amountTokens * 10 ** TOKEN_DECIMALS;
17
+
18
+ const transferIx = createTransferCheckedInstruction(
19
+ sourceAta,
20
+ mintAddress as any,
21
+ destinationAta,
22
+ payer.publicKey,
23
+ amount,
24
+ TOKEN_DECIMALS,
25
+ );
26
+
27
+ const tx = new Transaction().add(transferIx);
28
+ return connection.sendTransaction(tx, [payer]);
29
+ }
@@ -0,0 +1,22 @@
1
+ import { Connection, Keypair, Transaction } from "@solana/web3.js";
2
+ import { createTransferInstruction, getAssociatedTokenAddress } from "@solana/spl-token";
3
+
4
+ const TOKEN_DECIMALS = 6;
5
+
6
+ export async function executeSolanaPayout(
7
+ connection: Connection,
8
+ payer: Keypair,
9
+ mintAddress: string,
10
+ destinationOwner: string,
11
+ amountTokens: number,
12
+ ) {
13
+ const sourceAta = await getAssociatedTokenAddress(mintAddress as any, payer.publicKey);
14
+ const destinationAta = await getAssociatedTokenAddress(mintAddress as any, destinationOwner as any);
15
+
16
+ const amount = amountTokens * 10 ** TOKEN_DECIMALS;
17
+
18
+ const transferIx = createTransferInstruction(sourceAta, destinationAta, payer.publicKey, amount);
19
+
20
+ const tx = new Transaction().add(transferIx);
21
+ return connection.sendTransaction(tx, [payer]);
22
+ }
@@ -0,0 +1,49 @@
1
+ # Pure-data rule (facts). Binds to the vetted `forbidden-call-replacement`
2
+ # checker template.
3
+ #
4
+ # Real-world instance of this exact pattern:
5
+ # - elizaOS/eliza, payout-processor.ts — executeSolanaPayout() builds the
6
+ # transfer instruction with `createTransferInstruction(sourceAta,
7
+ # destinationAta, payer, amount)`, the legacy SPL Token instruction that
8
+ # does NOT validate the mint or decimals of the accounts it's given.
9
+ # `createTransferCheckedInstruction` is the drop-in replacement that adds
10
+ # mint+decimals validation, closing a class of bugs where a malicious or
11
+ # mismatched token account causes funds to move on the wrong mint or with
12
+ # the wrong decimal scaling.
13
+ id: spl-transfer-not-checked-in-payout
14
+ severity: high
15
+ title: Token payout uses createTransferInstruction instead of createTransferCheckedInstruction
16
+ component:
17
+ name: SPL Token
18
+ type: Blockchain
19
+ version: unversioned
20
+ sourceUrl: https://spl.solana.com/token
21
+ detect:
22
+ modules: ["@solana/spl-token"]
23
+ nameRegex: "payout|withdraw|disburse|transfer|distribute"
24
+ triggerCalls: [createTransferInstruction, createTransferCheckedInstruction]
25
+ check:
26
+ kind: forbidden-call-replacement
27
+ params:
28
+ forbiddenCalls: [createTransferInstruction]
29
+ saferCalls: [createTransferCheckedInstruction]
30
+ passDetail: >-
31
+ Payout uses createTransferCheckedInstruction, which validates the mint
32
+ and decimals of the source/destination token accounts before building
33
+ the transfer.
34
+ failDetail: >-
35
+ Payout builds its transfer with createTransferInstruction, the legacy
36
+ SPL Token instruction. It performs no on-chain validation that the
37
+ source and destination accounts belong to the expected mint or that the
38
+ amount is scaled to the correct decimals. If a caller (or a chain of
39
+ account-derivation bugs) supplies a token account for the wrong mint,
40
+ the transfer can move the wrong asset or move the right asset at the
41
+ wrong decimal scale, with no protocol-level check to stop it. Replace
42
+ with createTransferCheckedInstruction(source, mint, destination, owner,
43
+ amount, decimals).
44
+ absentDetail: >-
45
+ Handler is in the payout/transfer scope but neither
46
+ createTransferInstruction nor createTransferCheckedInstruction was
47
+ found; this rule does not apply here.
48
+ test:
49
+ kind: none
@@ -1,4 +1,4 @@
1
- # Token Economics Validator (v0.7.5) — the generalized Bags exploit.
1
+ # Fee Config Validator (v0.7.5) — the generalized Bags exploit.
2
2
  # A revenue-bearing field omitted from a config object silently defaults to
3
3
  # zero: the call succeeds, nothing is collected, forever. Here: Metaplex
4
4
  # `sellerFeeBasisPoints` — the on-chain royalty creators earn on secondary
@@ -18,7 +18,7 @@ detect:
18
18
  nameRegex: "metaplex|mpl|createNft|mint"
19
19
  triggerCalls: [create, createV1, createNft, createFungible, createAndMint]
20
20
  check:
21
- kind: economic-value-zero-or-missing
21
+ kind: fee-configs-zero-or-missing
22
22
  params:
23
23
  calls: [create, createV1, createNft, createFungible, createAndMint]
24
24
  field: sellerFeeBasisPoints
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainblast",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
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": [
@@ -1,19 +0,0 @@
1
- import {
2
- ECONOMIC_PATTERNS,
3
- economicPatternsByCategory,
4
- enforcedCount,
5
- getEconomicPattern,
6
- renderEconomicDetailText,
7
- renderEconomicsMd,
8
- renderEconomicsText
9
- } from "./chunk-S7X53LWF.js";
10
- import "./chunk-3RG5ZIWI.js";
11
- export {
12
- ECONOMIC_PATTERNS,
13
- economicPatternsByCategory,
14
- enforcedCount,
15
- getEconomicPattern,
16
- renderEconomicDetailText,
17
- renderEconomicsMd,
18
- renderEconomicsText
19
- };