@elytrasec/engine 0.3.0 → 0.3.2
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.d.ts +65 -1
- package/dist/index.js +356 -36
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -218,6 +218,23 @@ declare const ReviewResultSchema: z.ZodObject<{
|
|
|
218
218
|
deducted: number;
|
|
219
219
|
}>>;
|
|
220
220
|
}>>;
|
|
221
|
+
/** keyed by "${ruleId}:${filePath}:${startLine}" — only present when runPoc=true */
|
|
222
|
+
pocs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
223
|
+
status: z.ZodEnum<["confirmed", "not-exploitable", "generated", "error"]>;
|
|
224
|
+
pocCode: z.ZodString;
|
|
225
|
+
output: z.ZodString;
|
|
226
|
+
rpcUrl: z.ZodOptional<z.ZodString>;
|
|
227
|
+
}, "strip", z.ZodTypeAny, {
|
|
228
|
+
status: "confirmed" | "not-exploitable" | "generated" | "error";
|
|
229
|
+
pocCode: string;
|
|
230
|
+
output: string;
|
|
231
|
+
rpcUrl?: string | undefined;
|
|
232
|
+
}, {
|
|
233
|
+
status: "confirmed" | "not-exploitable" | "generated" | "error";
|
|
234
|
+
pocCode: string;
|
|
235
|
+
output: string;
|
|
236
|
+
rpcUrl?: string | undefined;
|
|
237
|
+
}>>>;
|
|
221
238
|
}, "strip", z.ZodTypeAny, {
|
|
222
239
|
findings: {
|
|
223
240
|
severity: "critical" | "high" | "medium" | "low" | "info";
|
|
@@ -244,6 +261,12 @@ declare const ReviewResultSchema: z.ZodObject<{
|
|
|
244
261
|
deducted: number;
|
|
245
262
|
}>>;
|
|
246
263
|
} | undefined;
|
|
264
|
+
pocs?: Record<string, {
|
|
265
|
+
status: "confirmed" | "not-exploitable" | "generated" | "error";
|
|
266
|
+
pocCode: string;
|
|
267
|
+
output: string;
|
|
268
|
+
rpcUrl?: string | undefined;
|
|
269
|
+
}> | undefined;
|
|
247
270
|
}, {
|
|
248
271
|
findings: {
|
|
249
272
|
severity: "critical" | "high" | "medium" | "low" | "info";
|
|
@@ -270,6 +293,12 @@ declare const ReviewResultSchema: z.ZodObject<{
|
|
|
270
293
|
deducted: number;
|
|
271
294
|
}>>;
|
|
272
295
|
} | undefined;
|
|
296
|
+
pocs?: Record<string, {
|
|
297
|
+
status: "confirmed" | "not-exploitable" | "generated" | "error";
|
|
298
|
+
pocCode: string;
|
|
299
|
+
output: string;
|
|
300
|
+
rpcUrl?: string | undefined;
|
|
301
|
+
}> | undefined;
|
|
273
302
|
}>;
|
|
274
303
|
type ReviewResult = z.infer<typeof ReviewResultSchema>;
|
|
275
304
|
|
|
@@ -1213,6 +1242,14 @@ interface AnalyzeParams {
|
|
|
1213
1242
|
skipStatic?: boolean;
|
|
1214
1243
|
/** AI concurrency limit. Defaults to AI_CONCURRENCY env var or 3. */
|
|
1215
1244
|
aiConcurrency?: number;
|
|
1245
|
+
/**
|
|
1246
|
+
* Run agentic PoC generation on critical/high Solidity findings.
|
|
1247
|
+
* Requires an AI provider. If forge is installed, attempts to confirm
|
|
1248
|
+
* exploitability by running the generated test against Anvil.
|
|
1249
|
+
*/
|
|
1250
|
+
runPoc?: boolean;
|
|
1251
|
+
/** Optional RPC URL for forked Anvil PoC execution (e.g. Base/mainnet). */
|
|
1252
|
+
pocRpcUrl?: string;
|
|
1216
1253
|
}
|
|
1217
1254
|
/**
|
|
1218
1255
|
* Deduplicate findings by (filePath, startLine, ruleId).
|
|
@@ -1291,4 +1328,31 @@ interface DiscoverOptions {
|
|
|
1291
1328
|
*/
|
|
1292
1329
|
declare function discoverFiles(targetPath: string, opts?: DiscoverOptions): DiscoveredFile[];
|
|
1293
1330
|
|
|
1294
|
-
|
|
1331
|
+
/**
|
|
1332
|
+
* Agentic PoC generator.
|
|
1333
|
+
*
|
|
1334
|
+
* Given a Finding from our scanner and the vulnerable contract source,
|
|
1335
|
+
* asks Claude to write a Foundry exploit test, then tries to run it
|
|
1336
|
+
* against an Anvil fork to confirm the vulnerability is actually exploitable.
|
|
1337
|
+
*
|
|
1338
|
+
* Returns a PocResult indicating whether the finding was confirmed, along
|
|
1339
|
+
* with the generated test and execution output.
|
|
1340
|
+
*
|
|
1341
|
+
* Degrades gracefully when forge/anvil are not installed — the PoC code
|
|
1342
|
+
* is still generated, status is "generated" rather than "confirmed".
|
|
1343
|
+
*/
|
|
1344
|
+
|
|
1345
|
+
type PocStatus = "confirmed" | "not-exploitable" | "generated" | "error";
|
|
1346
|
+
interface PocResult {
|
|
1347
|
+
status: PocStatus;
|
|
1348
|
+
pocCode: string;
|
|
1349
|
+
output: string;
|
|
1350
|
+
rpcUrl?: string;
|
|
1351
|
+
}
|
|
1352
|
+
declare class PocGenerator {
|
|
1353
|
+
private provider;
|
|
1354
|
+
constructor(provider: AIProvider);
|
|
1355
|
+
generate(finding: Finding, contractSource: string, rpcUrl?: string): Promise<PocResult>;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
export { AIClient, type AICompleteParams, type AICompleteResult, type AIMessage, type AIProvider, type AnalyzeCodeParams, type AnalyzeFileParams, type AnalyzeParams, AnthropicProvider, type ApplyFixesResult, type AuditIssue, type AuditReport, CWE_OWASP_MAP, type Chain, type ChangeType, type CreateProviderOptions, type CweOwaspEntry, type DepInfo, type DepVulnerability, type DependencyReport, type DiffChange, type DiffChunk, type DiffFile, type DiffHunk, type DiscoverOptions, type DiscoveredFile, type Ecosystem, type ElytraConfig, type FileClassification, type FileFixInput, type FileFixResult, type FileStatus, type FileTransform, type Finding, FindingSchema, type Framework, type FullScanParams, type GitHubReviewComment, type HardenResult, type HardenSuggestion, type IssueCategory, type Language, MockProvider, OllamaProvider, OpenAIProvider, type ParsedDiff, type PatternRule, PocGenerator, type PocResult, type PocStatus, type ProjectFile, type ProjectSummary, type ReportOptions, type ReviewResult, ReviewResultSchema, type RewriteResult, type Rule, type SecurityScore, type SecurityScoreOutput, SecurityScoreSchema, type Severity, SeveritySchema, type StaticAnalysisOptions, type StaticAnalysisResult, type StaticFinding, type ToolRunner, type ToolRunnerOptions, type TransformResult, type UpgradeItem, type UpgradeOptions, type UpgradePlan, type UpgradeResult, type UpgradeType, ALL_RULES as _ALL_RULES, scanFile as _scanFile, analyze, analyzeDependencies, analyzeFullScan, apiRules, applyFixes, applyHardenFix, auditCodebase, authRules, bestPracticeRules, classifyFile, complexityRules, computeScore, createProvider, deadCodeRules, dedupeFindings, discoverFiles, filterByConfig, formatAsReviewComments, formatStaticFindingsForAI, gasRules, generalRules, generateFallbackPlan, generateMarkdownReport, generateUpgradePlan, getAllRules, getCweOwasp, getRulesForChains, getSystemPrompt, gitleaksRunner, ingestRepo, injectionRules, loadConfig, logger, namingRules, parseDiff, patternScannerRunner, readRepoFile, readRepoFiles, reconRules, rewriteFile, rewriteFiles, runHarden, runStaticAnalysis, runUpgrade, semgrepRunner, slitherRunner, solidityRules, splitIntoChunks, transformFiles, validateFixedSyntax };
|
package/dist/index.js
CHANGED
|
@@ -251,10 +251,18 @@ var SecurityScoreSchema = z.object({
|
|
|
251
251
|
z.object({ count: z.number(), deducted: z.number() })
|
|
252
252
|
)
|
|
253
253
|
});
|
|
254
|
+
var PocResultSchema = z.object({
|
|
255
|
+
status: z.enum(["confirmed", "not-exploitable", "generated", "error"]),
|
|
256
|
+
pocCode: z.string(),
|
|
257
|
+
output: z.string(),
|
|
258
|
+
rpcUrl: z.string().optional()
|
|
259
|
+
});
|
|
254
260
|
var ReviewResultSchema = z.object({
|
|
255
261
|
findings: z.array(FindingSchema),
|
|
256
262
|
summary: z.string(),
|
|
257
|
-
score: SecurityScoreSchema.optional()
|
|
263
|
+
score: SecurityScoreSchema.optional(),
|
|
264
|
+
/** keyed by "${ruleId}:${filePath}:${startLine}" — only present when runPoc=true */
|
|
265
|
+
pocs: z.record(PocResultSchema).optional()
|
|
258
266
|
});
|
|
259
267
|
|
|
260
268
|
// src/logger.ts
|
|
@@ -4603,6 +4611,112 @@ var iacRules = [
|
|
|
4603
4611
|
pathPattern: /\.github[/\\]workflows[/\\]/
|
|
4604
4612
|
}
|
|
4605
4613
|
];
|
|
4614
|
+
var eip7702Rules = [
|
|
4615
|
+
{
|
|
4616
|
+
id: "cp-sol-7702-unguarded-init",
|
|
4617
|
+
title: "EIP-7702 delegation: unguarded initializer",
|
|
4618
|
+
description: "Contracts used as EIP-7702 delegation targets must guard initializers \u2014 constructors don't run on delegated EOAs. An unguarded `initialize()` can be front-run, letting an attacker take ownership of the delegated account.",
|
|
4619
|
+
suggestion: "Add an `initializer` modifier (OpenZeppelin) or an `initialized` flag checked at the top of every init function. Ensure no init path is callable twice.",
|
|
4620
|
+
pattern: /function\s+initialize\s*\([^)]*\)\s*(?:external|public)(?!\s+\w*[Ii]nitializer)/,
|
|
4621
|
+
severity: "critical",
|
|
4622
|
+
category: "solidity",
|
|
4623
|
+
confidence: "medium",
|
|
4624
|
+
languages: SOL
|
|
4625
|
+
},
|
|
4626
|
+
{
|
|
4627
|
+
id: "cp-sol-7702-nonce-race",
|
|
4628
|
+
title: "EIP-7702 delegation: nonce race condition",
|
|
4629
|
+
description: "EIP-7702 authorization tuples share the EOA nonce space with regular transactions. A pending delegation can be invalidated or front-run by submitting a regular transaction that increments the nonce before the authorization is mined.",
|
|
4630
|
+
suggestion: "Always set `nonce` explicitly in authorization tuples. Do not rely on mempool ordering for security-sensitive delegations \u2014 use a commit-reveal or atomic multicall pattern.",
|
|
4631
|
+
pattern: /authorization\s*=\s*\{[^}]*nonce\s*:\s*0[^x]/i,
|
|
4632
|
+
severity: "high",
|
|
4633
|
+
category: "solidity",
|
|
4634
|
+
confidence: "medium",
|
|
4635
|
+
languages: SOL
|
|
4636
|
+
}
|
|
4637
|
+
];
|
|
4638
|
+
var tstoreRules = [
|
|
4639
|
+
{
|
|
4640
|
+
id: "cp-sol-tstore-reentrancy",
|
|
4641
|
+
title: "Transient storage (TSTORE) used \u2014 verify reentrancy safety",
|
|
4642
|
+
description: "Transient storage (EIP-1153, live since Cancun) does not impose the 2300 gas minimum of SSTORE-based guards. If this contract also uses `transfer()` or `send()` for ETH, those calls are no longer reentrancy-safe \u2014 the callee has enough gas to re-enter via TLOAD/TSTORE paths.",
|
|
4643
|
+
suggestion: 'Audit every ETH transfer in this contract. Replace `transfer()`/`send()` with `call{value:}("")` + explicit reentrancy check. Ensure TSTORE lock is written before any external call.',
|
|
4644
|
+
pattern: /assembly\s*\{[^}]*tstore\s*\(|TransientSlot|tstore\s*\(/i,
|
|
4645
|
+
severity: "high",
|
|
4646
|
+
category: "solidity",
|
|
4647
|
+
confidence: "medium",
|
|
4648
|
+
languages: SOL
|
|
4649
|
+
},
|
|
4650
|
+
{
|
|
4651
|
+
id: "cp-sol-tstore-lock-pattern",
|
|
4652
|
+
title: "Transient storage lock: verify all entry points are guarded",
|
|
4653
|
+
description: "TSTORE-based reentrancy locks reset at transaction end (not call end). A lock set in `functionA` does NOT protect `functionB` called in a separate transaction, and a lock set mid-call does NOT protect earlier entry points in the same call chain.",
|
|
4654
|
+
suggestion: "Apply the TSTORE lock modifier to every external/public function that modifies state, not just the function that initiates the ETH transfer.",
|
|
4655
|
+
pattern: /assembly\s*\{[^}]*tload\s*\([^}]*\)[^}]*tstore\s*\(/is,
|
|
4656
|
+
severity: "medium",
|
|
4657
|
+
category: "solidity",
|
|
4658
|
+
confidence: "low",
|
|
4659
|
+
languages: SOL
|
|
4660
|
+
}
|
|
4661
|
+
];
|
|
4662
|
+
var uniswapV4Rules = [
|
|
4663
|
+
{
|
|
4664
|
+
id: "cp-sol-v4hook-missing-sender-check",
|
|
4665
|
+
title: "Uniswap v4 hook: missing PoolManager sender validation",
|
|
4666
|
+
description: "Hook callbacks (`beforeSwap`, `afterSwap`, `beforeAddLiquidity`, etc.) must verify `msg.sender == address(poolManager)`. Without this check, any address can call the hook directly and manipulate its state.",
|
|
4667
|
+
suggestion: 'Add `require(msg.sender == address(poolManager), "not PoolManager");` as the first line of every hook callback, or inherit from `BaseHook` which enforces this automatically.',
|
|
4668
|
+
pattern: /function\s+(?:before|after)(?:Swap|AddLiquidity|RemoveLiquidity|Initialize|Donate)\s*\(/,
|
|
4669
|
+
severity: "critical",
|
|
4670
|
+
category: "solidity",
|
|
4671
|
+
confidence: "medium",
|
|
4672
|
+
languages: SOL
|
|
4673
|
+
},
|
|
4674
|
+
{
|
|
4675
|
+
id: "cp-sol-v4hook-reentrancy-callback",
|
|
4676
|
+
title: "Uniswap v4 hook: external call inside hook callback",
|
|
4677
|
+
description: "Making external calls from within a hook callback (beforeSwap, afterSwap, etc.) while the PoolManager lock is held can trigger reentrancy into the pool. The singleton PoolManager uses a transient-storage lock that is bypassable via hooks that call back into the pool.",
|
|
4678
|
+
suggestion: "Avoid external calls in hook callbacks. If you must call out, use the `unlockCallback` pattern to queue actions and execute them after the current lock cycle completes.",
|
|
4679
|
+
pattern: /function\s+(?:before|after)(?:Swap|AddLiquidity|RemoveLiquidity|Initialize|Donate)[\s\S]{0,500}?\.call\s*\{/,
|
|
4680
|
+
multilinePattern: /function\s+(before|after)(Swap|AddLiquidity|RemoveLiquidity|Initialize|Donate)\s*\([^)]*\)[^{]*\{[\s\S]{0,800}?(?:\.call\s*\{|\.transfer\s*\(|\.send\s*\()/gm,
|
|
4681
|
+
severity: "critical",
|
|
4682
|
+
category: "solidity",
|
|
4683
|
+
confidence: "medium",
|
|
4684
|
+
languages: SOL
|
|
4685
|
+
},
|
|
4686
|
+
{
|
|
4687
|
+
id: "cp-sol-v4hook-delta-not-consumed",
|
|
4688
|
+
title: "Uniswap v4 hook: BalanceDelta returned without settle/take",
|
|
4689
|
+
description: "Hooks that return a modified `BalanceDelta` must ensure the delta is fully consumed (settled or taken) within the same unlock cycle. An unconsumed delta causes `CurrencyNotSettled` revert; partial consumption can silently strand tokens.",
|
|
4690
|
+
suggestion: "Ensure `poolManager.settle()` or `poolManager.take()` is called for both currency0 and currency1 before returning from the unlock callback.",
|
|
4691
|
+
pattern: /returns\s*\([^)]*BalanceDelta[^)]*\)(?![\s\S]{0,800}?(?:settle|\.take)\s*\()/,
|
|
4692
|
+
severity: "high",
|
|
4693
|
+
category: "solidity",
|
|
4694
|
+
confidence: "low",
|
|
4695
|
+
languages: SOL
|
|
4696
|
+
},
|
|
4697
|
+
{
|
|
4698
|
+
id: "cp-sol-v4hook-unrestricted-permissions",
|
|
4699
|
+
title: "Uniswap v4 hook: overly broad hook permissions",
|
|
4700
|
+
description: "Hook permissions are set at deployment via the address bit-flags and cannot be changed. Requesting permissions you don't need (e.g. `BEFORE_SWAP_RETURNS_DELTA` when you never return a delta) increases attack surface and gas costs for every pool interaction.",
|
|
4701
|
+
suggestion: "Return only the minimum required `Hooks.Permissions` from `getHookPermissions()`. Audit each permission flag against your actual callback implementations.",
|
|
4702
|
+
pattern: /getHookPermissions\s*\(\s*\)\s*(?:public|external|override)[^{]*\{[\s\S]{0,300}?true/,
|
|
4703
|
+
severity: "medium",
|
|
4704
|
+
category: "solidity",
|
|
4705
|
+
confidence: "low",
|
|
4706
|
+
languages: SOL
|
|
4707
|
+
},
|
|
4708
|
+
{
|
|
4709
|
+
id: "cp-sol-v4hook-sqrt-price-manipulation",
|
|
4710
|
+
title: "Uniswap v4 hook: spot sqrtPriceX96 used for pricing without limit",
|
|
4711
|
+
description: "Using spot `sqrtPriceX96` read from pool state inside a hook callback for pricing decisions without a `sqrtPriceLimitX96` bound is sandwich-attackable. The price reflects post-swap state which may be manipulated.",
|
|
4712
|
+
suggestion: "Pass `sqrtPriceLimitX96` to all swap calls from hooks. Use a TWAP oracle for pricing rather than spot price.",
|
|
4713
|
+
pattern: /sqrtPriceX96\s*[*/+-]\s*\w|sqrtPriceX96\s*=\s*(?!.*[Ll]imit)/,
|
|
4714
|
+
severity: "high",
|
|
4715
|
+
category: "solidity",
|
|
4716
|
+
confidence: "low",
|
|
4717
|
+
languages: SOL
|
|
4718
|
+
}
|
|
4719
|
+
];
|
|
4606
4720
|
var ALL_RULES2 = [
|
|
4607
4721
|
...securityRules,
|
|
4608
4722
|
...solidityRules2,
|
|
@@ -4613,7 +4727,10 @@ var ALL_RULES2 = [
|
|
|
4613
4727
|
...performanceRules,
|
|
4614
4728
|
...cleaningRules,
|
|
4615
4729
|
...reactRules,
|
|
4616
|
-
...iacRules
|
|
4730
|
+
...iacRules,
|
|
4731
|
+
...eip7702Rules,
|
|
4732
|
+
...tstoreRules,
|
|
4733
|
+
...uniswapV4Rules
|
|
4617
4734
|
];
|
|
4618
4735
|
|
|
4619
4736
|
// src/static/pattern-scanner.ts
|
|
@@ -5015,6 +5132,175 @@ var patternScannerRunner = {
|
|
|
5015
5132
|
}
|
|
5016
5133
|
};
|
|
5017
5134
|
|
|
5135
|
+
// src/ai/poc-generator.ts
|
|
5136
|
+
import { execFile as execFile4 } from "child_process";
|
|
5137
|
+
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
5138
|
+
import { promisify as promisify4 } from "util";
|
|
5139
|
+
import { tmpdir } from "os";
|
|
5140
|
+
import { join } from "path";
|
|
5141
|
+
import { randomBytes } from "crypto";
|
|
5142
|
+
var exec4 = promisify4(execFile4);
|
|
5143
|
+
var SYSTEM_PROMPT = `You are an expert smart contract security researcher who writes Foundry (forge) exploit tests to demonstrate vulnerabilities.
|
|
5144
|
+
|
|
5145
|
+
Given a security finding and the vulnerable contract source code, write a minimal Foundry test that:
|
|
5146
|
+
1. Deploys the vulnerable contract (or a minimal reproduction of the vulnerable pattern)
|
|
5147
|
+
2. Demonstrates the exploit in a test function named test_exploit()
|
|
5148
|
+
3. Uses vm.expectRevert() or assertions to show the vulnerability is real
|
|
5149
|
+
4. Includes a brief comment explaining the attack vector
|
|
5150
|
+
|
|
5151
|
+
Rules:
|
|
5152
|
+
- Use Solidity ^0.8.20 and Foundry's Test base
|
|
5153
|
+
- Import only from forge-std (available: Test, console, Vm, StdCheats)
|
|
5154
|
+
- If the contract needs an RPC fork (e.g. for on-chain state), emit a // @fork-required comment on line 1
|
|
5155
|
+
- Keep it minimal \u2014 prove the bug, nothing else
|
|
5156
|
+
- Return ONLY valid Solidity, no markdown fences, no explanation outside comments
|
|
5157
|
+
|
|
5158
|
+
Format:
|
|
5159
|
+
\`\`\`solidity
|
|
5160
|
+
// SPDX-License-Identifier: MIT
|
|
5161
|
+
pragma solidity ^0.8.20;
|
|
5162
|
+
|
|
5163
|
+
import "forge-std/Test.sol";
|
|
5164
|
+
|
|
5165
|
+
contract ExploitTest is Test {
|
|
5166
|
+
// ...setup...
|
|
5167
|
+
|
|
5168
|
+
function test_exploit() public {
|
|
5169
|
+
// ...exploit...
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5172
|
+
\`\`\``;
|
|
5173
|
+
function buildUserMessage(finding, contractSource) {
|
|
5174
|
+
return `## Vulnerability Finding
|
|
5175
|
+
|
|
5176
|
+
Rule ID: ${finding.ruleId}
|
|
5177
|
+
Title: ${finding.title}
|
|
5178
|
+
Severity: ${finding.severity}
|
|
5179
|
+
File: ${finding.filePath} (lines ${finding.startLine}\u2013${finding.endLine})
|
|
5180
|
+
Description: ${finding.description}
|
|
5181
|
+
|
|
5182
|
+
Vulnerable code snippet:
|
|
5183
|
+
\`\`\`solidity
|
|
5184
|
+
${finding.codeSnippet}
|
|
5185
|
+
\`\`\`
|
|
5186
|
+
|
|
5187
|
+
Fix suggestion: ${finding.suggestion}
|
|
5188
|
+
|
|
5189
|
+
## Full Contract Source
|
|
5190
|
+
|
|
5191
|
+
\`\`\`solidity
|
|
5192
|
+
${contractSource}
|
|
5193
|
+
\`\`\`
|
|
5194
|
+
|
|
5195
|
+
Write the Foundry exploit test now.`;
|
|
5196
|
+
}
|
|
5197
|
+
function extractSolidity(text) {
|
|
5198
|
+
const fenceMatch = text.match(/```(?:solidity)?\s*([\s\S]*?)```/);
|
|
5199
|
+
if (fenceMatch) return fenceMatch[1].trim();
|
|
5200
|
+
return text.trim();
|
|
5201
|
+
}
|
|
5202
|
+
async function forgeAvailable() {
|
|
5203
|
+
try {
|
|
5204
|
+
await exec4("forge", ["--version"], { timeout: 5e3 });
|
|
5205
|
+
return true;
|
|
5206
|
+
} catch {
|
|
5207
|
+
return false;
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5210
|
+
async function runForgeTest(pocCode, rpcUrl) {
|
|
5211
|
+
const id = randomBytes(6).toString("hex");
|
|
5212
|
+
const dir = join(tmpdir(), `elytra-poc-${id}`);
|
|
5213
|
+
const srcDir = join(dir, "src");
|
|
5214
|
+
const testDir = join(dir, "test");
|
|
5215
|
+
const libDir = join(dir, "lib");
|
|
5216
|
+
try {
|
|
5217
|
+
await mkdir2(srcDir, { recursive: true });
|
|
5218
|
+
await mkdir2(testDir, { recursive: true });
|
|
5219
|
+
await mkdir2(libDir, { recursive: true });
|
|
5220
|
+
await writeFile2(
|
|
5221
|
+
join(dir, "foundry.toml"),
|
|
5222
|
+
`[profile.default]
|
|
5223
|
+
src = "src"
|
|
5224
|
+
out = "out"
|
|
5225
|
+
libs = ["lib"]
|
|
5226
|
+
`
|
|
5227
|
+
);
|
|
5228
|
+
const testFile = join(testDir, "Exploit.t.sol");
|
|
5229
|
+
await writeFile2(testFile, pocCode);
|
|
5230
|
+
const args = ["test", "--match-contract", "ExploitTest", "--no-match-coverage", "-vv"];
|
|
5231
|
+
if (rpcUrl) {
|
|
5232
|
+
args.push("--fork-url", rpcUrl);
|
|
5233
|
+
}
|
|
5234
|
+
const { stdout, stderr } = await exec4("forge", args, {
|
|
5235
|
+
cwd: dir,
|
|
5236
|
+
timeout: 6e4,
|
|
5237
|
+
env: { ...process.env, FOUNDRY_PROFILE: "default" }
|
|
5238
|
+
});
|
|
5239
|
+
const output = [stdout, stderr].filter(Boolean).join("\n");
|
|
5240
|
+
const passed = output.includes("PASS") || output.includes("ok");
|
|
5241
|
+
return { success: passed, output };
|
|
5242
|
+
} catch (err) {
|
|
5243
|
+
const output = err.stdout ?? err.stderr ?? String(err);
|
|
5244
|
+
return { success: false, output };
|
|
5245
|
+
} finally {
|
|
5246
|
+
exec4("rm", ["-rf", dir]).catch(() => {
|
|
5247
|
+
});
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
var PocGenerator = class {
|
|
5251
|
+
provider;
|
|
5252
|
+
constructor(provider) {
|
|
5253
|
+
this.provider = provider;
|
|
5254
|
+
}
|
|
5255
|
+
async generate(finding, contractSource, rpcUrl) {
|
|
5256
|
+
if (finding.category !== "solidity" && !finding.ruleId.startsWith("cp-sol")) {
|
|
5257
|
+
return {
|
|
5258
|
+
status: "error",
|
|
5259
|
+
pocCode: "",
|
|
5260
|
+
output: "PoC generation only supported for Solidity findings"
|
|
5261
|
+
};
|
|
5262
|
+
}
|
|
5263
|
+
let pocCode = "";
|
|
5264
|
+
try {
|
|
5265
|
+
const response = await this.provider.complete({
|
|
5266
|
+
messages: [
|
|
5267
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
5268
|
+
{ role: "user", content: buildUserMessage(finding, contractSource) }
|
|
5269
|
+
],
|
|
5270
|
+
maxTokens: 4096
|
|
5271
|
+
});
|
|
5272
|
+
pocCode = extractSolidity(response.text);
|
|
5273
|
+
if (!pocCode) {
|
|
5274
|
+
return { status: "error", pocCode: "", output: "AI returned empty response" };
|
|
5275
|
+
}
|
|
5276
|
+
} catch (err) {
|
|
5277
|
+
logger.error(`PocGenerator: AI call failed: ${err}`);
|
|
5278
|
+
return { status: "error", pocCode: "", output: String(err) };
|
|
5279
|
+
}
|
|
5280
|
+
const hasForge = await forgeAvailable();
|
|
5281
|
+
if (!hasForge) {
|
|
5282
|
+
logger.info("PocGenerator: forge not available, returning generated code only");
|
|
5283
|
+
return {
|
|
5284
|
+
status: "generated",
|
|
5285
|
+
pocCode,
|
|
5286
|
+
output: "forge not installed \u2014 PoC generated but not executed",
|
|
5287
|
+
rpcUrl
|
|
5288
|
+
};
|
|
5289
|
+
}
|
|
5290
|
+
const forkRequired = pocCode.includes("@fork-required");
|
|
5291
|
+
const { success, output } = await runForgeTest(
|
|
5292
|
+
pocCode,
|
|
5293
|
+
forkRequired ? rpcUrl : void 0
|
|
5294
|
+
);
|
|
5295
|
+
return {
|
|
5296
|
+
status: success ? "confirmed" : "not-exploitable",
|
|
5297
|
+
pocCode,
|
|
5298
|
+
output,
|
|
5299
|
+
rpcUrl: forkRequired ? rpcUrl : void 0
|
|
5300
|
+
};
|
|
5301
|
+
}
|
|
5302
|
+
};
|
|
5303
|
+
|
|
5018
5304
|
// src/analyzer.ts
|
|
5019
5305
|
var SEVERITY_ORDER2 = {
|
|
5020
5306
|
critical: 0,
|
|
@@ -5119,7 +5405,9 @@ async function analyze(params) {
|
|
|
5119
5405
|
repoPath,
|
|
5120
5406
|
staticOnly = false,
|
|
5121
5407
|
skipStatic = false,
|
|
5122
|
-
aiConcurrency
|
|
5408
|
+
aiConcurrency,
|
|
5409
|
+
runPoc = false,
|
|
5410
|
+
pocRpcUrl
|
|
5123
5411
|
} = params;
|
|
5124
5412
|
const parsed = parseDiff(diff);
|
|
5125
5413
|
if (parsed.files.length === 0) {
|
|
@@ -5237,7 +5525,38 @@ async function analyze(params) {
|
|
|
5237
5525
|
const enriched = enrichFindings(sorted);
|
|
5238
5526
|
const score = computeScore(enriched, parsed.files.length);
|
|
5239
5527
|
const summary = buildSummary(enriched, parsed.files.length, staticResult?.toolsRan);
|
|
5240
|
-
|
|
5528
|
+
let pocs;
|
|
5529
|
+
if (runPoc && (provider || apiKey)) {
|
|
5530
|
+
const pocProvider = provider ?? {
|
|
5531
|
+
name: "anthropic-poc",
|
|
5532
|
+
async complete(p) {
|
|
5533
|
+
const { AnthropicProvider: AnthropicProvider2 } = await import("./anthropic-PRRF4E3I.js");
|
|
5534
|
+
return new AnthropicProvider2(apiKey).complete(p);
|
|
5535
|
+
}
|
|
5536
|
+
};
|
|
5537
|
+
const pocGen = new PocGenerator(pocProvider);
|
|
5538
|
+
const solFindings = enriched.filter(
|
|
5539
|
+
(f) => (f.severity === "critical" || f.severity === "high") && (f.category === "solidity" || f.ruleId.startsWith("cp-sol"))
|
|
5540
|
+
);
|
|
5541
|
+
const fileContents = /* @__PURE__ */ new Map();
|
|
5542
|
+
for (const file of parsed.files) {
|
|
5543
|
+
fileContents.set(file.path, reconstructContent(file));
|
|
5544
|
+
}
|
|
5545
|
+
pocs = {};
|
|
5546
|
+
await Promise.all(
|
|
5547
|
+
solFindings.map(async (finding) => {
|
|
5548
|
+
const src = fileContents.get(finding.filePath) ?? finding.codeSnippet;
|
|
5549
|
+
const key = `${finding.ruleId}:${finding.filePath}:${finding.startLine}`;
|
|
5550
|
+
try {
|
|
5551
|
+
pocs[key] = await pocGen.generate(finding, src, pocRpcUrl);
|
|
5552
|
+
} catch (err) {
|
|
5553
|
+
logger.error(`[analyzer] PoC generation failed for ${key}: ${err}`);
|
|
5554
|
+
pocs[key] = { status: "error", pocCode: "", output: String(err) };
|
|
5555
|
+
}
|
|
5556
|
+
})
|
|
5557
|
+
);
|
|
5558
|
+
}
|
|
5559
|
+
return { findings: enriched, summary, score, ...pocs ? { pocs } : {} };
|
|
5241
5560
|
}
|
|
5242
5561
|
function enrichFindings(findings) {
|
|
5243
5562
|
return findings.map((f) => {
|
|
@@ -5373,12 +5692,12 @@ function formatStaticFindingsForAI(rawFindings) {
|
|
|
5373
5692
|
import { rm as rm2 } from "fs/promises";
|
|
5374
5693
|
|
|
5375
5694
|
// src/upgrade/ingest.ts
|
|
5376
|
-
import { execFile as
|
|
5377
|
-
import { promisify as
|
|
5695
|
+
import { execFile as execFile5 } from "child_process";
|
|
5696
|
+
import { promisify as promisify5 } from "util";
|
|
5378
5697
|
import { readdir, stat, readFile as readFile2, mkdtemp } from "fs/promises";
|
|
5379
5698
|
import path4 from "path";
|
|
5380
5699
|
import os2 from "os";
|
|
5381
|
-
var
|
|
5700
|
+
var exec5 = promisify5(execFile5);
|
|
5382
5701
|
var EXT_TO_LANG = {
|
|
5383
5702
|
".ts": "typescript",
|
|
5384
5703
|
".tsx": "typescript",
|
|
@@ -5599,7 +5918,7 @@ async function ingestRepo(repoUrl, branch) {
|
|
|
5599
5918
|
if (branch) {
|
|
5600
5919
|
cloneArgs.splice(2, 0, "--branch", branch);
|
|
5601
5920
|
}
|
|
5602
|
-
await
|
|
5921
|
+
await exec5("git", cloneArgs, {
|
|
5603
5922
|
timeout: 12e4,
|
|
5604
5923
|
maxBuffer: 200 * 1024 * 1024
|
|
5605
5924
|
});
|
|
@@ -5661,11 +5980,11 @@ async function readRepoFiles(repoPath, filePaths, maxTotalSize = 5e5) {
|
|
|
5661
5980
|
}
|
|
5662
5981
|
|
|
5663
5982
|
// src/upgrade/deps.ts
|
|
5664
|
-
import { execFile as
|
|
5665
|
-
import { promisify as
|
|
5983
|
+
import { execFile as execFile6 } from "child_process";
|
|
5984
|
+
import { promisify as promisify6 } from "util";
|
|
5666
5985
|
import { readFile as readFile3, access } from "fs/promises";
|
|
5667
5986
|
import path5 from "path";
|
|
5668
|
-
var
|
|
5987
|
+
var exec6 = promisify6(execFile6);
|
|
5669
5988
|
var DEPRECATED_REPLACEMENTS = {
|
|
5670
5989
|
"moment": "dayjs or date-fns",
|
|
5671
5990
|
"request": "node-fetch or axios or undici",
|
|
@@ -5719,7 +6038,7 @@ async function analyzeNodeDeps(repoPath) {
|
|
|
5719
6038
|
let vulnerableCount = 0;
|
|
5720
6039
|
let auditResults = {};
|
|
5721
6040
|
try {
|
|
5722
|
-
const { stdout } = await
|
|
6041
|
+
const { stdout } = await exec6("npm", ["audit", "--json"], {
|
|
5723
6042
|
cwd: repoPath,
|
|
5724
6043
|
timeout: 6e4,
|
|
5725
6044
|
maxBuffer: 20 * 1024 * 1024
|
|
@@ -5744,7 +6063,7 @@ async function analyzeNodeDeps(repoPath) {
|
|
|
5744
6063
|
}
|
|
5745
6064
|
let outdatedResults = {};
|
|
5746
6065
|
try {
|
|
5747
|
-
const { stdout } = await
|
|
6066
|
+
const { stdout } = await exec6("npm", ["outdated", "--json"], {
|
|
5748
6067
|
cwd: repoPath,
|
|
5749
6068
|
timeout: 3e4,
|
|
5750
6069
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -5804,7 +6123,7 @@ async function analyzeRustDeps(repoPath) {
|
|
|
5804
6123
|
const deps = [];
|
|
5805
6124
|
let vulnerableCount = 0;
|
|
5806
6125
|
try {
|
|
5807
|
-
const { stdout } = await
|
|
6126
|
+
const { stdout } = await exec6("cargo", ["audit", "--json"], {
|
|
5808
6127
|
cwd: repoPath,
|
|
5809
6128
|
timeout: 6e4,
|
|
5810
6129
|
maxBuffer: 20 * 1024 * 1024
|
|
@@ -5862,7 +6181,7 @@ async function analyzePythonDeps(repoPath) {
|
|
|
5862
6181
|
const deps = [];
|
|
5863
6182
|
let vulnerableCount = 0;
|
|
5864
6183
|
try {
|
|
5865
|
-
const { stdout } = await
|
|
6184
|
+
const { stdout } = await exec6(
|
|
5866
6185
|
"pip-audit",
|
|
5867
6186
|
["-r", path5.join(repoPath, "requirements.txt"), "--format", "json"],
|
|
5868
6187
|
{ cwd: repoPath, timeout: 6e4, maxBuffer: 20 * 1024 * 1024 }
|
|
@@ -5944,10 +6263,10 @@ async function analyzeDependencies(repoPath, ecosystems) {
|
|
|
5944
6263
|
}
|
|
5945
6264
|
|
|
5946
6265
|
// src/upgrade/audit.ts
|
|
5947
|
-
import { execFile as
|
|
5948
|
-
import { promisify as
|
|
6266
|
+
import { execFile as execFile7 } from "child_process";
|
|
6267
|
+
import { promisify as promisify7 } from "util";
|
|
5949
6268
|
import path6 from "path";
|
|
5950
|
-
var
|
|
6269
|
+
var exec7 = promisify7(execFile7);
|
|
5951
6270
|
async function runSlitherFullScan(repoPath, solFiles) {
|
|
5952
6271
|
if (solFiles.length === 0) return [];
|
|
5953
6272
|
const IMPACT_MAP2 = {
|
|
@@ -5960,7 +6279,7 @@ async function runSlitherFullScan(repoPath, solFiles) {
|
|
|
5960
6279
|
try {
|
|
5961
6280
|
let stdout;
|
|
5962
6281
|
try {
|
|
5963
|
-
const result = await
|
|
6282
|
+
const result = await exec7("slither", [repoPath, "--json", "-", "--no-fail"], {
|
|
5964
6283
|
timeout: 18e4,
|
|
5965
6284
|
maxBuffer: 50 * 1024 * 1024,
|
|
5966
6285
|
cwd: repoPath
|
|
@@ -6001,7 +6320,7 @@ async function runSemgrepFullScan(repoPath) {
|
|
|
6001
6320
|
try {
|
|
6002
6321
|
let stdout;
|
|
6003
6322
|
try {
|
|
6004
|
-
const result = await
|
|
6323
|
+
const result = await exec7(
|
|
6005
6324
|
"semgrep",
|
|
6006
6325
|
["--config", "p/security-audit", "--json", "--timeout", "30", repoPath],
|
|
6007
6326
|
{ timeout: 18e4, maxBuffer: 50 * 1024 * 1024, cwd: repoPath }
|
|
@@ -6032,7 +6351,7 @@ async function runGitleaksFullScan(repoPath) {
|
|
|
6032
6351
|
try {
|
|
6033
6352
|
let stdout;
|
|
6034
6353
|
try {
|
|
6035
|
-
const result = await
|
|
6354
|
+
const result = await exec7(
|
|
6036
6355
|
"gitleaks",
|
|
6037
6356
|
["detect", "--source", repoPath, "--report-format", "json", "--report-path", "/dev/stdout", "--no-git", "--no-banner"],
|
|
6038
6357
|
{ timeout: 6e4, maxBuffer: 20 * 1024 * 1024, cwd: repoPath }
|
|
@@ -6858,7 +7177,7 @@ async function rewriteFiles(params) {
|
|
|
6858
7177
|
|
|
6859
7178
|
// src/config.ts
|
|
6860
7179
|
import { readFileSync, existsSync } from "fs";
|
|
6861
|
-
import { resolve, join } from "path";
|
|
7180
|
+
import { resolve, join as join2 } from "path";
|
|
6862
7181
|
import yaml from "js-yaml";
|
|
6863
7182
|
import { z as z3 } from "zod";
|
|
6864
7183
|
var DEFAULT_CONFIG = {
|
|
@@ -6901,7 +7220,7 @@ function levenshtein(a, b) {
|
|
|
6901
7220
|
return dp[m][n];
|
|
6902
7221
|
}
|
|
6903
7222
|
function loadConfig(dir) {
|
|
6904
|
-
const configPath = resolve(
|
|
7223
|
+
const configPath = resolve(join2(dir, ".elytra.yml"));
|
|
6905
7224
|
if (!existsSync(configPath)) return null;
|
|
6906
7225
|
let raw;
|
|
6907
7226
|
try {
|
|
@@ -6996,7 +7315,7 @@ function filterByConfig(findings, config) {
|
|
|
6996
7315
|
|
|
6997
7316
|
// src/harden/harden.ts
|
|
6998
7317
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
6999
|
-
import { join as
|
|
7318
|
+
import { join as join3, relative } from "path";
|
|
7000
7319
|
function readJson(filePath) {
|
|
7001
7320
|
try {
|
|
7002
7321
|
return JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
@@ -7015,7 +7334,7 @@ function walkFiles(dir, exts, maxDepth = 6, depth = 0) {
|
|
|
7015
7334
|
}
|
|
7016
7335
|
for (const entry of entries) {
|
|
7017
7336
|
if (entry === "node_modules" || entry === ".git" || entry === "dist" || entry === ".next") continue;
|
|
7018
|
-
const full =
|
|
7337
|
+
const full = join3(dir, entry);
|
|
7019
7338
|
let stat2;
|
|
7020
7339
|
try {
|
|
7021
7340
|
stat2 = statSync(full);
|
|
@@ -7062,7 +7381,7 @@ function checkHelmet(projectPath, sourceFiles, frameworks) {
|
|
|
7062
7381
|
if (!frameworks.includes("express")) return null;
|
|
7063
7382
|
const helmetFile = anyFileContains(sourceFiles, /\bhelmet\s*\(/);
|
|
7064
7383
|
if (helmetFile) return null;
|
|
7065
|
-
const pkgJson = readJson(
|
|
7384
|
+
const pkgJson = readJson(join3(projectPath, "package.json"));
|
|
7066
7385
|
const deps = {
|
|
7067
7386
|
...pkgJson?.dependencies ?? {},
|
|
7068
7387
|
...pkgJson?.devDependencies ?? {}
|
|
@@ -7203,9 +7522,9 @@ app.use(cors({
|
|
|
7203
7522
|
return null;
|
|
7204
7523
|
}
|
|
7205
7524
|
function checkEnvLeakage(projectPath) {
|
|
7206
|
-
const gitignorePath =
|
|
7525
|
+
const gitignorePath = join3(projectPath, ".gitignore");
|
|
7207
7526
|
if (!existsSync2(gitignorePath)) {
|
|
7208
|
-
const hasEnvFile = existsSync2(
|
|
7527
|
+
const hasEnvFile = existsSync2(join3(projectPath, ".env")) || existsSync2(join3(projectPath, ".env.local"));
|
|
7209
7528
|
if (!hasEnvFile) return null;
|
|
7210
7529
|
return {
|
|
7211
7530
|
id: "harden-env-no-gitignore",
|
|
@@ -7224,7 +7543,7 @@ function checkEnvLeakage(projectPath) {
|
|
|
7224
7543
|
const gitignore = readFileSync2(gitignorePath, "utf-8");
|
|
7225
7544
|
const hasEnvRule = /^\.env$/m.test(gitignore) || /^\.env\.\*$/m.test(gitignore) || /^\.env\*$/m.test(gitignore) || /^\.env\.local$/m.test(gitignore);
|
|
7226
7545
|
if (hasEnvRule) return null;
|
|
7227
|
-
const hasEnvFile = existsSync2(
|
|
7546
|
+
const hasEnvFile = existsSync2(join3(projectPath, ".env")) || existsSync2(join3(projectPath, ".env.local")) || existsSync2(join3(projectPath, ".env.production"));
|
|
7228
7547
|
if (!hasEnvFile) return null;
|
|
7229
7548
|
return {
|
|
7230
7549
|
id: "harden-env-not-gitignored",
|
|
@@ -7332,7 +7651,7 @@ res.cookie("token", value, {
|
|
|
7332
7651
|
};
|
|
7333
7652
|
}
|
|
7334
7653
|
function checkTsStrict(projectPath) {
|
|
7335
|
-
const tsconfigPath =
|
|
7654
|
+
const tsconfigPath = join3(projectPath, "tsconfig.json");
|
|
7336
7655
|
if (!existsSync2(tsconfigPath)) return null;
|
|
7337
7656
|
const tsconfig = readJson(tsconfigPath);
|
|
7338
7657
|
if (!tsconfig) return null;
|
|
@@ -7352,7 +7671,7 @@ function checkTsStrict(projectPath) {
|
|
|
7352
7671
|
function applyHardenFix(projectPath, suggestion) {
|
|
7353
7672
|
if (!suggestion.autoFixable) return false;
|
|
7354
7673
|
if (suggestion.id === "harden-ts-no-strict") {
|
|
7355
|
-
const tsconfigPath =
|
|
7674
|
+
const tsconfigPath = join3(projectPath, "tsconfig.json");
|
|
7356
7675
|
const tsconfig = readJson(tsconfigPath);
|
|
7357
7676
|
if (!tsconfig) return false;
|
|
7358
7677
|
const compilerOptions = tsconfig.compilerOptions ?? {};
|
|
@@ -7368,7 +7687,7 @@ function applyHardenFix(projectPath, suggestion) {
|
|
|
7368
7687
|
return false;
|
|
7369
7688
|
}
|
|
7370
7689
|
function runHarden(projectPath) {
|
|
7371
|
-
const pkgJsonPath =
|
|
7690
|
+
const pkgJsonPath = join3(projectPath, "package.json");
|
|
7372
7691
|
const pkgJson = readJson(pkgJsonPath);
|
|
7373
7692
|
if (!pkgJson) {
|
|
7374
7693
|
return { suggestions: [], projectPath, frameworksDetected: [] };
|
|
@@ -7479,7 +7798,7 @@ Respond with a valid JSON object:
|
|
|
7479
7798
|
// src/full-scan-discovery.ts
|
|
7480
7799
|
import { execSync } from "child_process";
|
|
7481
7800
|
import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
7482
|
-
import { join as
|
|
7801
|
+
import { join as join4, relative as relative2, extname } from "path";
|
|
7483
7802
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
7484
7803
|
".ts",
|
|
7485
7804
|
".tsx",
|
|
@@ -7608,7 +7927,7 @@ function discoverViaGit(targetPath, maxFileSizeKB) {
|
|
|
7608
7927
|
for (const line of raw.split("\n")) {
|
|
7609
7928
|
const rel = line.trim();
|
|
7610
7929
|
if (!rel || !isSourceFile(rel)) continue;
|
|
7611
|
-
const abs =
|
|
7930
|
+
const abs = join4(targetPath, rel);
|
|
7612
7931
|
try {
|
|
7613
7932
|
const stat2 = statSync2(abs);
|
|
7614
7933
|
if (!stat2.isFile()) continue;
|
|
@@ -7640,9 +7959,9 @@ function discoverViaWalk(targetPath, maxFileSizeKB) {
|
|
|
7640
7959
|
if (entry.isDirectory()) {
|
|
7641
7960
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
7642
7961
|
if (entry.name.startsWith(".") && !ALLOWED_HIDDEN_DIRS.has(entry.name)) continue;
|
|
7643
|
-
walk(
|
|
7962
|
+
walk(join4(dir, entry.name));
|
|
7644
7963
|
} else if (entry.isFile()) {
|
|
7645
|
-
const abs =
|
|
7964
|
+
const abs = join4(dir, entry.name);
|
|
7646
7965
|
const rel = relative2(targetPath, abs);
|
|
7647
7966
|
if (!isSourceFile(rel)) continue;
|
|
7648
7967
|
try {
|
|
@@ -7922,6 +8241,7 @@ export {
|
|
|
7922
8241
|
MockProvider,
|
|
7923
8242
|
OllamaProvider,
|
|
7924
8243
|
OpenAIProvider,
|
|
8244
|
+
PocGenerator,
|
|
7925
8245
|
ReviewResultSchema,
|
|
7926
8246
|
SecurityScoreSchema,
|
|
7927
8247
|
SeveritySchema,
|
package/package.json
CHANGED