tryassay 0.29.0 → 0.30.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.
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/generate.js +1 -0
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/harvest.d.ts +9 -0
- package/dist/commands/harvest.js +76 -0
- package/dist/commands/harvest.js.map +1 -0
- package/dist/lib/__tests__/learned-rules.test.d.ts +1 -0
- package/dist/lib/__tests__/learned-rules.test.js +260 -0
- package/dist/lib/__tests__/learned-rules.test.js.map +1 -0
- package/dist/lib/__tests__/pr-harvester-types.test.d.ts +1 -0
- package/dist/lib/__tests__/pr-harvester-types.test.js +43 -0
- package/dist/lib/__tests__/pr-harvester-types.test.js.map +1 -0
- package/dist/lib/__tests__/pr-harvester.test.d.ts +1 -0
- package/dist/lib/__tests__/pr-harvester.test.js +341 -0
- package/dist/lib/__tests__/pr-harvester.test.js.map +1 -0
- package/dist/lib/__tests__/rule-harvester.test.d.ts +1 -0
- package/dist/lib/__tests__/rule-harvester.test.js +526 -0
- package/dist/lib/__tests__/rule-harvester.test.js.map +1 -0
- package/dist/lib/learned-rules/category-map.d.ts +28 -0
- package/dist/lib/learned-rules/category-map.js +110 -0
- package/dist/lib/learned-rules/category-map.js.map +1 -0
- package/dist/lib/learned-rules/index.d.ts +105 -0
- package/dist/lib/learned-rules/index.js +198 -0
- package/dist/lib/learned-rules/index.js.map +1 -0
- package/dist/lib/learned-rules/learned-catalog.d.ts +62 -0
- package/dist/lib/learned-rules/learned-catalog.js +161 -0
- package/dist/lib/learned-rules/learned-catalog.js.map +1 -0
- package/dist/lib/learned-rules/pattern-extractor.d.ts +25 -0
- package/dist/lib/learned-rules/pattern-extractor.js +351 -0
- package/dist/lib/learned-rules/pattern-extractor.js.map +1 -0
- package/dist/lib/learned-rules/rule-codifier.d.ts +41 -0
- package/dist/lib/learned-rules/rule-codifier.js +138 -0
- package/dist/lib/learned-rules/rule-codifier.js.map +1 -0
- package/dist/lib/learned-rules/starter-catalog.d.ts +16 -0
- package/dist/lib/learned-rules/starter-catalog.js +402 -0
- package/dist/lib/learned-rules/starter-catalog.js.map +1 -0
- package/dist/lib/learned-rules/types.d.ts +196 -0
- package/dist/lib/learned-rules/types.js +9 -0
- package/dist/lib/learned-rules/types.js.map +1 -0
- package/dist/lib/learned-rules/validation-harness.d.ts +26 -0
- package/dist/lib/learned-rules/validation-harness.js +260 -0
- package/dist/lib/learned-rules/validation-harness.js.map +1 -0
- package/dist/lib/rule-harvester/diff-parser.d.ts +9 -0
- package/dist/lib/rule-harvester/diff-parser.js +77 -0
- package/dist/lib/rule-harvester/diff-parser.js.map +1 -0
- package/dist/lib/rule-harvester/file-selector.d.ts +10 -0
- package/dist/lib/rule-harvester/file-selector.js +59 -0
- package/dist/lib/rule-harvester/file-selector.js.map +1 -0
- package/dist/lib/rule-harvester/ground-truth.d.ts +19 -0
- package/dist/lib/rule-harvester/ground-truth.js +156 -0
- package/dist/lib/rule-harvester/ground-truth.js.map +1 -0
- package/dist/lib/rule-harvester/harvest.d.ts +26 -0
- package/dist/lib/rule-harvester/harvest.js +307 -0
- package/dist/lib/rule-harvester/harvest.js.map +1 -0
- package/dist/lib/rule-harvester/pr-discovery.d.ts +49 -0
- package/dist/lib/rule-harvester/pr-discovery.js +168 -0
- package/dist/lib/rule-harvester/pr-discovery.js.map +1 -0
- package/dist/lib/rule-harvester/pr-harvest.d.ts +53 -0
- package/dist/lib/rule-harvester/pr-harvest.js +326 -0
- package/dist/lib/rule-harvester/pr-harvest.js.map +1 -0
- package/dist/lib/rule-harvester/progress.d.ts +13 -0
- package/dist/lib/rule-harvester/progress.js +50 -0
- package/dist/lib/rule-harvester/progress.js.map +1 -0
- package/dist/lib/rule-harvester/reporter.d.ts +35 -0
- package/dist/lib/rule-harvester/reporter.js +46 -0
- package/dist/lib/rule-harvester/reporter.js.map +1 -0
- package/dist/lib/rule-harvester/rule-generalizer.d.ts +25 -0
- package/dist/lib/rule-harvester/rule-generalizer.js +135 -0
- package/dist/lib/rule-harvester/rule-generalizer.js.map +1 -0
- package/dist/lib/rule-harvester/scanner.d.ts +20 -0
- package/dist/lib/rule-harvester/scanner.js +37 -0
- package/dist/lib/rule-harvester/scanner.js.map +1 -0
- package/dist/sdk/forward-verify.d.ts +3 -1
- package/dist/sdk/forward-verify.js +68 -5
- package/dist/sdk/forward-verify.js.map +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/types.d.ts +21 -0
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Extractor — extracts generalizable detection patterns from confirmed LLM findings.
|
|
3
|
+
*
|
|
4
|
+
* This is the core of Phase 1: Self-Expanding Formal Verification.
|
|
5
|
+
* When the LLM finds a real bug and it's confirmed, this module
|
|
6
|
+
* analyzes the code and finding to extract a regex pattern that
|
|
7
|
+
* would catch the same class of bug deterministically.
|
|
8
|
+
*
|
|
9
|
+
* The extracted pattern becomes a formal rule — no LLM needed for
|
|
10
|
+
* future instances of the same bug class.
|
|
11
|
+
*/
|
|
12
|
+
import type { PatternExtractionInput, PatternExtractionResult } from './types.js';
|
|
13
|
+
/** Reset counter (for testing). */
|
|
14
|
+
export declare function resetPatternCounter(start?: number): void;
|
|
15
|
+
/**
|
|
16
|
+
* Extract a generalizable detection pattern from a confirmed LLM finding.
|
|
17
|
+
*
|
|
18
|
+
* @param input - The confirmed finding with code context.
|
|
19
|
+
* @returns The extraction result with pattern or failure reason.
|
|
20
|
+
*/
|
|
21
|
+
export declare function extractPattern(input: PatternExtractionInput): PatternExtractionResult;
|
|
22
|
+
/**
|
|
23
|
+
* Get the list of claim categories that have extraction strategies.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getSupportedCategories(): string[];
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Extractor — extracts generalizable detection patterns from confirmed LLM findings.
|
|
3
|
+
*
|
|
4
|
+
* This is the core of Phase 1: Self-Expanding Formal Verification.
|
|
5
|
+
* When the LLM finds a real bug and it's confirmed, this module
|
|
6
|
+
* analyzes the code and finding to extract a regex pattern that
|
|
7
|
+
* would catch the same class of bug deterministically.
|
|
8
|
+
*
|
|
9
|
+
* The extracted pattern becomes a formal rule — no LLM needed for
|
|
10
|
+
* future instances of the same bug class.
|
|
11
|
+
*/
|
|
12
|
+
// ── Language Helpers ──────────────────────────────────────────
|
|
13
|
+
function langGroup(language) {
|
|
14
|
+
const lang = language.toLowerCase();
|
|
15
|
+
if (['typescript', 'ts', 'tsx'].includes(lang))
|
|
16
|
+
return ['ts', 'tsx', 'typescript'];
|
|
17
|
+
if (['javascript', 'js', 'jsx'].includes(lang))
|
|
18
|
+
return ['js', 'jsx', 'javascript'];
|
|
19
|
+
if (['python', 'py'].includes(lang))
|
|
20
|
+
return ['py', 'python'];
|
|
21
|
+
if (['go', 'golang'].includes(lang))
|
|
22
|
+
return ['go', 'golang'];
|
|
23
|
+
return [lang];
|
|
24
|
+
}
|
|
25
|
+
function fileGlobForLang(language) {
|
|
26
|
+
const lang = language.toLowerCase();
|
|
27
|
+
if (['typescript', 'ts', 'tsx', 'javascript', 'js', 'jsx'].includes(lang)) {
|
|
28
|
+
return '**/*.{ts,tsx,js,jsx}';
|
|
29
|
+
}
|
|
30
|
+
if (['python', 'py'].includes(lang))
|
|
31
|
+
return '**/*.py';
|
|
32
|
+
if (['go', 'golang'].includes(lang))
|
|
33
|
+
return '**/*.go';
|
|
34
|
+
if (['rust', 'rs'].includes(lang))
|
|
35
|
+
return '**/*.rs';
|
|
36
|
+
if (['java'].includes(lang))
|
|
37
|
+
return '**/*.java';
|
|
38
|
+
return '**/*';
|
|
39
|
+
}
|
|
40
|
+
// ── ID Generation ────────────────────────────────────────────
|
|
41
|
+
let patternCounter = 0;
|
|
42
|
+
function nextPatternId() {
|
|
43
|
+
patternCounter++;
|
|
44
|
+
return `lp_${String(patternCounter).padStart(4, '0')}`;
|
|
45
|
+
}
|
|
46
|
+
/** Reset counter (for testing). */
|
|
47
|
+
export function resetPatternCounter(start = 0) {
|
|
48
|
+
patternCounter = start;
|
|
49
|
+
}
|
|
50
|
+
// ── Extraction Strategies ────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Strategy: Missing error handling.
|
|
53
|
+
* If the LLM found that error handling is missing, extract a pattern
|
|
54
|
+
* that checks for try/catch, .catch(), or error callbacks near
|
|
55
|
+
* async operations.
|
|
56
|
+
*/
|
|
57
|
+
const missingErrorHandling = {
|
|
58
|
+
categories: ['error-handling'],
|
|
59
|
+
extract(input) {
|
|
60
|
+
const { claim, code, language } = input;
|
|
61
|
+
const text = `${claim.description} ${claim.assertion}`.toLowerCase();
|
|
62
|
+
// Only handle "missing error handling" findings
|
|
63
|
+
if (!/(?:missing|no|lacks?|without|absent)\s+(?:error|exception)\s+(?:handling|catching|recovery)/i.test(text) &&
|
|
64
|
+
!/(?:does\s+not|doesn't)\s+(?:handle|catch)\s+(?:errors?|exceptions?|failures?)/i.test(text)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const lang = language.toLowerCase();
|
|
68
|
+
// Extract the specific operation that needs error handling
|
|
69
|
+
const asyncOpMatch = code.match(/(?:await\s+(\w+(?:\.\w+)*)\s*\()|(?:(\w+(?:\.\w+)*)\s*\(\s*\)\.then)/);
|
|
70
|
+
let pattern;
|
|
71
|
+
let description;
|
|
72
|
+
if (asyncOpMatch) {
|
|
73
|
+
const op = asyncOpMatch[1] || asyncOpMatch[2];
|
|
74
|
+
// Pattern: this specific async call without surrounding try/catch
|
|
75
|
+
pattern = `(?:await\\s+${escapeRegex(op)}\\s*\\()`;
|
|
76
|
+
description = `Async call to '${op}' without error handling`;
|
|
77
|
+
}
|
|
78
|
+
else if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
|
|
79
|
+
// Generic: any fetch/API call without error handling nearby
|
|
80
|
+
pattern = '\\bfetch\\s*\\([^)]*\\)(?![\\s\\S]{0,200}(?:\\.catch|try\\s*\\{|catch\\s*\\())';
|
|
81
|
+
description = 'Fetch call without error handling within 200 characters';
|
|
82
|
+
}
|
|
83
|
+
else if (['py', 'python'].includes(lang)) {
|
|
84
|
+
pattern = '(?:requests\\.(?:get|post|put|delete|patch)\\s*\\()(?![\\s\\S]{0,200}(?:except|try\\s*:))';
|
|
85
|
+
description = 'HTTP request without error handling within 200 characters';
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
return null; // Can't generalize for this language
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
id: nextPatternId(),
|
|
92
|
+
description,
|
|
93
|
+
kind: 'regex',
|
|
94
|
+
languages: langGroup(language),
|
|
95
|
+
regexPattern: pattern,
|
|
96
|
+
fileGlob: fileGlobForLang(language),
|
|
97
|
+
matchBehavior: 'presence_is_bad',
|
|
98
|
+
claimCategory: 'error-handling',
|
|
99
|
+
severity: claim.severity === 'low' ? 'medium' : claim.severity,
|
|
100
|
+
evidenceTemplate: `Missing error handling: {match} in {file}`,
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Strategy: SQL injection via string concatenation.
|
|
106
|
+
* Extracts patterns for detecting SQL built from string concatenation.
|
|
107
|
+
*/
|
|
108
|
+
const sqlInjection = {
|
|
109
|
+
categories: ['security'],
|
|
110
|
+
extract(input) {
|
|
111
|
+
const { claim, language } = input;
|
|
112
|
+
const text = `${claim.description} ${claim.assertion}`.toLowerCase();
|
|
113
|
+
if (!/sql\s+injection|string\s+concatenat.*sql|unsanitized.*sql|sql.*interpolat/i.test(text)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const lang = language.toLowerCase();
|
|
117
|
+
let pattern;
|
|
118
|
+
if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
|
|
119
|
+
// Template literals with SQL keywords and interpolation
|
|
120
|
+
pattern = '`[^`]*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\\s[^`]*\\$\\{(?!\\d)';
|
|
121
|
+
}
|
|
122
|
+
else if (['py', 'python'].includes(lang)) {
|
|
123
|
+
// f-strings or .format() with SQL
|
|
124
|
+
pattern = '(?:f[\'"][^\'"]*(?:SELECT|INSERT|UPDATE|DELETE)|\\.format\\s*\\([^)]*\\).*(?:SELECT|INSERT|UPDATE|DELETE))';
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Generic string concat with SQL
|
|
128
|
+
pattern = '(?:SELECT|INSERT|UPDATE|DELETE|DROP)\\s[^"\']*\\+\\s*\\w+';
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
id: nextPatternId(),
|
|
132
|
+
description: 'SQL query built with string interpolation/concatenation',
|
|
133
|
+
kind: 'regex',
|
|
134
|
+
languages: langGroup(language),
|
|
135
|
+
regexPattern: pattern,
|
|
136
|
+
fileGlob: fileGlobForLang(language),
|
|
137
|
+
matchBehavior: 'presence_is_bad',
|
|
138
|
+
claimCategory: 'security',
|
|
139
|
+
severity: 'critical',
|
|
140
|
+
evidenceTemplate: `SQL injection risk: {match} in {file}`,
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Strategy: Missing null/undefined check.
|
|
146
|
+
* When LLM finds that a function doesn't validate its inputs for null.
|
|
147
|
+
*/
|
|
148
|
+
const missingNullCheck = {
|
|
149
|
+
categories: ['edge-case', 'correctness'],
|
|
150
|
+
extract(input) {
|
|
151
|
+
const { claim, code, language } = input;
|
|
152
|
+
const text = `${claim.description} ${claim.assertion}`.toLowerCase();
|
|
153
|
+
if (!/(?:null|undefined|nil|none)\s+(?:check|guard|validation|handling)/i.test(text) &&
|
|
154
|
+
!/(?:does\s+not|doesn't)\s+(?:check|validate|guard|handle)\s+(?:null|undefined|nil|none)/i.test(text)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
// Try to extract the specific function/variable name
|
|
158
|
+
const funcMatch = code.match(/(?:function|const|let|var)\s+(\w+)\s*(?:=\s*(?:async\s+)?)?(?:\([^)]*\)|=>|\{)/);
|
|
159
|
+
if (!funcMatch)
|
|
160
|
+
return null;
|
|
161
|
+
const funcName = funcMatch[1];
|
|
162
|
+
const lang = language.toLowerCase();
|
|
163
|
+
let pattern;
|
|
164
|
+
if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
|
|
165
|
+
// Function that takes params but doesn't check for null/undefined
|
|
166
|
+
pattern = `(?:function\\s+${escapeRegex(funcName)}|(?:const|let|var)\\s+${escapeRegex(funcName)}\\s*=)\\s*(?:async\\s+)?\\([^)]+\\)[^{]*\\{(?![\\s\\S]{0,300}(?:!==?\\s*(?:null|undefined)|===?\\s*(?:null|undefined)|\\?\\.))`;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
id: nextPatternId(),
|
|
173
|
+
description: `Function '${funcName}' does not validate inputs for null/undefined`,
|
|
174
|
+
kind: 'regex',
|
|
175
|
+
languages: langGroup(language),
|
|
176
|
+
regexPattern: pattern,
|
|
177
|
+
fileGlob: fileGlobForLang(language),
|
|
178
|
+
matchBehavior: 'presence_is_bad',
|
|
179
|
+
claimCategory: claim.category,
|
|
180
|
+
severity: claim.severity === 'low' ? 'medium' : claim.severity,
|
|
181
|
+
evidenceTemplate: `Missing null check in function '{match}' in {file}`,
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Strategy: Hardcoded secrets/credentials.
|
|
187
|
+
* When LLM finds API keys, tokens, or passwords hardcoded in source.
|
|
188
|
+
*/
|
|
189
|
+
const hardcodedSecrets = {
|
|
190
|
+
categories: ['security'],
|
|
191
|
+
extract(input) {
|
|
192
|
+
const { claim } = input;
|
|
193
|
+
const text = `${claim.description} ${claim.assertion}`.toLowerCase();
|
|
194
|
+
if (!/(?:hardcod|embed|literal)\w*\s+(?:secret|key|token|password|credential|api.?key)/i.test(text) &&
|
|
195
|
+
!/(?:secret|key|token|password|credential|api.?key)\s+(?:hardcod|embed|literal)/i.test(text)) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
id: nextPatternId(),
|
|
200
|
+
description: 'Hardcoded secret, API key, token, or password in source code',
|
|
201
|
+
kind: 'regex',
|
|
202
|
+
languages: [], // All languages
|
|
203
|
+
regexPattern: "(?:api[_-]?key|secret|password|token|auth)\\s*[:=]\\s*['\"`][A-Za-z0-9+/=_-]{20,}['\"`]",
|
|
204
|
+
fileGlob: '**/*.{ts,tsx,js,jsx,py,go,java,rs}',
|
|
205
|
+
matchBehavior: 'presence_is_bad',
|
|
206
|
+
claimCategory: 'security',
|
|
207
|
+
severity: 'critical',
|
|
208
|
+
evidenceTemplate: `Hardcoded secret found: {match} in {file}`,
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Strategy: Missing input validation.
|
|
214
|
+
* When LLM finds that user input isn't validated before use.
|
|
215
|
+
*/
|
|
216
|
+
const missingInputValidation = {
|
|
217
|
+
categories: ['security', 'correctness'],
|
|
218
|
+
extract(input) {
|
|
219
|
+
const { claim, code, language } = input;
|
|
220
|
+
const text = `${claim.description} ${claim.assertion}`.toLowerCase();
|
|
221
|
+
if (!/(?:missing|no|lacks?|without)\s+(?:input|user|request)\s+validation/i.test(text) &&
|
|
222
|
+
!/(?:does\s+not|doesn't)\s+(?:validate|sanitize|check)\s+(?:input|user|request)/i.test(text)) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const lang = language.toLowerCase();
|
|
226
|
+
let pattern;
|
|
227
|
+
if (['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
|
|
228
|
+
// Express/Node: req.body used without validation
|
|
229
|
+
if (code.includes('req.body') || code.includes('req.params') || code.includes('req.query')) {
|
|
230
|
+
pattern = '(?:req\\.(?:body|params|query))(?![\\s\\S]{0,100}(?:validate|parse|schema|zod|joi|yup))';
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (['py', 'python'].includes(lang)) {
|
|
237
|
+
// Flask/Django: request data used without validation
|
|
238
|
+
pattern = '(?:request\\.(?:json|form|args|data))(?![\\s\\S]{0,100}(?:validate|schema|marshmallow|pydantic))';
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
id: nextPatternId(),
|
|
245
|
+
description: 'User input used without validation or sanitization',
|
|
246
|
+
kind: 'regex',
|
|
247
|
+
languages: langGroup(language),
|
|
248
|
+
regexPattern: pattern,
|
|
249
|
+
fileGlob: fileGlobForLang(language),
|
|
250
|
+
matchBehavior: 'presence_is_bad',
|
|
251
|
+
claimCategory: 'security',
|
|
252
|
+
severity: 'high',
|
|
253
|
+
evidenceTemplate: `Unvalidated input: {match} in {file}`,
|
|
254
|
+
};
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Strategy: Unhandled promise rejection.
|
|
259
|
+
* When LLM finds async operations without await or .catch().
|
|
260
|
+
*/
|
|
261
|
+
const unhandledPromise = {
|
|
262
|
+
categories: ['error-handling', 'correctness'],
|
|
263
|
+
extract(input) {
|
|
264
|
+
const { claim, language } = input;
|
|
265
|
+
const text = `${claim.description} ${claim.assertion}`.toLowerCase();
|
|
266
|
+
if (!/(?:unhandled|floating|detached)\s+promise/i.test(text) &&
|
|
267
|
+
!/promise\s+(?:not\s+)?(?:awaited|handled|caught)/i.test(text)) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
const lang = language.toLowerCase();
|
|
271
|
+
if (!['ts', 'tsx', 'typescript', 'js', 'jsx', 'javascript'].includes(lang)) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
id: nextPatternId(),
|
|
276
|
+
description: 'Promise-returning call without await or .catch()',
|
|
277
|
+
kind: 'regex',
|
|
278
|
+
languages: langGroup(language),
|
|
279
|
+
// Match function calls that likely return promises but aren't awaited
|
|
280
|
+
// Look for common async patterns without await
|
|
281
|
+
regexPattern: '(?<!await\\s)(?<!return\\s)\\b(?:fetch|axios\\.\\w+|\\w+\\.save|\\w+\\.delete|\\w+\\.update)\\s*\\([^)]*\\)(?!\\s*\\.(?:then|catch))',
|
|
282
|
+
fileGlob: fileGlobForLang(language),
|
|
283
|
+
matchBehavior: 'presence_is_bad',
|
|
284
|
+
claimCategory: 'error-handling',
|
|
285
|
+
severity: 'high',
|
|
286
|
+
evidenceTemplate: `Unhandled promise: {match} in {file}`,
|
|
287
|
+
};
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
// ── Registry ─────────────────────────────────────────────────
|
|
291
|
+
const STRATEGIES = [
|
|
292
|
+
missingErrorHandling,
|
|
293
|
+
sqlInjection,
|
|
294
|
+
missingNullCheck,
|
|
295
|
+
hardcodedSecrets,
|
|
296
|
+
missingInputValidation,
|
|
297
|
+
unhandledPromise,
|
|
298
|
+
];
|
|
299
|
+
// ── Helpers ──────────────────────────────────────────────────
|
|
300
|
+
function escapeRegex(str) {
|
|
301
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
302
|
+
}
|
|
303
|
+
// ── Public API ───────────────────────────────────────────────
|
|
304
|
+
/**
|
|
305
|
+
* Extract a generalizable detection pattern from a confirmed LLM finding.
|
|
306
|
+
*
|
|
307
|
+
* @param input - The confirmed finding with code context.
|
|
308
|
+
* @returns The extraction result with pattern or failure reason.
|
|
309
|
+
*/
|
|
310
|
+
export function extractPattern(input) {
|
|
311
|
+
// Only process confirmed failures
|
|
312
|
+
if (input.verification.verdict !== 'FAIL') {
|
|
313
|
+
return {
|
|
314
|
+
success: false,
|
|
315
|
+
failureReason: `Verdict is '${input.verification.verdict}', not 'FAIL'. Only confirmed failures can generate rules.`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
// Try each strategy in order
|
|
319
|
+
for (const strategy of STRATEGIES) {
|
|
320
|
+
if (!strategy.categories.includes(input.claim.category))
|
|
321
|
+
continue;
|
|
322
|
+
const pattern = strategy.extract(input);
|
|
323
|
+
if (pattern) {
|
|
324
|
+
// Validate the regex compiles
|
|
325
|
+
try {
|
|
326
|
+
new RegExp(pattern.regexPattern);
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
continue; // Skip patterns that don't compile
|
|
330
|
+
}
|
|
331
|
+
return { success: true, pattern };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
success: false,
|
|
336
|
+
failureReason: `No extraction strategy matched claim category '${input.claim.category}' with description: ${input.claim.description}`,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Get the list of claim categories that have extraction strategies.
|
|
341
|
+
*/
|
|
342
|
+
export function getSupportedCategories() {
|
|
343
|
+
const categories = new Set();
|
|
344
|
+
for (const strategy of STRATEGIES) {
|
|
345
|
+
for (const cat of strategy.categories) {
|
|
346
|
+
categories.add(cat);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return [...categories];
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=pattern-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-extractor.js","sourceRoot":"","sources":["../../../src/lib/learned-rules/pattern-extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Codifier — converts extracted patterns into executable formal checks.
|
|
3
|
+
*
|
|
4
|
+
* Takes an ExtractedPattern and produces a LearnedRule that can run
|
|
5
|
+
* alongside hand-crafted rules in the formal verifier.
|
|
6
|
+
*
|
|
7
|
+
* Current implementation: regex-based rules only.
|
|
8
|
+
* Future: AST patterns (tree-sitter), type constraints (ts-morph).
|
|
9
|
+
*/
|
|
10
|
+
import type { ExtractedPattern, LearnedRule, PatternExtractionInput } from './types.js';
|
|
11
|
+
/** Reset counter (for testing). */
|
|
12
|
+
export declare function resetRuleCounter(start?: number): void;
|
|
13
|
+
/**
|
|
14
|
+
* Create a new learned rule from an extracted pattern and its source finding.
|
|
15
|
+
*
|
|
16
|
+
* The rule starts in 'candidate' status and must pass validation
|
|
17
|
+
* before being promoted to 'validated' and then 'promoted'.
|
|
18
|
+
*/
|
|
19
|
+
export declare function codifyRule(pattern: ExtractedPattern, input: PatternExtractionInput): LearnedRule;
|
|
20
|
+
/**
|
|
21
|
+
* Execute a learned rule against a code string.
|
|
22
|
+
*
|
|
23
|
+
* Returns true if the rule fires (detects a potential issue).
|
|
24
|
+
*/
|
|
25
|
+
export declare function executeRule(rule: LearnedRule, code: string, language: string): {
|
|
26
|
+
fires: boolean;
|
|
27
|
+
matches: string[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Merge a new source finding into an existing rule.
|
|
31
|
+
* This happens when the same pattern is extracted from a different finding.
|
|
32
|
+
*/
|
|
33
|
+
export declare function mergeSourceFinding(rule: LearnedRule, input: PatternExtractionInput): LearnedRule;
|
|
34
|
+
/**
|
|
35
|
+
* Update rule status through the lifecycle.
|
|
36
|
+
*/
|
|
37
|
+
export declare function updateRuleStatus(rule: LearnedRule, status: LearnedRule['status']): LearnedRule;
|
|
38
|
+
/**
|
|
39
|
+
* Record that a rule fired and was confirmed/rejected by a human or auto-confirmation.
|
|
40
|
+
*/
|
|
41
|
+
export declare function recordRuleFire(rule: LearnedRule, wasCorrect: boolean): LearnedRule;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule Codifier — converts extracted patterns into executable formal checks.
|
|
3
|
+
*
|
|
4
|
+
* Takes an ExtractedPattern and produces a LearnedRule that can run
|
|
5
|
+
* alongside hand-crafted rules in the formal verifier.
|
|
6
|
+
*
|
|
7
|
+
* Current implementation: regex-based rules only.
|
|
8
|
+
* Future: AST patterns (tree-sitter), type constraints (ts-morph).
|
|
9
|
+
*/
|
|
10
|
+
// ── Rule ID Generation ───────────────────────────────────────
|
|
11
|
+
let ruleCounter = 0;
|
|
12
|
+
function nextRuleId() {
|
|
13
|
+
ruleCounter++;
|
|
14
|
+
return `lr_${String(ruleCounter).padStart(4, '0')}`;
|
|
15
|
+
}
|
|
16
|
+
/** Reset counter (for testing). */
|
|
17
|
+
export function resetRuleCounter(start = 0) {
|
|
18
|
+
ruleCounter = start;
|
|
19
|
+
}
|
|
20
|
+
// ── Rule Creation ────────────────────────────────────────────
|
|
21
|
+
/**
|
|
22
|
+
* Create a new learned rule from an extracted pattern and its source finding.
|
|
23
|
+
*
|
|
24
|
+
* The rule starts in 'candidate' status and must pass validation
|
|
25
|
+
* before being promoted to 'validated' and then 'promoted'.
|
|
26
|
+
*/
|
|
27
|
+
export function codifyRule(pattern, input) {
|
|
28
|
+
const now = new Date().toISOString();
|
|
29
|
+
const sourceFinding = {
|
|
30
|
+
claimId: input.claim.id,
|
|
31
|
+
claimDescription: input.claim.description,
|
|
32
|
+
codeSnippet: truncateCode(input.code, 500),
|
|
33
|
+
filePath: input.filePath,
|
|
34
|
+
language: input.language,
|
|
35
|
+
timestamp: now,
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
id: nextRuleId(),
|
|
39
|
+
pattern,
|
|
40
|
+
status: 'candidate',
|
|
41
|
+
createdAt: now,
|
|
42
|
+
updatedAt: now,
|
|
43
|
+
fireCount: 0,
|
|
44
|
+
truePositiveCount: 0,
|
|
45
|
+
falsePositiveCount: 0,
|
|
46
|
+
sourceFindings: [sourceFinding],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Execute a learned rule against a code string.
|
|
51
|
+
*
|
|
52
|
+
* Returns true if the rule fires (detects a potential issue).
|
|
53
|
+
*/
|
|
54
|
+
export function executeRule(rule, code, language) {
|
|
55
|
+
const pattern = rule.pattern;
|
|
56
|
+
// Check language applicability
|
|
57
|
+
if (pattern.languages.length > 0) {
|
|
58
|
+
const langLower = language.toLowerCase();
|
|
59
|
+
if (!pattern.languages.some(l => l.toLowerCase() === langLower)) {
|
|
60
|
+
return { fires: false, matches: [] };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Currently only regex patterns are supported
|
|
64
|
+
if (pattern.kind !== 'regex' || !pattern.regexPattern) {
|
|
65
|
+
return { fires: false, matches: [] };
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const regex = new RegExp(pattern.regexPattern, 'gi');
|
|
69
|
+
const matches = [];
|
|
70
|
+
let match;
|
|
71
|
+
while ((match = regex.exec(code)) !== null) {
|
|
72
|
+
matches.push(match[0]);
|
|
73
|
+
// Prevent infinite loops on zero-length matches
|
|
74
|
+
if (match[0].length === 0)
|
|
75
|
+
regex.lastIndex++;
|
|
76
|
+
}
|
|
77
|
+
if (pattern.matchBehavior === 'presence_is_bad') {
|
|
78
|
+
return { fires: matches.length > 0, matches };
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// absence_is_bad: fires when pattern is NOT found
|
|
82
|
+
return { fires: matches.length === 0, matches: [] };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Invalid regex — rule should be rejected
|
|
87
|
+
return { fires: false, matches: [] };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Merge a new source finding into an existing rule.
|
|
92
|
+
* This happens when the same pattern is extracted from a different finding.
|
|
93
|
+
*/
|
|
94
|
+
export function mergeSourceFinding(rule, input) {
|
|
95
|
+
const now = new Date().toISOString();
|
|
96
|
+
const newFinding = {
|
|
97
|
+
claimId: input.claim.id,
|
|
98
|
+
claimDescription: input.claim.description,
|
|
99
|
+
codeSnippet: truncateCode(input.code, 500),
|
|
100
|
+
filePath: input.filePath,
|
|
101
|
+
language: input.language,
|
|
102
|
+
timestamp: now,
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
...rule,
|
|
106
|
+
updatedAt: now,
|
|
107
|
+
sourceFindings: [...rule.sourceFindings, newFinding],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Update rule status through the lifecycle.
|
|
112
|
+
*/
|
|
113
|
+
export function updateRuleStatus(rule, status) {
|
|
114
|
+
return {
|
|
115
|
+
...rule,
|
|
116
|
+
status,
|
|
117
|
+
updatedAt: new Date().toISOString(),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Record that a rule fired and was confirmed/rejected by a human or auto-confirmation.
|
|
122
|
+
*/
|
|
123
|
+
export function recordRuleFire(rule, wasCorrect) {
|
|
124
|
+
return {
|
|
125
|
+
...rule,
|
|
126
|
+
fireCount: rule.fireCount + 1,
|
|
127
|
+
truePositiveCount: rule.truePositiveCount + (wasCorrect ? 1 : 0),
|
|
128
|
+
falsePositiveCount: rule.falsePositiveCount + (wasCorrect ? 0 : 1),
|
|
129
|
+
updatedAt: new Date().toISOString(),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// ── Helpers ──────────────────────────────────────────────────
|
|
133
|
+
function truncateCode(code, maxLength) {
|
|
134
|
+
if (code.length <= maxLength)
|
|
135
|
+
return code;
|
|
136
|
+
return code.slice(0, maxLength) + '\n// ... truncated';
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=rule-codifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-codifier.js","sourceRoot":"","sources":["../../../src/lib/learned-rules/rule-codifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starter Catalog — ~15 high-signal learned rules that ship with the npm package.
|
|
3
|
+
*
|
|
4
|
+
* These rules were curated from 214 rules harvested from real PR diffs in
|
|
5
|
+
* popular open-source TypeScript projects. Selection criteria:
|
|
6
|
+
* - Valid, compilable regex
|
|
7
|
+
* - High or critical severity preferred
|
|
8
|
+
* - General-purpose (not tied to a specific project)
|
|
9
|
+
* - Good coverage across categories (security, error handling, etc.)
|
|
10
|
+
* - Useful fixDescription
|
|
11
|
+
*
|
|
12
|
+
* All starter rules have `source: "bundled"` to distinguish them from
|
|
13
|
+
* user-harvested rules. Local rules with the same ID take precedence.
|
|
14
|
+
*/
|
|
15
|
+
import type { LearnedRule } from './types.js';
|
|
16
|
+
export declare const STARTER_RULES: readonly LearnedRule[];
|