tryassay 0.34.0 → 0.35.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/bayesian/__tests__/bas-calculator.test.d.ts +1 -0
- package/dist/bayesian/__tests__/bas-calculator.test.js +63 -0
- package/dist/bayesian/__tests__/bas-calculator.test.js.map +1 -0
- package/dist/bayesian/__tests__/structural-entropy.test.d.ts +1 -0
- package/dist/bayesian/__tests__/structural-entropy.test.js +21 -0
- package/dist/bayesian/__tests__/structural-entropy.test.js.map +1 -0
- package/dist/bayesian/bas-calculator.d.ts +41 -0
- package/dist/bayesian/bas-calculator.js +198 -0
- package/dist/bayesian/bas-calculator.js.map +1 -0
- package/dist/bayesian/index.d.ts +3 -0
- package/dist/bayesian/index.js +3 -0
- package/dist/bayesian/index.js.map +1 -0
- package/dist/bayesian/structural-entropy.d.ts +12 -0
- package/dist/bayesian/structural-entropy.js +37 -0
- package/dist/bayesian/structural-entropy.js.map +1 -0
- package/dist/bayesian/types.d.ts +37 -0
- package/dist/bayesian/types.js +6 -0
- package/dist/bayesian/types.js.map +1 -0
- package/dist/cli.js +28 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/__tests__/assess-formal.test.d.ts +1 -0
- package/dist/commands/__tests__/assess-formal.test.js +72 -0
- package/dist/commands/__tests__/assess-formal.test.js.map +1 -0
- package/dist/commands/activate.d.ts +1 -0
- package/dist/commands/activate.js +48 -0
- package/dist/commands/activate.js.map +1 -0
- package/dist/commands/assess.js +100 -5
- package/dist/commands/assess.js.map +1 -1
- package/dist/commands/bas-score.d.ts +13 -0
- package/dist/commands/bas-score.js +310 -0
- package/dist/commands/bas-score.js.map +1 -0
- package/dist/commands/bounty-watch.js.map +1 -1
- package/dist/commands/hunt.js +32 -0
- package/dist/commands/hunt.js.map +1 -1
- package/dist/commands/runtime.js +11 -10
- package/dist/commands/runtime.js.map +1 -1
- package/dist/commands/stream-verify.d.ts +16 -0
- package/dist/commands/stream-verify.js +228 -0
- package/dist/commands/stream-verify.js.map +1 -0
- package/dist/commands/watch.js.map +1 -1
- package/dist/hunt/__tests__/deep-dive.test.js.map +1 -1
- package/dist/hunt/__tests__/e2e.test.js.map +1 -1
- package/dist/hunt/__tests__/finding-to-template.test.js +10 -1
- package/dist/hunt/__tests__/finding-to-template.test.js.map +1 -1
- package/dist/hunt/__tests__/orchestrator.test.js.map +1 -1
- package/dist/hunt/__tests__/templates.test.js +2 -2
- package/dist/hunt/__tests__/triage.test.js.map +1 -1
- package/dist/hunt/deep-dive.js +7 -7
- package/dist/hunt/deep-dive.js.map +1 -1
- package/dist/hunt/parse-utils.d.ts +1 -1
- package/dist/hunt/state.js.map +1 -1
- package/dist/hunt/templates/injection.js +1 -1
- package/dist/hunt/templates/injection.js.map +1 -1
- package/dist/hunt/triage.js +5 -5
- package/dist/hunt/triage.js.map +1 -1
- package/dist/lib/__tests__/arithmetic-quick-test.js +10 -9
- package/dist/lib/__tests__/arithmetic-quick-test.js.map +1 -1
- package/dist/lib/__tests__/arithmetic-real-llm-test.js +8 -8
- package/dist/lib/__tests__/arithmetic-real-llm-test.js.map +1 -1
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.js +4 -3
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.js.map +1 -1
- package/dist/lib/__tests__/formal-verifier-behavioral.test.d.ts +18 -0
- package/dist/lib/__tests__/formal-verifier-behavioral.test.js +576 -0
- package/dist/lib/__tests__/formal-verifier-behavioral.test.js.map +1 -0
- package/dist/lib/__tests__/formal-verifier-claimless-async.test.d.ts +1 -0
- package/dist/lib/__tests__/formal-verifier-claimless-async.test.js +154 -0
- package/dist/lib/__tests__/formal-verifier-claimless-async.test.js.map +1 -0
- package/dist/lib/__tests__/formal-verifier-claimless-quality.test.d.ts +1 -0
- package/dist/lib/__tests__/formal-verifier-claimless-quality.test.js +121 -0
- package/dist/lib/__tests__/formal-verifier-claimless-quality.test.js.map +1 -0
- package/dist/lib/__tests__/formal-verifier-claimless-realworld.test.d.ts +1 -0
- package/dist/lib/__tests__/formal-verifier-claimless-realworld.test.js +119 -0
- package/dist/lib/__tests__/formal-verifier-claimless-realworld.test.js.map +1 -0
- package/dist/lib/__tests__/formal-verifier-claimless.test.d.ts +1 -0
- package/dist/lib/__tests__/formal-verifier-claimless.test.js +667 -0
- package/dist/lib/__tests__/formal-verifier-claimless.test.js.map +1 -0
- package/dist/lib/__tests__/mr-gsm8k-benchmark.js +6 -6
- package/dist/lib/__tests__/mr-gsm8k-benchmark.js.map +1 -1
- package/dist/lib/__tests__/pr-harvester.test.js.map +1 -1
- package/dist/lib/assessment-reporter.d.ts +1 -1
- package/dist/lib/assessment-reporter.js +2 -1
- package/dist/lib/assessment-reporter.js.map +1 -1
- package/dist/lib/chain-analyzer.d.ts +4 -3
- package/dist/lib/chain-analyzer.js.map +1 -1
- package/dist/lib/formal-verifier.d.ts +20 -1
- package/dist/lib/formal-verifier.js +1182 -24
- package/dist/lib/formal-verifier.js.map +1 -1
- package/dist/lib/issue-reporter.d.ts +2 -1
- package/dist/lib/issue-reporter.js.map +1 -1
- package/dist/lib/remediation-generator.js.map +1 -1
- package/dist/lib/report-generator.js.map +1 -1
- package/dist/lib/rule-harvester/ground-truth.js +13 -2
- package/dist/lib/rule-harvester/ground-truth.js.map +1 -1
- package/dist/lib/rule-harvester/scanner.d.ts +1 -1
- package/dist/lib/user-config.d.ts +1 -0
- package/dist/lib/user-config.js.map +1 -1
- package/dist/realtime/__tests__/entropy-detector.test.d.ts +1 -0
- package/dist/realtime/__tests__/entropy-detector.test.js +200 -0
- package/dist/realtime/__tests__/entropy-detector.test.js.map +1 -0
- package/dist/realtime/__tests__/entropy-live-demo.d.ts +1 -0
- package/dist/realtime/__tests__/entropy-live-demo.js +103 -0
- package/dist/realtime/__tests__/entropy-live-demo.js.map +1 -0
- package/dist/realtime/__tests__/entropy-live.d.ts +8 -0
- package/dist/realtime/__tests__/entropy-live.js +114 -0
- package/dist/realtime/__tests__/entropy-live.js.map +1 -0
- package/dist/realtime/__tests__/streaming-checks.test.js +3 -4
- package/dist/realtime/__tests__/streaming-checks.test.js.map +1 -1
- package/dist/realtime/entropy-detector.d.ts +143 -0
- package/dist/realtime/entropy-detector.js +504 -0
- package/dist/realtime/entropy-detector.js.map +1 -0
- package/dist/realtime/mcp-server.d.ts +7 -1
- package/dist/realtime/mcp-server.js +378 -2
- package/dist/realtime/mcp-server.js.map +1 -1
- package/dist/realtime/stream-interceptor.d.ts +28 -0
- package/dist/realtime/stream-interceptor.js +204 -0
- package/dist/realtime/stream-interceptor.js.map +1 -1
- package/dist/realtime/streaming-checks.js +28 -0
- package/dist/realtime/streaming-checks.js.map +1 -1
- package/dist/realtime/streaming-verifier.d.ts +45 -0
- package/dist/realtime/streaming-verifier.js +98 -5
- package/dist/realtime/streaming-verifier.js.map +1 -1
- package/dist/realtime/types.d.ts +56 -0
- package/dist/runtime/agents/research-agent.js +10 -1
- package/dist/runtime/agents/research-agent.js.map +1 -1
- package/dist/runtime/agents/test-agent.js +10 -7
- package/dist/runtime/agents/test-agent.js.map +1 -1
- package/dist/runtime/composition-verifier.js +13 -3
- package/dist/runtime/composition-verifier.js.map +1 -1
- package/dist/runtime/fs-helpers.js.map +1 -1
- package/dist/runtime/prompt-safety-analyzer.js.map +1 -1
- package/dist/sdk/verified-generate.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/package.json +3 -2
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { runClaimlessChecks } from '../formal-verifier.js';
|
|
3
|
+
describe('runClaimlessChecks', () => {
|
|
4
|
+
describe('SQL injection detection', () => {
|
|
5
|
+
it('detects string-concatenated SQL queries', () => {
|
|
6
|
+
const code = `
|
|
7
|
+
const query = "SELECT * FROM users WHERE id = " + userId;
|
|
8
|
+
db.query(query);
|
|
9
|
+
`;
|
|
10
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
11
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
12
|
+
expect(findings[0].checkType).toBe('sql_parameterized');
|
|
13
|
+
expect(findings[0].severity).toBe('critical');
|
|
14
|
+
expect(findings[0].line).toBe(2);
|
|
15
|
+
});
|
|
16
|
+
it('passes clean parameterized queries', () => {
|
|
17
|
+
const code = `
|
|
18
|
+
const query = "SELECT * FROM users WHERE id = $1";
|
|
19
|
+
db.query(query, [userId]);
|
|
20
|
+
`;
|
|
21
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
22
|
+
const sqlFindings = findings.filter(f => f.checkType === 'sql_parameterized');
|
|
23
|
+
expect(sqlFindings).toHaveLength(0);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('error handling detection', () => {
|
|
27
|
+
it('detects async function without try/catch', () => {
|
|
28
|
+
const code = `
|
|
29
|
+
async function fetchData() {
|
|
30
|
+
const res = await fetch('/api/data');
|
|
31
|
+
return res.json();
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
35
|
+
const errFindings = findings.filter(f => f.checkType === 'error_handling');
|
|
36
|
+
expect(errFindings.length).toBeGreaterThan(0);
|
|
37
|
+
expect(errFindings[0].severity).toBe('high');
|
|
38
|
+
});
|
|
39
|
+
it('passes async function with try/catch', () => {
|
|
40
|
+
const code = `
|
|
41
|
+
async function fetchData() {
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch('/api/data');
|
|
44
|
+
return res.json();
|
|
45
|
+
} catch (e) {
|
|
46
|
+
throw new Error('fetch failed');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
51
|
+
const errFindings = findings.filter(f => f.checkType === 'error_handling');
|
|
52
|
+
expect(errFindings).toHaveLength(0);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('API misuse detection', () => {
|
|
56
|
+
it('detects .flatten() misuse in JS/TS', () => {
|
|
57
|
+
const code = `
|
|
58
|
+
const items = nested.flatten();
|
|
59
|
+
`;
|
|
60
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
61
|
+
const apiFindings = findings.filter(f => f.checkType === 'api_misuse');
|
|
62
|
+
expect(apiFindings.length).toBeGreaterThan(0);
|
|
63
|
+
expect(apiFindings[0].severity).toBe('critical');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('input validation detection', () => {
|
|
67
|
+
it('detects unvalidated req.body usage', () => {
|
|
68
|
+
const code = `
|
|
69
|
+
app.post('/api/users', (req, res) => {
|
|
70
|
+
const name = req.body.name;
|
|
71
|
+
db.insert({ name });
|
|
72
|
+
});
|
|
73
|
+
`;
|
|
74
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
75
|
+
const valFindings = findings.filter(f => f.checkType === 'input_validation');
|
|
76
|
+
expect(valFindings.length).toBeGreaterThan(0);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('null safety detection', () => {
|
|
80
|
+
it('detects non-null assertion operator as null_check finding', () => {
|
|
81
|
+
const code = `
|
|
82
|
+
const result = value!.property;
|
|
83
|
+
`;
|
|
84
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
85
|
+
const nullFindings = findings.filter(f => f.checkType === 'null_check');
|
|
86
|
+
expect(nullFindings.length).toBeGreaterThan(0);
|
|
87
|
+
expect(nullFindings[0].severity).toBe('medium');
|
|
88
|
+
});
|
|
89
|
+
it('does not flag optional chaining as a null_check finding', () => {
|
|
90
|
+
const code = `
|
|
91
|
+
const result = value?.property;
|
|
92
|
+
`;
|
|
93
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
94
|
+
const nullFindings = findings.filter(f => f.checkType === 'null_check');
|
|
95
|
+
expect(nullFindings).toHaveLength(0);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('clean code produces no findings', () => {
|
|
99
|
+
it('returns empty array for well-written code', () => {
|
|
100
|
+
const code = `
|
|
101
|
+
function add(a: number, b: number): number {
|
|
102
|
+
return a + b;
|
|
103
|
+
}
|
|
104
|
+
`;
|
|
105
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
106
|
+
expect(findings).toHaveLength(0);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('security_control detection', () => {
|
|
110
|
+
it('detects eval() usage', () => {
|
|
111
|
+
const code = `
|
|
112
|
+
const result = eval(userInput);
|
|
113
|
+
`;
|
|
114
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
115
|
+
const secFindings = findings.filter(f => f.checkType === 'security_control');
|
|
116
|
+
expect(secFindings.length).toBeGreaterThan(0);
|
|
117
|
+
expect(secFindings[0].severity).toBe('critical');
|
|
118
|
+
});
|
|
119
|
+
it('detects innerHTML assignment', () => {
|
|
120
|
+
const code = `
|
|
121
|
+
element.innerHTML = userInput;
|
|
122
|
+
`;
|
|
123
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
124
|
+
const secFindings = findings.filter(f => f.checkType === 'security_control');
|
|
125
|
+
expect(secFindings.length).toBeGreaterThan(0);
|
|
126
|
+
expect(secFindings[0].severity).toBe('critical');
|
|
127
|
+
});
|
|
128
|
+
it('detects hardcoded API key (sk_live_)', () => {
|
|
129
|
+
const code = `
|
|
130
|
+
const apiKey = "sk_live_abc123xyz";
|
|
131
|
+
`;
|
|
132
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
133
|
+
const secFindings = findings.filter(f => f.checkType === 'security_control');
|
|
134
|
+
expect(secFindings.length).toBeGreaterThan(0);
|
|
135
|
+
expect(secFindings[0].severity).toBe('critical');
|
|
136
|
+
});
|
|
137
|
+
it('passes safe DOM operations', () => {
|
|
138
|
+
const code = `
|
|
139
|
+
element.textContent = userInput;
|
|
140
|
+
const text = element.innerHTML;
|
|
141
|
+
`;
|
|
142
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
143
|
+
const secFindings = findings.filter(f => f.checkType === 'security_control');
|
|
144
|
+
expect(secFindings).toHaveLength(0);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('data_handling detection', () => {
|
|
148
|
+
it('detects JSON.parse without try/catch', () => {
|
|
149
|
+
const code = `
|
|
150
|
+
function parseData(raw: string) {
|
|
151
|
+
const data = JSON.parse(raw);
|
|
152
|
+
return data;
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
156
|
+
const dataFindings = findings.filter(f => f.checkType === 'data_handling');
|
|
157
|
+
expect(dataFindings.length).toBeGreaterThan(0);
|
|
158
|
+
expect(dataFindings[0].severity).toBe('high');
|
|
159
|
+
});
|
|
160
|
+
it('passes JSON.parse inside try/catch', () => {
|
|
161
|
+
const code = `
|
|
162
|
+
function parseData(raw: string) {
|
|
163
|
+
try {
|
|
164
|
+
const data = JSON.parse(raw);
|
|
165
|
+
return data;
|
|
166
|
+
} catch (e) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
`;
|
|
171
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
172
|
+
const dataFindings = findings.filter(f => f.checkType === 'data_handling');
|
|
173
|
+
expect(dataFindings).toHaveLength(0);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
describe('configuration_check detection', () => {
|
|
177
|
+
it('detects process.env.X without fallback', () => {
|
|
178
|
+
const code = `
|
|
179
|
+
const port = process.env.PORT;
|
|
180
|
+
app.listen(port);
|
|
181
|
+
`;
|
|
182
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
183
|
+
const configFindings = findings.filter(f => f.checkType === 'configuration_check');
|
|
184
|
+
expect(configFindings.length).toBeGreaterThan(0);
|
|
185
|
+
expect(configFindings[0].severity).toBe('high');
|
|
186
|
+
});
|
|
187
|
+
it('passes process.env.X with ?? fallback', () => {
|
|
188
|
+
const code = `
|
|
189
|
+
const port = process.env.PORT ?? '3000';
|
|
190
|
+
app.listen(port);
|
|
191
|
+
`;
|
|
192
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
193
|
+
const configFindings = findings.filter(f => f.checkType === 'configuration_check');
|
|
194
|
+
expect(configFindings).toHaveLength(0);
|
|
195
|
+
});
|
|
196
|
+
it('passes process.env.X with || fallback', () => {
|
|
197
|
+
const code = `
|
|
198
|
+
const port = process.env.PORT || '3000';
|
|
199
|
+
app.listen(port);
|
|
200
|
+
`;
|
|
201
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
202
|
+
const configFindings = findings.filter(f => f.checkType === 'configuration_check');
|
|
203
|
+
expect(configFindings).toHaveLength(0);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe('undefined_reference detection', () => {
|
|
207
|
+
it('detects import of deprecated "request" package', () => {
|
|
208
|
+
const code = `
|
|
209
|
+
import request from 'request';
|
|
210
|
+
request.get('/api/data', callback);
|
|
211
|
+
`;
|
|
212
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
213
|
+
const refFindings = findings.filter(f => f.checkType === 'undefined_reference');
|
|
214
|
+
expect(refFindings.length).toBeGreaterThan(0);
|
|
215
|
+
expect(refFindings[0].severity).toBe('high');
|
|
216
|
+
});
|
|
217
|
+
it('passes non-deprecated imports like express', () => {
|
|
218
|
+
const code = `
|
|
219
|
+
import express from 'express';
|
|
220
|
+
const app = express();
|
|
221
|
+
`;
|
|
222
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
223
|
+
const refFindings = findings.filter(f => f.checkType === 'undefined_reference');
|
|
224
|
+
expect(refFindings).toHaveLength(0);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
describe('type_annotation detection', () => {
|
|
228
|
+
it('detects exported function without return type in TypeScript', () => {
|
|
229
|
+
const code = `
|
|
230
|
+
export function foo(items) {
|
|
231
|
+
return items.length;
|
|
232
|
+
}
|
|
233
|
+
`;
|
|
234
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
235
|
+
const typeFindings = findings.filter(f => f.checkType === 'type_annotation');
|
|
236
|
+
expect(typeFindings.length).toBeGreaterThan(0);
|
|
237
|
+
expect(typeFindings[0].severity).toBe('medium');
|
|
238
|
+
});
|
|
239
|
+
it('passes exported function with return type in TypeScript', () => {
|
|
240
|
+
const code = `
|
|
241
|
+
export function foo(items: Item[]): number {
|
|
242
|
+
return items.length;
|
|
243
|
+
}
|
|
244
|
+
`;
|
|
245
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
246
|
+
const typeFindings = findings.filter(f => f.checkType === 'type_annotation');
|
|
247
|
+
expect(typeFindings).toHaveLength(0);
|
|
248
|
+
});
|
|
249
|
+
it('does not flag type_annotation in JavaScript', () => {
|
|
250
|
+
const code = `
|
|
251
|
+
export function foo(items) {
|
|
252
|
+
return items.length;
|
|
253
|
+
}
|
|
254
|
+
`;
|
|
255
|
+
const findings = runClaimlessChecks(code, 'javascript');
|
|
256
|
+
const typeFindings = findings.filter(f => f.checkType === 'type_annotation');
|
|
257
|
+
expect(typeFindings).toHaveLength(0);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
describe('connectivity_check detection', () => {
|
|
261
|
+
it('detects fetch() without timeout/signal', () => {
|
|
262
|
+
const code = `
|
|
263
|
+
async function getData() {
|
|
264
|
+
const res = await fetch('https://api.example.com/data');
|
|
265
|
+
return res.json();
|
|
266
|
+
}
|
|
267
|
+
`;
|
|
268
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
269
|
+
const connFindings = findings.filter(f => f.checkType === 'connectivity_check');
|
|
270
|
+
expect(connFindings.length).toBeGreaterThan(0);
|
|
271
|
+
expect(connFindings[0].severity).toBe('medium');
|
|
272
|
+
});
|
|
273
|
+
it('passes fetch() with AbortSignal.timeout', () => {
|
|
274
|
+
const code = `
|
|
275
|
+
async function getData() {
|
|
276
|
+
const res = await fetch('https://api.example.com/data', { signal: AbortSignal.timeout(5000) });
|
|
277
|
+
return res.json();
|
|
278
|
+
}
|
|
279
|
+
`;
|
|
280
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
281
|
+
const connFindings = findings.filter(f => f.checkType === 'connectivity_check');
|
|
282
|
+
expect(connFindings).toHaveLength(0);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
// ── Check 12: Async forEach ──────────────────────────────────
|
|
286
|
+
describe('async forEach detection (check 12)', () => {
|
|
287
|
+
it('detects async callback inside forEach', () => {
|
|
288
|
+
const code = `
|
|
289
|
+
const items = [1, 2, 3];
|
|
290
|
+
items.forEach(async (item) => {
|
|
291
|
+
await processItem(item);
|
|
292
|
+
});
|
|
293
|
+
`;
|
|
294
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
295
|
+
const f = findings.filter(f => f.checkType === 'error_handling' && f.evidence.toLowerCase().includes('foreach'));
|
|
296
|
+
expect(f.length).toBeGreaterThan(0);
|
|
297
|
+
expect(f[0].severity).toBe('critical');
|
|
298
|
+
});
|
|
299
|
+
it('passes for-of loop with await', () => {
|
|
300
|
+
const code = `
|
|
301
|
+
for (const item of items) {
|
|
302
|
+
await processItem(item);
|
|
303
|
+
}
|
|
304
|
+
`;
|
|
305
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
306
|
+
const f = findings.filter(f => f.checkType === 'error_handling' && f.evidence.toLowerCase().includes('foreach'));
|
|
307
|
+
expect(f).toHaveLength(0);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
// ── Check 13: Console in production ──────────────────────────
|
|
311
|
+
describe('console.log/debug detection (check 13)', () => {
|
|
312
|
+
it('detects console.log', () => {
|
|
313
|
+
const code = `
|
|
314
|
+
function handleClick() {
|
|
315
|
+
console.log("button clicked");
|
|
316
|
+
submitForm();
|
|
317
|
+
}
|
|
318
|
+
`;
|
|
319
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
320
|
+
const f = findings.filter(f => f.checkType === 'debug_artifact');
|
|
321
|
+
expect(f.length).toBeGreaterThan(0);
|
|
322
|
+
expect(f[0].severity).toBe('medium');
|
|
323
|
+
});
|
|
324
|
+
it('detects console.debug', () => {
|
|
325
|
+
const code = `
|
|
326
|
+
function loadData() {
|
|
327
|
+
console.debug("loading...");
|
|
328
|
+
return fetch('/api/data');
|
|
329
|
+
}
|
|
330
|
+
`;
|
|
331
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
332
|
+
const f = findings.filter(f => f.checkType === 'debug_artifact');
|
|
333
|
+
expect(f.length).toBeGreaterThan(0);
|
|
334
|
+
});
|
|
335
|
+
it('does not flag console.error or console.warn', () => {
|
|
336
|
+
const code = `
|
|
337
|
+
function handleError(err: Error) {
|
|
338
|
+
console.error("Fatal:", err.message);
|
|
339
|
+
console.warn("Deprecation notice");
|
|
340
|
+
}
|
|
341
|
+
`;
|
|
342
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
343
|
+
const f = findings.filter(f => f.checkType === 'debug_artifact');
|
|
344
|
+
expect(f).toHaveLength(0);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
// ── Check 14: Loose equality ─────────────────────────────────
|
|
348
|
+
describe('loose equality detection (check 14)', () => {
|
|
349
|
+
it('detects == null', () => {
|
|
350
|
+
const code = `
|
|
351
|
+
function check(x: unknown) {
|
|
352
|
+
if (x == null) {
|
|
353
|
+
return 'missing';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
`;
|
|
357
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
358
|
+
const f = findings.filter(f => f.checkType === 'equality_check');
|
|
359
|
+
expect(f.length).toBeGreaterThan(0);
|
|
360
|
+
expect(f[0].severity).toBe('medium');
|
|
361
|
+
});
|
|
362
|
+
it('detects != undefined', () => {
|
|
363
|
+
const code = `
|
|
364
|
+
function validate(y: string | undefined) {
|
|
365
|
+
if (y != undefined) {
|
|
366
|
+
process(y);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
`;
|
|
370
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
371
|
+
const f = findings.filter(f => f.checkType === 'equality_check');
|
|
372
|
+
expect(f.length).toBeGreaterThan(0);
|
|
373
|
+
});
|
|
374
|
+
it('passes strict equality', () => {
|
|
375
|
+
const code = `
|
|
376
|
+
function check(x: unknown) {
|
|
377
|
+
if (x === null || x === undefined) {
|
|
378
|
+
return 'missing';
|
|
379
|
+
}
|
|
380
|
+
if (x !== null) {
|
|
381
|
+
process(x);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
`;
|
|
385
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
386
|
+
const f = findings.filter(f => f.checkType === 'equality_check');
|
|
387
|
+
expect(f).toHaveLength(0);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
// ── Check 15: RegExp injection ───────────────────────────────
|
|
391
|
+
describe('RegExp injection detection (check 15)', () => {
|
|
392
|
+
it('detects new RegExp with variable', () => {
|
|
393
|
+
const code = `
|
|
394
|
+
function search(userInput: string, text: string) {
|
|
395
|
+
const re = new RegExp(userInput);
|
|
396
|
+
return re.test(text);
|
|
397
|
+
}
|
|
398
|
+
`;
|
|
399
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
400
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('regexp'));
|
|
401
|
+
expect(f.length).toBeGreaterThan(0);
|
|
402
|
+
expect(f[0].severity).toBe('critical');
|
|
403
|
+
});
|
|
404
|
+
it('passes new RegExp with string literal', () => {
|
|
405
|
+
const code = `
|
|
406
|
+
const emailPattern = new RegExp("^[a-zA-Z0-9.]+@example\\\\.com$");
|
|
407
|
+
const isValid = emailPattern.test(input);
|
|
408
|
+
`;
|
|
409
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
410
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('regexp'));
|
|
411
|
+
expect(f).toHaveLength(0);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
// ── Check 16: Promise.all without error handling ─────────────
|
|
415
|
+
describe('Promise.all without error handling (check 16)', () => {
|
|
416
|
+
it('detects Promise.all without try/catch', () => {
|
|
417
|
+
const code = `
|
|
418
|
+
async function loadAll(urls: string[]) {
|
|
419
|
+
const promises = urls.map(url => fetch(url));
|
|
420
|
+
const results = await Promise.all(promises);
|
|
421
|
+
return results;
|
|
422
|
+
}
|
|
423
|
+
`;
|
|
424
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
425
|
+
const f = findings.filter(f => f.checkType === 'error_handling' && f.evidence.toLowerCase().includes('promise.all'));
|
|
426
|
+
expect(f.length).toBeGreaterThan(0);
|
|
427
|
+
expect(f[0].severity).toBe('high');
|
|
428
|
+
});
|
|
429
|
+
it('passes Promise.all inside try/catch', () => {
|
|
430
|
+
const code = `
|
|
431
|
+
async function loadAll(urls: string[]) {
|
|
432
|
+
try {
|
|
433
|
+
const promises = urls.map(url => fetch(url));
|
|
434
|
+
const results = await Promise.all(promises);
|
|
435
|
+
return results;
|
|
436
|
+
} catch (e) {
|
|
437
|
+
handleError(e);
|
|
438
|
+
return [];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
`;
|
|
442
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
443
|
+
const f = findings.filter(f => f.checkType === 'error_handling' && f.evidence.toLowerCase().includes('promise.all'));
|
|
444
|
+
expect(f).toHaveLength(0);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
// ── Check 17: parseInt without radix ─────────────────────────
|
|
448
|
+
describe('parseInt without radix (check 17)', () => {
|
|
449
|
+
it('detects parseInt with single argument', () => {
|
|
450
|
+
const code = `
|
|
451
|
+
function parseAge(input: string) {
|
|
452
|
+
const age = parseInt(input);
|
|
453
|
+
return age;
|
|
454
|
+
}
|
|
455
|
+
`;
|
|
456
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
457
|
+
const f = findings.filter(f => f.checkType === 'api_misuse' && f.evidence.toLowerCase().includes('parseint'));
|
|
458
|
+
expect(f.length).toBeGreaterThan(0);
|
|
459
|
+
expect(f[0].severity).toBe('medium');
|
|
460
|
+
});
|
|
461
|
+
it('passes parseInt with radix', () => {
|
|
462
|
+
const code = `
|
|
463
|
+
function parseAge(input: string) {
|
|
464
|
+
const age = parseInt(input, 10);
|
|
465
|
+
return age;
|
|
466
|
+
}
|
|
467
|
+
`;
|
|
468
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
469
|
+
const f = findings.filter(f => f.checkType === 'api_misuse' && f.evidence.toLowerCase().includes('parseint'));
|
|
470
|
+
expect(f).toHaveLength(0);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
// ── Check 18: Fetch body on GET ──────────────────────────────
|
|
474
|
+
describe('fetch body on GET (check 18)', () => {
|
|
475
|
+
it('detects body in GET request', () => {
|
|
476
|
+
const code = `
|
|
477
|
+
async function getData(filters: object) {
|
|
478
|
+
const res = await fetch('/api/data', {
|
|
479
|
+
method: 'GET',
|
|
480
|
+
body: JSON.stringify(filters)
|
|
481
|
+
});
|
|
482
|
+
return res.json();
|
|
483
|
+
}
|
|
484
|
+
`;
|
|
485
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
486
|
+
const f = findings.filter(f => f.checkType === 'api_misuse' && f.evidence.toLowerCase().includes('body'));
|
|
487
|
+
expect(f.length).toBeGreaterThan(0);
|
|
488
|
+
expect(f[0].severity).toBe('high');
|
|
489
|
+
});
|
|
490
|
+
it('passes body in POST request', () => {
|
|
491
|
+
const code = `
|
|
492
|
+
async function createItem(data: object) {
|
|
493
|
+
const res = await fetch('/api/items', {
|
|
494
|
+
method: 'POST',
|
|
495
|
+
body: JSON.stringify(data),
|
|
496
|
+
headers: { 'Content-Type': 'application/json' }
|
|
497
|
+
});
|
|
498
|
+
return res.json();
|
|
499
|
+
}
|
|
500
|
+
`;
|
|
501
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
502
|
+
const f = findings.filter(f => f.checkType === 'api_misuse' && f.evidence.toLowerCase().includes('body') && f.evidence.toLowerCase().includes('get'));
|
|
503
|
+
expect(f).toHaveLength(0);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
// ── Check 19: addEventListener without cleanup ───────────────
|
|
507
|
+
describe('addEventListener without cleanup (check 19)', () => {
|
|
508
|
+
it('detects addEventListener in useEffect without removeEventListener', () => {
|
|
509
|
+
const code = `
|
|
510
|
+
useEffect(() => {
|
|
511
|
+
window.addEventListener('resize', handleResize);
|
|
512
|
+
}, []);
|
|
513
|
+
`;
|
|
514
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
515
|
+
const f = findings.filter(f => f.checkType === 'resource_leak');
|
|
516
|
+
expect(f.length).toBeGreaterThan(0);
|
|
517
|
+
expect(f[0].severity).toBe('high');
|
|
518
|
+
});
|
|
519
|
+
it('passes addEventListener with cleanup', () => {
|
|
520
|
+
const code = `
|
|
521
|
+
useEffect(() => {
|
|
522
|
+
window.addEventListener('resize', handleResize);
|
|
523
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
524
|
+
}, []);
|
|
525
|
+
`;
|
|
526
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
527
|
+
const f = findings.filter(f => f.checkType === 'resource_leak');
|
|
528
|
+
expect(f).toHaveLength(0);
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
// ── Check 20: Empty catch block ──────────────────────────────
|
|
532
|
+
describe('empty catch block (check 20)', () => {
|
|
533
|
+
it('detects empty catch block', () => {
|
|
534
|
+
const code = `
|
|
535
|
+
function safeParse(json: string) {
|
|
536
|
+
try {
|
|
537
|
+
return JSON.parse(json);
|
|
538
|
+
} catch (e) {}
|
|
539
|
+
}
|
|
540
|
+
`;
|
|
541
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
542
|
+
const f = findings.filter(f => f.checkType === 'error_handling' && f.evidence.toLowerCase().includes('empty catch'));
|
|
543
|
+
expect(f.length).toBeGreaterThan(0);
|
|
544
|
+
expect(f[0].severity).toBe('high');
|
|
545
|
+
});
|
|
546
|
+
it('passes catch with error handling', () => {
|
|
547
|
+
const code = `
|
|
548
|
+
function safeParse(json: string) {
|
|
549
|
+
try {
|
|
550
|
+
return JSON.parse(json);
|
|
551
|
+
} catch (e) {
|
|
552
|
+
console.error('Parse failed:', e);
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
`;
|
|
557
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
558
|
+
const f = findings.filter(f => f.checkType === 'error_handling' && f.evidence.toLowerCase().includes('empty catch'));
|
|
559
|
+
expect(f).toHaveLength(0);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
// ── Check 21: dangerouslySetInnerHTML ────────────────────────
|
|
563
|
+
describe('dangerouslySetInnerHTML (check 21)', () => {
|
|
564
|
+
it('detects dangerouslySetInnerHTML', () => {
|
|
565
|
+
const code = `
|
|
566
|
+
function RichContent({ html }: { html: string }) {
|
|
567
|
+
return <div dangerouslySetInnerHTML={{ __html: html }} />;
|
|
568
|
+
}
|
|
569
|
+
`;
|
|
570
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
571
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('dangerously'));
|
|
572
|
+
expect(f.length).toBeGreaterThan(0);
|
|
573
|
+
expect(f[0].severity).toBe('critical');
|
|
574
|
+
});
|
|
575
|
+
it('passes safe JSX', () => {
|
|
576
|
+
const code = `
|
|
577
|
+
function SafeContent({ text }: { text: string }) {
|
|
578
|
+
return <div>{text}</div>;
|
|
579
|
+
}
|
|
580
|
+
`;
|
|
581
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
582
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('dangerously'));
|
|
583
|
+
expect(f).toHaveLength(0);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
// ── Check 22: Hardcoded http:// URLs ─────────────────────────
|
|
587
|
+
describe('hardcoded http:// URLs (check 22)', () => {
|
|
588
|
+
it('detects hardcoded http:// URL', () => {
|
|
589
|
+
const code = `
|
|
590
|
+
const API_BASE = "http://example.com/api";
|
|
591
|
+
const res = await fetch(API_BASE + "/users");
|
|
592
|
+
`;
|
|
593
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
594
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('http://'));
|
|
595
|
+
expect(f.length).toBeGreaterThan(0);
|
|
596
|
+
expect(f[0].severity).toBe('medium');
|
|
597
|
+
});
|
|
598
|
+
it('passes https:// URLs', () => {
|
|
599
|
+
const code = `
|
|
600
|
+
const API_BASE = "https://example.com/api";
|
|
601
|
+
const res = await fetch(API_BASE + "/users");
|
|
602
|
+
`;
|
|
603
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
604
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('http://'));
|
|
605
|
+
expect(f).toHaveLength(0);
|
|
606
|
+
});
|
|
607
|
+
it('passes http://localhost', () => {
|
|
608
|
+
const code = `
|
|
609
|
+
const DEV_API = "http://localhost:3000/api";
|
|
610
|
+
const res = await fetch(DEV_API + "/health");
|
|
611
|
+
`;
|
|
612
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
613
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('http://'));
|
|
614
|
+
expect(f).toHaveLength(0);
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
// ── Check 23: document.write ─────────────────────────────────
|
|
618
|
+
describe('document.write (check 23)', () => {
|
|
619
|
+
it('detects document.write', () => {
|
|
620
|
+
const code = `
|
|
621
|
+
function renderPage(content: string) {
|
|
622
|
+
document.write("<h1>" + content + "</h1>");
|
|
623
|
+
}
|
|
624
|
+
`;
|
|
625
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
626
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('document.write'));
|
|
627
|
+
expect(f.length).toBeGreaterThan(0);
|
|
628
|
+
expect(f[0].severity).toBe('medium');
|
|
629
|
+
});
|
|
630
|
+
it('passes safe DOM manipulation', () => {
|
|
631
|
+
const code = `
|
|
632
|
+
function renderPage(content: string) {
|
|
633
|
+
const el = document.getElementById("root");
|
|
634
|
+
if (el) { el.textContent = content; }
|
|
635
|
+
}
|
|
636
|
+
`;
|
|
637
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
638
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('document.write'));
|
|
639
|
+
expect(f).toHaveLength(0);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
// ── Check 24: setTimeout with string ─────────────────────────
|
|
643
|
+
describe('setTimeout with string (check 24)', () => {
|
|
644
|
+
it('detects setTimeout with string argument', () => {
|
|
645
|
+
const code = `
|
|
646
|
+
function delayedGreeting(name: string) {
|
|
647
|
+
setTimeout("alert('Hello')", 1000);
|
|
648
|
+
}
|
|
649
|
+
`;
|
|
650
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
651
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('settimeout'));
|
|
652
|
+
expect(f.length).toBeGreaterThan(0);
|
|
653
|
+
expect(f[0].severity).toBe('critical');
|
|
654
|
+
});
|
|
655
|
+
it('passes setTimeout with function', () => {
|
|
656
|
+
const code = `
|
|
657
|
+
function delayedGreeting(name: string) {
|
|
658
|
+
setTimeout(() => alert('Hello ' + name), 1000);
|
|
659
|
+
}
|
|
660
|
+
`;
|
|
661
|
+
const findings = runClaimlessChecks(code, 'typescript');
|
|
662
|
+
const f = findings.filter(f => f.checkType === 'security_control' && f.evidence.toLowerCase().includes('settimeout'));
|
|
663
|
+
expect(f).toHaveLength(0);
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
//# sourceMappingURL=formal-verifier-claimless.test.js.map
|