@elytrasec/engine 0.4.5 → 0.4.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.
- package/dist/index.js +89 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2912,6 +2912,7 @@ var PY = [".py"];
|
|
|
2912
2912
|
var GO = [".go"];
|
|
2913
2913
|
var SOL = [".sol"];
|
|
2914
2914
|
var JAVA = [".java"];
|
|
2915
|
+
var RUST = [".rs"];
|
|
2915
2916
|
var RUBY = [".rb", ".erb"];
|
|
2916
2917
|
var PHP = [".php"];
|
|
2917
2918
|
var ALL = ["*"];
|
|
@@ -5229,6 +5230,86 @@ var modernDeFiRules = [
|
|
|
5229
5230
|
languages: SOL
|
|
5230
5231
|
}
|
|
5231
5232
|
];
|
|
5233
|
+
var solanaRules = [
|
|
5234
|
+
{
|
|
5235
|
+
id: "cp-sl-missing-signer-check",
|
|
5236
|
+
title: "Solana: account used as authority without signer check",
|
|
5237
|
+
description: "An account is passed to an instruction and used as the authority for an action (transfer, mint, close) without verifying it has signed the transaction. The function deserializes the account but never checks `is_signer` or uses the Anchor `Signer<'info>` type.",
|
|
5238
|
+
suggestion: "Anchor: declare the authority as `pub authority: Signer<'info>` so the runtime enforces is_signer. Raw: `require!(ctx.accounts.authority.is_signer, ErrorCode::Unauthorized);`",
|
|
5239
|
+
multilinePattern: /pub\s+\w+\s*:\s*AccountInfo\s*<'info>[\s\S]{0,400}?(?:transfer|mint_to|close|set_authority|burn)\s*\((?![\s\S]{0,800}?(?:is_signer|Signer\s*<|require!\s*\([^)]*is_signer|require_keys_eq!\s*\([^)]*authority))/,
|
|
5240
|
+
severity: "critical",
|
|
5241
|
+
category: "solidity",
|
|
5242
|
+
confidence: "medium",
|
|
5243
|
+
languages: RUST
|
|
5244
|
+
},
|
|
5245
|
+
{
|
|
5246
|
+
id: "cp-sl-missing-owner-check",
|
|
5247
|
+
title: "Solana: account ownership not verified before deserialization",
|
|
5248
|
+
description: "Raw `AccountInfo` is deserialized with `try_from_slice` or accessed via `data.borrow()` without first checking `account.owner == &expected_program_id`. An attacker can pass any account holding crafted bytes, leading to type confusion.",
|
|
5249
|
+
suggestion: "Anchor: use typed account wrappers (`Account<'info, MyState>`) which check the discriminator automatically. Raw: `require_keys_eq!(*account.owner, program_id, ErrorCode::WrongOwner);` before any deserialization.",
|
|
5250
|
+
multilinePattern: /(?:\.|::)try_from_slice\s*\(\s*(?:&|&mut\s+)\s*\w+\.(?:data|try_borrow_data)(?![\s\S]{0,800}?(?:require_keys_eq!\s*\([^)]*\.owner|require!\s*\([^)]*\.owner\s*==|assert_eq!\s*\([^)]*\.owner|Account\s*<|AccountLoader\s*<))/,
|
|
5251
|
+
severity: "critical",
|
|
5252
|
+
category: "solidity",
|
|
5253
|
+
confidence: "medium",
|
|
5254
|
+
languages: RUST
|
|
5255
|
+
},
|
|
5256
|
+
{
|
|
5257
|
+
id: "cp-sl-ata-confusion",
|
|
5258
|
+
title: "Solana: token account mint not validated (ATA confusion)",
|
|
5259
|
+
description: "An SPL `TokenAccount` is used in a transfer without validating its `mint` field matches the expected mint. An attacker can substitute a token account they control with the same authority but different (worthless) mint, draining the real account.",
|
|
5260
|
+
suggestion: "Anchor: use `#[account(mint = expected_mint, token::authority = user)]` constraints. Raw: `require_keys_eq!(token_account.mint, expected_mint);`",
|
|
5261
|
+
multilinePattern: /token::transfer\s*\([\s\S]{0,400}?\)(?![\s\S]{0,1500}?(?:mint\s*=\s*\w+|mint\s*==\s*\w+|require_keys_eq!\s*\([^)]*\.mint|associated_token::))/,
|
|
5262
|
+
severity: "high",
|
|
5263
|
+
category: "solidity",
|
|
5264
|
+
confidence: "low",
|
|
5265
|
+
languages: RUST
|
|
5266
|
+
},
|
|
5267
|
+
{
|
|
5268
|
+
id: "cp-sl-account-substitution",
|
|
5269
|
+
title: "Solana: account passed without has_one or constraint binding",
|
|
5270
|
+
description: "An account is destructured from `ctx.accounts` and read/written, but the program never checks that this account is the one expected by the user's PDA-derived state. An attacker can substitute any same-typed account.",
|
|
5271
|
+
suggestion: "Use Anchor's `#[account(has_one = expected_field)]` or `#[account(constraint = a.key() == state.b)]` to bind related accounts. Raw: `require_keys_eq!(ctx.accounts.target.key(), state.target);`",
|
|
5272
|
+
multilinePattern: /#\[derive\s*\(\s*Accounts\s*\)\][\s\S]{0,2000}?pub\s+\w+\s*:\s*Account\s*<\s*'info\s*,\s*\w+\s*>\s*,(?![\s\S]{0,2000}?(?:has_one\s*=|constraint\s*=|seeds\s*=))/,
|
|
5273
|
+
// Downgraded to medium: it's a "review needed" heuristic, not a confirmed exploit.
|
|
5274
|
+
severity: "medium",
|
|
5275
|
+
category: "solidity",
|
|
5276
|
+
confidence: "low",
|
|
5277
|
+
languages: RUST
|
|
5278
|
+
},
|
|
5279
|
+
{
|
|
5280
|
+
id: "cp-sl-pyth-staleness",
|
|
5281
|
+
title: "Solana: Pyth/Switchboard price used without staleness check",
|
|
5282
|
+
description: "Pyth `get_price_unchecked` (or Switchboard `get_result`) returns a price that may be stale \u2014 the publisher could be offline. Using this for collateral pricing without checking `publish_time` is the Solana-side equivalent of the Mango oracle exploit ($114M).",
|
|
5283
|
+
suggestion: "Use Pyth's `get_price_no_older_than(clock, MAX_AGE)` (or `get_ema_price_no_older_than`). For Switchboard, check `result.last_update_timestamp` against `clock.unix_timestamp`.",
|
|
5284
|
+
multilinePattern: /(?:get_price_unchecked|get_price_no_older|get_ema_price_unchecked|get_result\s*\(\s*\))\s*\([\s\S]{0,200}?\)(?![\s\S]{0,1200}?(?:publish_time|MAX_AGE|max_age|staleness|STALENESS|unix_timestamp\s*-\s*\w+|clock\.\s*unix_timestamp))/,
|
|
5285
|
+
severity: "high",
|
|
5286
|
+
category: "solidity",
|
|
5287
|
+
confidence: "medium",
|
|
5288
|
+
languages: RUST
|
|
5289
|
+
},
|
|
5290
|
+
{
|
|
5291
|
+
id: "cp-sl-cpi-reentrancy",
|
|
5292
|
+
title: "Solana: state mutation after cross-program invocation",
|
|
5293
|
+
description: "Anchor checks-effects-interactions: state should be updated BEFORE invoking another program. A CPI followed by state mutation invites cross-program reentrancy, especially with callback programs.",
|
|
5294
|
+
suggestion: "Move state writes to BEFORE the `invoke` / `invoke_signed` / `CpiContext::new` call. Or use Anchor's account locking + a reentrancy guard flag.",
|
|
5295
|
+
multilinePattern: /(?:invoke|invoke_signed|CpiContext::new(?:_with_signer)?)\s*\([\s\S]{0,400}?\)\s*\?\s*;[\s\S]{0,400}?(?:\.\s*\w+\s*=\s*\w+|state\.\s*\w+\s*=|account\.\s*\w+\s*=)/,
|
|
5296
|
+
severity: "high",
|
|
5297
|
+
category: "solidity",
|
|
5298
|
+
confidence: "low",
|
|
5299
|
+
languages: RUST
|
|
5300
|
+
},
|
|
5301
|
+
{
|
|
5302
|
+
id: "cp-sl-arbitrary-cpi",
|
|
5303
|
+
title: "Solana: program passed as account, used for arbitrary CPI",
|
|
5304
|
+
description: "An `AccountInfo` (or untyped account) is used as the `program_id` for an `invoke()` call. An attacker who can choose the program account effectively gets arbitrary cross-program invocation \u2014 equivalent to delegatecall in EVM.",
|
|
5305
|
+
suggestion: "Type the program field as `Program<'info, MyProgram>` (Anchor) so the runtime enforces the program's pubkey. Or `require_keys_eq!(program.key(), expected_program_id);` before invoke.",
|
|
5306
|
+
multilinePattern: /invoke(?:_signed)?\s*\(\s*&Instruction\s*\{\s*program_id\s*:\s*[*\w.()]+\s*,/,
|
|
5307
|
+
severity: "critical",
|
|
5308
|
+
category: "solidity",
|
|
5309
|
+
confidence: "medium",
|
|
5310
|
+
languages: RUST
|
|
5311
|
+
}
|
|
5312
|
+
];
|
|
5232
5313
|
var ALL_RULES2 = [
|
|
5233
5314
|
...securityRules,
|
|
5234
5315
|
...solidityRules2,
|
|
@@ -5245,7 +5326,8 @@ var ALL_RULES2 = [
|
|
|
5245
5326
|
...tstoreRules,
|
|
5246
5327
|
...uniswapV4Rules,
|
|
5247
5328
|
...hackReplayRules,
|
|
5248
|
-
...modernDeFiRules
|
|
5329
|
+
...modernDeFiRules,
|
|
5330
|
+
...solanaRules
|
|
5249
5331
|
];
|
|
5250
5332
|
|
|
5251
5333
|
// src/static/pattern-scanner.ts
|
|
@@ -5584,6 +5666,12 @@ function scanFile(relPath, content, rules, changedRanges) {
|
|
|
5584
5666
|
if (rule.id === "cp-hack-wormhole-unchecked-signature-set" && /\b(?:EIP712|DOMAIN_SEPARATOR|_hashTypedDataV4|PERMIT_TYPEHASH|DELEGATION_TYPEHASH|ERC1271|EIP712Upgradeable)\b/.test(content)) continue;
|
|
5585
5667
|
if (rule.id === "cp-sol-eip712-missing-chainid" && /\b(?:block\.chainid|chainId|chainid)\b/.test(content)) continue;
|
|
5586
5668
|
if (rule.id === "cp-sol-bridge-missing-source-check" && /\b(?:EntryPoint|UserOperation|PackedUserOperation|IERC7579|IERC4337|executionCalldata|onlyEntryPoint)\b/.test(content)) continue;
|
|
5669
|
+
if (rule.id === "cp-sl-missing-signer-check" && /\bSigner\s*<\s*'info\s*>/.test(content)) continue;
|
|
5670
|
+
if (rule.id === "cp-sl-ata-confusion" && /(?:mint\s*=\s*\w+|require_keys_eq!\s*\([^)]*\.mint|token::mint_to)/.test(content)) continue;
|
|
5671
|
+
if (rule.id === "cp-sl-account-substitution" && /#\[account\s*\([^)]*(?:has_one|constraint|seeds)\s*=/.test(content)) continue;
|
|
5672
|
+
if (rule.id === "cp-sl-pyth-staleness" && /(?:get_price_no_older_than|publish_time|MAX_AGE|max_age|stale|clock\.unix_timestamp\s*-)/.test(content)) continue;
|
|
5673
|
+
if (rule.id === "cp-sl-arbitrary-cpi" && /Program\s*<\s*'info\s*,/.test(content)) continue;
|
|
5674
|
+
if (rule.id === "cp-sl-missing-owner-check" && /(?:require_keys_eq!\s*\([^)]*\.owner|assert_eq!\s*\([^)]*\.owner|Account\s*<\s*'info|AccountLoader\s*<\s*'info)/.test(content)) continue;
|
|
5587
5675
|
rule.multilinePattern.lastIndex = 0;
|
|
5588
5676
|
const isGlobal = rule.multilinePattern.flags.includes("g");
|
|
5589
5677
|
let match;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elytrasec/engine",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Core analysis engine for Elytra \u2014
|
|
3
|
+
"version": "0.4.6",
|
|
4
|
+
"description": "Core analysis engine for Elytra \u2014 188 detection rules across Solidity, Solana/Anchor (Rust), JS/TS, Python, Go, IaC. Includes 12 famous-hack patterns, 11 rug-surface checks, 5 modern-DeFi detectors, 7 Solana detectors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ElytraSec <hello@elytrasec.io>",
|
|
7
7
|
"homepage": "https://elytrasec.io",
|