@elytrasec/engine 0.4.3 → 0.4.5
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 +107 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3028,12 +3028,48 @@ var securityRules = [
|
|
|
3028
3028
|
title: "Potential command injection",
|
|
3029
3029
|
description: "Shell command built with string concatenation or template literals. Attacker-controlled input may escape the intended command.",
|
|
3030
3030
|
suggestion: "Use execFile() with an argument array or a library like execa that avoids shell interpretation.",
|
|
3031
|
-
|
|
3031
|
+
// Catches:
|
|
3032
|
+
// JS: exec(`ls ${dir}`), execSync("cmd " + x), spawn("...", ...)
|
|
3033
|
+
// Py: os.system(f"ping {host}"), os.popen("cmd " + x), subprocess.call("..." + x, shell=True)
|
|
3034
|
+
pattern: /(?:exec|execSync|spawn|spawnSync|system|popen|run|call|check_output|getoutput)\s*\(\s*(?:`[^`]*\$\{|[fF]"(?:[^"\\]|\\.)*\{|[fF]'(?:[^'\\]|\\.)*\{|"(?:[^"\\]|\\.)*"\s*\+|'(?:[^'\\]|\\.)*'\s*\+)/,
|
|
3032
3035
|
severity: "critical",
|
|
3033
3036
|
category: "security",
|
|
3034
3037
|
confidence: "medium",
|
|
3035
3038
|
languages: [...JS_TS, ...PY]
|
|
3036
3039
|
},
|
|
3040
|
+
{
|
|
3041
|
+
id: "cp-sec-python-eval",
|
|
3042
|
+
title: "Python eval() / exec() with dynamic input",
|
|
3043
|
+
description: "Python `eval()` or `exec()` executes arbitrary code from a string. Reachable via `request.args`, `request.form`, or any user-controlled variable, this is direct RCE.",
|
|
3044
|
+
suggestion: "Avoid eval/exec entirely. For arithmetic, use `ast.literal_eval`. For dispatch, use an explicit allow-list dict.",
|
|
3045
|
+
pattern: /\b(?:eval|exec)\s*\(\s*(?:request\.|input\s*\(|sys\.argv|os\.environ|[\w.]+\.(?:args|form|query|params|json|body))/,
|
|
3046
|
+
severity: "critical",
|
|
3047
|
+
category: "security",
|
|
3048
|
+
confidence: "high",
|
|
3049
|
+
languages: PY
|
|
3050
|
+
},
|
|
3051
|
+
{
|
|
3052
|
+
id: "cp-sec-python-eval-loose",
|
|
3053
|
+
title: "Python eval() / exec() call",
|
|
3054
|
+
description: "Python's `eval()` and `exec()` evaluate arbitrary code. Even when input looks trusted, these are common code-injection vectors.",
|
|
3055
|
+
suggestion: "Replace with `ast.literal_eval` (for data literals) or an explicit allow-list. If you must use eval, scope the globals/locals dict to empty.",
|
|
3056
|
+
pattern: /\b(?:eval|exec)\s*\(\s*(?!{|\[|'\)|"\)|None|globals|locals)/,
|
|
3057
|
+
severity: "high",
|
|
3058
|
+
category: "security",
|
|
3059
|
+
confidence: "low",
|
|
3060
|
+
languages: PY
|
|
3061
|
+
},
|
|
3062
|
+
{
|
|
3063
|
+
id: "cp-sec-go-path-traversal",
|
|
3064
|
+
title: "Go file read with unsanitized user input (path traversal)",
|
|
3065
|
+
description: "`ioutil.ReadFile` / `os.ReadFile` / `os.Open` constructed via concatenation with HTTP request data is path-traversal-vulnerable. Attackers pass `../../etc/passwd`.",
|
|
3066
|
+
suggestion: "Use `filepath.Clean` and verify the resolved path starts with the expected base directory. Better: maintain an allow-list of accepted file IDs.",
|
|
3067
|
+
pattern: /(?:ioutil\.ReadFile|os\.ReadFile|os\.Open(?:File)?)\s*\([^)]*(?:\+\s*\w+\s*\)|r\.URL\.Query|r\.FormValue|mux\.Vars)/,
|
|
3068
|
+
severity: "high",
|
|
3069
|
+
category: "security",
|
|
3070
|
+
confidence: "medium",
|
|
3071
|
+
languages: [".go"]
|
|
3072
|
+
},
|
|
3037
3073
|
{
|
|
3038
3074
|
id: "cp-sec-open-redirect",
|
|
3039
3075
|
title: "Potential open redirect",
|
|
@@ -5128,6 +5164,71 @@ var rugSurfaceRules = [
|
|
|
5128
5164
|
languages: SOL
|
|
5129
5165
|
}
|
|
5130
5166
|
];
|
|
5167
|
+
var modernDeFiRules = [
|
|
5168
|
+
{
|
|
5169
|
+
id: "cp-sol-erc4626-inflation",
|
|
5170
|
+
title: "ERC-4626 vault: missing inflation-attack protection",
|
|
5171
|
+
description: "ERC-4626 deposit/mint computes shares as `assets * totalSupply / totalAssets`. On an empty vault, an attacker who is the first depositor can deposit 1 wei, then DIRECTLY transfer a large amount of underlying to the vault \u2014 inflating the share/asset ratio. Subsequent depositors get rounded to ZERO shares for any deposit smaller than the inflated rate. Cream, Hundred, and many forks lost funds this way.",
|
|
5172
|
+
suggestion: "Use OpenZeppelin's ERC4626 v5+ which adds 1e6 virtual shares/assets (decimal offset). OR: in deposit(), require msg.sender to mint to a dead address first (initial burn of N shares). OR: enforce a minimum total-supply invariant.",
|
|
5173
|
+
multilinePattern: /function\s+(?:deposit|mint)\s*\([^)]*\)\s*(?:external|public)[^{;]*\{[\s\S]{0,1500}?(?:totalSupply|_totalSupply)\s*\([^)]*\)\s*[*/]\s*\w(?![\s\S]{0,800}?(?:_decimalsOffset|virtualShares|VIRTUAL_SHARES|MINIMUM_LIQUIDITY|10\s*\*\*\s*\d|DEAD_SHARES|_burnDead|dead\s*\(|initialBurn))/,
|
|
5174
|
+
severity: "high",
|
|
5175
|
+
category: "solidity",
|
|
5176
|
+
confidence: "medium",
|
|
5177
|
+
languages: SOL
|
|
5178
|
+
},
|
|
5179
|
+
{
|
|
5180
|
+
id: "cp-sol-eip712-missing-chainid",
|
|
5181
|
+
title: "EIP-712 domain separator computed without chainId",
|
|
5182
|
+
description: "An EIP-712 typed-data signature scheme that omits `block.chainid` from the DOMAIN_SEPARATOR allows the same signature to be replayed on a fork or testnet. Several bridge and permit exploits have come from this exact gap.",
|
|
5183
|
+
suggestion: "Always include `block.chainid` in the EIP-712 domain hash, OR inherit from OpenZeppelin's EIP712 which handles it. Recompute the separator when chainid changes (fork detection).",
|
|
5184
|
+
// Catches the domain-separator computation. File-level FP filter in pattern-scanner.ts
|
|
5185
|
+
// suppresses this rule entirely if the file has any chainid reference anywhere.
|
|
5186
|
+
multilinePattern: /(?:DOMAIN_SEPARATOR|_domainSeparator|domainSeparator)\s*=\s*keccak256\s*\(\s*abi\.encode\s*\([\s\S]{20,500}?\)\s*\)/,
|
|
5187
|
+
severity: "high",
|
|
5188
|
+
category: "solidity",
|
|
5189
|
+
confidence: "medium",
|
|
5190
|
+
languages: SOL
|
|
5191
|
+
},
|
|
5192
|
+
{
|
|
5193
|
+
id: "cp-sol-swap-zero-deadline",
|
|
5194
|
+
title: "Swap call with zero or absent deadline (MEV vulnerable)",
|
|
5195
|
+
description: "Calling a Uniswap V2/V3/V4 router swap with deadline = 0, deadline = block.timestamp (no protection), or deadline = type(uint256).max means the transaction can sit in the mempool indefinitely and be sandwiched whenever the price moves against you. Users lose money to MEV bots.",
|
|
5196
|
+
suggestion: "Enforce a real deadline: `deadline = block.timestamp + 15 minutes` at minimum. Pass it from the user, not computed inside the function. Reject calls with deadline > block.timestamp + 1 hour.",
|
|
5197
|
+
// Catches both named-arg (deadline: 0) and positional last-arg = 0 or block.timestamp or type(uint).max.
|
|
5198
|
+
// Multiline + tolerant of nested parens (address(this), etc.). Catches positional last-arg
|
|
5199
|
+
// = 0, block.timestamp, or type(uint).max as the deadline.
|
|
5200
|
+
multilinePattern: /(?:swapExactTokensForTokens|swapTokensForExactTokens|exactInput|exactOutput|swap[A-Z]\w*)\s*\([\s\S]{0,400}?,\s*(?:0|block\.timestamp|type\s*\(\s*uint256\s*\)\s*\.\s*max)\s*\)\s*;/,
|
|
5201
|
+
severity: "medium",
|
|
5202
|
+
category: "solidity",
|
|
5203
|
+
confidence: "low",
|
|
5204
|
+
languages: SOL
|
|
5205
|
+
},
|
|
5206
|
+
{
|
|
5207
|
+
id: "cp-sol-bridge-missing-source-check",
|
|
5208
|
+
title: "Cross-chain receive function: missing source-chain / trusted-remote check",
|
|
5209
|
+
description: "LayerZero `lzReceive`, Axelar `_execute`, Hyperlane `handle`, Wormhole `receiveMessage` and similar cross-chain receivers MUST verify (a) the source chain ID and (b) the trusted-remote sender address. Without these checks ANY contract on any chain can forge a message and trigger the receive logic \u2014 leading to unauthorized mint/withdraw of bridged assets.",
|
|
5210
|
+
suggestion: "Add explicit checks: `require(_srcChainId == trustedSrcChain)` and `require(keccak256(_srcAddress) == keccak256(trustedRemote))`. For LayerZero use `lzApp.setTrustedRemote`. For Axelar verify `sourceChain` + `sourceAddress`.",
|
|
5211
|
+
// Bridge-specific function names; file-level filter in pattern-scanner.ts skips ERC-4337
|
|
5212
|
+
// and similar accounts. Negative lookahead for trusted-remote / source-check in body.
|
|
5213
|
+
multilinePattern: /function\s+(?:lzReceive|nonblockingLzReceive|_nonblockingLzReceive|_handleMessage|receiveMessage|handle)\s*\([^)]*\)\s*(?:external|internal|public)[^{;]*\{(?![\s\S]{0,1500}?(?:trustedRemote|_trustedRemoteLookup|sourceChain\s*==|_srcChainId\s*==|trustedSrcChain|trustedSender|_validateMessageSource|_authorizeSource|onlyEndpoint|onlyMailbox|onlyGateway|interchainSecurityModule))/,
|
|
5214
|
+
severity: "critical",
|
|
5215
|
+
category: "solidity",
|
|
5216
|
+
confidence: "medium",
|
|
5217
|
+
languages: SOL
|
|
5218
|
+
},
|
|
5219
|
+
{
|
|
5220
|
+
id: "cp-sol-permit2-no-deadline-check",
|
|
5221
|
+
title: "Permit signature accepted without deadline / expiry check",
|
|
5222
|
+
description: "EIP-2612 permit() (and similar typed-data signature endpoints) must check that `deadline >= block.timestamp`. Without this, a leaked signature is valid forever. Several memecoin-launchpad bugs of 2024 leaked permits and lost early-mover allocations.",
|
|
5223
|
+
suggestion: 'Add `require(deadline >= block.timestamp, "Permit expired")` at the top of permit-like functions. Reject if deadline is type(uint256).max (suggests caller forgot to set one).',
|
|
5224
|
+
// Also accept assembly-style timestamp/deadline check (Solady pattern: gt(timestamp(), deadline))
|
|
5225
|
+
multilinePattern: /function\s+(?:permit|permitWithSig|permitTransfer|permitTransferFrom)\s*\([^)]*deadline[^)]*\)[^{;]*\{(?![\s\S]{0,1500}?(?:deadline\s*[><]=?\s*block\.timestamp|block\.timestamp\s*[<>]=?\s*deadline|require\s*\([^)]*deadline|gt\s*\(\s*timestamp\s*\(\s*\)\s*,\s*deadline|PermitExpired|ExpiredSignature|EXPIRED))/,
|
|
5226
|
+
severity: "high",
|
|
5227
|
+
category: "solidity",
|
|
5228
|
+
confidence: "medium",
|
|
5229
|
+
languages: SOL
|
|
5230
|
+
}
|
|
5231
|
+
];
|
|
5131
5232
|
var ALL_RULES2 = [
|
|
5132
5233
|
...securityRules,
|
|
5133
5234
|
...solidityRules2,
|
|
@@ -5143,7 +5244,8 @@ var ALL_RULES2 = [
|
|
|
5143
5244
|
...eip7702Rules,
|
|
5144
5245
|
...tstoreRules,
|
|
5145
5246
|
...uniswapV4Rules,
|
|
5146
|
-
...hackReplayRules
|
|
5247
|
+
...hackReplayRules,
|
|
5248
|
+
...modernDeFiRules
|
|
5147
5249
|
];
|
|
5148
5250
|
|
|
5149
5251
|
// src/static/pattern-scanner.ts
|
|
@@ -5479,6 +5581,9 @@ function scanFile(relPath, content, rules, changedRanges) {
|
|
|
5479
5581
|
if (!rule.multilinePattern) continue;
|
|
5480
5582
|
if (rule.id === "cp-clean-callback-hell" && isTestFile(relPath)) continue;
|
|
5481
5583
|
if (rule.id === "cp-sec-command-injection" && isScriptDir(relPath)) continue;
|
|
5584
|
+
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
|
+
if (rule.id === "cp-sol-eip712-missing-chainid" && /\b(?:block\.chainid|chainId|chainid)\b/.test(content)) continue;
|
|
5586
|
+
if (rule.id === "cp-sol-bridge-missing-source-check" && /\b(?:EntryPoint|UserOperation|PackedUserOperation|IERC7579|IERC4337|executionCalldata|onlyEntryPoint)\b/.test(content)) continue;
|
|
5482
5587
|
rule.multilinePattern.lastIndex = 0;
|
|
5483
5588
|
const isGlobal = rule.multilinePattern.flags.includes("g");
|
|
5484
5589
|
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.5",
|
|
4
|
+
"description": "Core analysis engine for Elytra \u2014 181 detection rules including 12 famous-hack patterns, 11 rug-surface checks, 5 modern-DeFi detectors (ERC-4626 inflation, EIP-712 chainId, bridge source check, permit deadline, MEV swap), static + AI scanning, scoring.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ElytraSec <hello@elytrasec.io>",
|
|
7
7
|
"homepage": "https://elytrasec.io",
|