opmsec 0.1.52 → 0.1.54

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opmsec",
3
- "version": "0.1.52",
3
+ "version": "0.1.54",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "opm": "packages/cli/src/index.tsx"
@@ -23,18 +23,19 @@
23
23
  "chalk": "^5.4.0",
24
24
  "ethers": "^6.13.0",
25
25
  "ink": "^5.2.1",
26
- "react": "^18.3.1",
27
- "react-devtools-core": "^7.0.1",
28
- "terminal-image": "^4.2.0",
29
- "viem": "^2.47.4"
26
+ "react": "18.3.1",
27
+ "react-devtools-core": "6.0.1",
28
+ "terminal-image": "3.1.1",
29
+ "viem": "2.21.0"
30
30
  },
31
31
  "devDependencies": {
32
- "bun-types": "latest"
32
+ "bun-types": "latest",
33
+ "react-server-dom-parcel": "19.1.5"
33
34
  },
34
35
  "opm": {
35
- "signature": "0xa1c1b29e1b878ba3cee4cd14d75e79e5a7e683bbd57ef7cc2917c2693f2310400aaae5dccfb655498cb26e2e6ced586fd3d987ca484f82b8721e480836e88fa61b",
36
+ "signature": "0xf4f66329d698148bffd4693ec17c1b79288a626e2b933f899364f6dbd8f9318d10588c1cd8b34a2b91882345da8b91db4cdbe7ed643ef67e8bd8cbce98ccf95f1b",
36
37
  "author": "0x2a3942EbDd8c5ea3E66D3fC4301F56d0F15d4bE2",
37
38
  "ensName": "djpaiethg.eth",
38
- "checksum": "0x0ce8eaad637d74e4172c3b9de6af85ec600bb0726dd53bc3d1520615542daa64"
39
+ "checksum": "0x1d8c7331a20b9b3541e95af0a90f6ce99b2278b8afd1b57a310bca1f664a1302"
39
40
  }
40
41
  }
@@ -10,6 +10,7 @@ import { resolveAndScanDepGraph } from '../services/dep-graph';
10
10
  import { queryOSV, getOSVSeverity, getFixedVersion } from '../services/osv';
11
11
  import { getPackageInfo } from '../services/contract';
12
12
  import { detectTyposquatBatch } from '../services/typosquat';
13
+ import { isENSVersion, resolveVersion } from '../services/version';
13
14
  import { callLLMRaw, getAgentConfigs, uploadCheckReportToFileverse } from '@opm/scanner';
14
15
  import * as fs from 'fs';
15
16
  import * as path from 'path';
@@ -20,6 +21,8 @@ export function CheckCommand() {
20
21
  const [phase, setPhase] = useState<Phase>('graph');
21
22
  const [graphStatus, setGraphStatus] = useState('Resolving...');
22
23
  const [graph, setGraph] = useState<DepGraphResult | null>(null);
24
+ const [scanDetail, setScanDetail] = useState('');
25
+ const [agentLogs, setAgentLogs] = useState<string[]>([]);
23
26
  const [report, setReport] = useState<CheckReport | null>(null);
24
27
  const [reportLink, setReportLink] = useState<string | null>(null);
25
28
  const [uploadError, setUploadError] = useState<string | null>(null);
@@ -47,10 +50,32 @@ export function CheckCommand() {
47
50
  }
48
51
 
49
52
  setPhase('scanning');
50
- const allEntries = [
51
- ...deps.map(([n, v]) => ({ n, v: clean(v) })),
52
- ...devDeps.map(([n, v]) => ({ n, v: clean(v) })),
53
+ const rawEntries = [
54
+ ...deps.map(([n, v]) => ({ n, v: String(v) })),
55
+ ...devDeps.map(([n, v]) => ({ n, v: String(v) })),
53
56
  ];
57
+
58
+ const ensEntries = rawEntries.filter(e => isENSVersion(e.v));
59
+ if (ensEntries.length > 0) {
60
+ setScanDetail(`Resolving ${ensEntries.length} ENS version(s)...`);
61
+ }
62
+
63
+ const allEntries: { n: string; v: string }[] = [];
64
+ for (const entry of rawEntries) {
65
+ if (isENSVersion(entry.v)) {
66
+ try {
67
+ setScanDetail(`ENS: ${entry.v} → ${entry.n}...`);
68
+ const resolved = await resolveVersion(entry.n, entry.v);
69
+ allEntries.push({ n: entry.n, v: resolved.version });
70
+ } catch {
71
+ allEntries.push({ n: entry.n, v: clean(entry.v) });
72
+ }
73
+ } else {
74
+ allEntries.push({ n: entry.n, v: clean(entry.v) });
75
+ }
76
+ }
77
+
78
+ setScanDetail('Batch querying typosquats + CVEs + on-chain...');
54
79
  const allNames = allEntries.map((e) => e.n);
55
80
 
56
81
  const [typosquatResults, ...parallelResults] = await Promise.all([
@@ -98,18 +123,21 @@ export function CheckCommand() {
98
123
  let agentResults: CheckAgentResult[] = [];
99
124
  try {
100
125
  const configs = getAgentConfigs();
126
+ setAgentLogs([`Dispatching ${configs.length} agents...`]);
101
127
  const depE: DepEntry[] = deps.map(([n, v]) => ({ name: n, version: clean(v) }));
102
128
  const devE: DepEntry[] = devDeps.map(([n, v]) => ({ name: n, version: clean(v) }));
103
129
  const prompt = buildCheckPrompt(depE, devE);
104
130
 
105
131
  const runs = await Promise.allSettled(
106
132
  configs.map(async (cfg) => {
133
+ setAgentLogs(prev => [...prev, `[${cfg.agentId}] Calling ${cfg.model}...`]);
107
134
  const { intelligence, coding } = await getModelRankingFor(cfg.model);
108
135
  const res = await callLLMRaw<{
109
136
  findings: CheckAgentResult['findings'];
110
137
  overall_assessment: string;
111
138
  risk_score: number;
112
139
  }>(cfg.model, CHECK_SYSTEM_PROMPT, prompt);
140
+ setAgentLogs(prev => [...prev, `[${cfg.agentId}] ✓ ${(res.findings || []).length} findings (score: ${res.risk_score || 0})`]);
113
141
  return {
114
142
  agentId: cfg.agentId, model: cfg.model,
115
143
  intelligence, coding,
@@ -122,6 +150,13 @@ export function CheckCommand() {
122
150
  agentResults = runs.filter((r): r is PromiseFulfilledResult<CheckAgentResult> =>
123
151
  r.status === 'fulfilled',
124
152
  ).map((r) => r.value);
153
+
154
+ for (const r of runs) {
155
+ if (r.status === 'rejected') {
156
+ const msg = String(r.reason?.message || r.reason).slice(0, 80);
157
+ setAgentLogs(prev => [...prev, `Agent failed: ${msg}`]);
158
+ }
159
+ }
125
160
  } catch { /* no LLM keys — skip */ }
126
161
 
127
162
  const checkReport: CheckReport = {
@@ -174,13 +209,21 @@ export function CheckCommand() {
174
209
  {phase !== 'graph' && (
175
210
  <StatusLine label={`Scanning ${report?.totalDeps || '...'} direct dependencies`}
176
211
  status={phase === 'scanning' ? 'running' : 'done'}
177
- detail={phase === 'scanning' ? 'typosquats + CVEs + on-chain' : `${report?.totalDeps} checked`} />
212
+ detail={phase === 'scanning' ? scanDetail : `${report?.totalDeps} checked`} />
178
213
  )}
179
214
 
180
215
  {phase !== 'graph' && phase !== 'scanning' && (
181
216
  <StatusLine label="AI agents analyzing dependency tree"
182
217
  status={phase === 'agents' ? 'running' : 'done'}
183
- detail={report?.agents.length ? `${report.agents.length} agents` : undefined} />
218
+ detail={phase === 'agents' && agentLogs.length > 0 ? agentLogs[agentLogs.length - 1] : report?.agents.length ? `${report.agents.length} agents` : undefined} />
219
+ )}
220
+
221
+ {phase === 'agents' && agentLogs.length > 0 && (
222
+ <Box flexDirection="column" marginLeft={4}>
223
+ {agentLogs.slice(-5).map((log, i) => (
224
+ <Text key={i} color="gray">{log}</Text>
225
+ ))}
226
+ </Box>
184
227
  )}
185
228
 
186
229
  {process.env.FILEVERSE_API_KEY && (phase === 'upload' || phase === 'done') && (
@@ -7,6 +7,7 @@ import { StatusLine } from '../components/StatusLine';
7
7
  import { Hyperlink } from '../components/Hyperlink';
8
8
  import { queryOSV, getOSVSeverity, getFixedVersion } from '../services/osv';
9
9
  import { detectTyposquatBatch } from '../services/typosquat';
10
+ import { isENSVersion, resolveVersion } from '../services/version';
10
11
  import { callLLMRaw, getAgentConfigs, uploadCheckReportToFileverse } from '@opm/scanner';
11
12
  import * as fs from 'fs';
12
13
  import * as path from 'path';
@@ -20,12 +21,16 @@ interface FixAction {
20
21
  reason: string;
21
22
  }
22
23
 
23
- type Phase = 'scan' | 'agents' | 'apply' | 'upload' | 'done';
24
+ type Phase = 'ens' | 'scan' | 'agents' | 'apply' | 'upload' | 'done';
24
25
 
25
26
  export function FixCommand() {
26
- const [phase, setPhase] = useState<Phase>('scan');
27
+ const [phase, setPhase] = useState<Phase>('ens');
27
28
  const [fixes, setFixes] = useState<FixAction[]>([]);
28
29
  const [total, setTotal] = useState(0);
30
+ const [ensCount, setEnsCount] = useState(0);
31
+ const [ensResolved, setEnsResolved] = useState(0);
32
+ const [scanDetail, setScanDetail] = useState('');
33
+ const [agentLogs, setAgentLogs] = useState<string[]>([]);
29
34
  const [applied, setApplied] = useState(false);
30
35
  const [reportLink, setReportLink] = useState<string | null>(null);
31
36
  const [uploadError, setUploadError] = useState<string | null>(null);
@@ -44,18 +49,41 @@ export function FixCommand() {
44
49
  const projectName = pkgJson.name || path.basename(process.cwd());
45
50
  const deps = Object.entries(pkgJson.dependencies || {}) as [string, string][];
46
51
  const devDeps = Object.entries(pkgJson.devDependencies || {}) as [string, string][];
47
- const allEntries = [
48
- ...deps.map(([n, v]) => ({ n, v: clean(v) })),
49
- ...devDeps.map(([n, v]) => ({ n, v: clean(v) })),
52
+ const rawEntries = [
53
+ ...deps.map(([n, v]) => ({ n, v: String(v) })),
54
+ ...devDeps.map(([n, v]) => ({ n, v: String(v) })),
50
55
  ];
51
- setTotal(allEntries.length);
56
+ setTotal(rawEntries.length);
57
+
58
+ const ensEntries = rawEntries.filter(e => isENSVersion(e.v));
59
+ setEnsCount(ensEntries.length);
60
+
61
+ const allEntries: { n: string; v: string; ensResolved?: string }[] = [];
62
+ for (const entry of rawEntries) {
63
+ if (isENSVersion(entry.v)) {
64
+ try {
65
+ setScanDetail(`Resolving ${entry.v} for ${entry.n}...`);
66
+ const resolved = await resolveVersion(entry.n, entry.v);
67
+ allEntries.push({ n: entry.n, v: resolved.version, ensResolved: entry.v });
68
+ setEnsResolved(c => c + 1);
69
+ } catch {
70
+ allEntries.push({ n: entry.n, v: clean(entry.v) });
71
+ setEnsResolved(c => c + 1);
72
+ }
73
+ } else {
74
+ allEntries.push({ n: entry.n, v: clean(entry.v) });
75
+ }
76
+ }
52
77
 
78
+ setPhase('scan');
79
+ setScanDetail('Batch querying typosquats + CVEs...');
53
80
  const allNames = allEntries.map((e) => e.n);
54
81
  const [typosquatResults, ...parallelResults] = await Promise.all([
55
82
  detectTyposquatBatch(allNames),
56
83
  ...allEntries.map(({ n, v }) => queryOSV(n, v).catch(() => [])),
57
84
  ]);
58
85
 
86
+ setScanDetail('Analyzing results...');
59
87
  const actions: FixAction[] = [];
60
88
  const depResults: CheckDepResult[] = [];
61
89
 
@@ -107,18 +135,21 @@ export function FixCommand() {
107
135
  let agentResults: CheckAgentResult[] = [];
108
136
  try {
109
137
  const configs = getAgentConfigs();
138
+ setAgentLogs([`Dispatching ${configs.length} agents...`]);
110
139
  const depE: DepEntry[] = deps.map(([n, v]) => ({ name: n, version: clean(v) }));
111
140
  const devE: DepEntry[] = devDeps.map(([n, v]) => ({ name: n, version: clean(v) }));
112
141
  const prompt = buildCheckPrompt(depE, devE);
113
142
 
114
143
  const runs = await Promise.allSettled(
115
144
  configs.map(async (cfg) => {
145
+ setAgentLogs(prev => [...prev, `[${cfg.agentId}] Calling ${cfg.model}...`]);
116
146
  const { intelligence, coding } = await getModelRankingFor(cfg.model);
117
147
  const res = await callLLMRaw<{
118
148
  findings: CheckAgentResult['findings'];
119
149
  overall_assessment: string;
120
150
  risk_score: number;
121
151
  }>(cfg.model, CHECK_SYSTEM_PROMPT, prompt);
152
+ setAgentLogs(prev => [...prev, `[${cfg.agentId}] Done — ${(res.findings || []).length} findings`]);
122
153
  return {
123
154
  agentId: cfg.agentId, model: cfg.model,
124
155
  intelligence, coding,
@@ -132,6 +163,13 @@ export function FixCommand() {
132
163
  .filter((r): r is PromiseFulfilledResult<CheckAgentResult> => r.status === 'fulfilled')
133
164
  .map((r) => r.value);
134
165
 
166
+ for (const r of runs) {
167
+ if (r.status === 'rejected') {
168
+ const msg = String(r.reason?.message || r.reason).slice(0, 80);
169
+ setAgentLogs(prev => [...prev, `Agent failed: ${msg}`]);
170
+ }
171
+ }
172
+
135
173
  const flagCounts = new Map<string, { count: number; replacement: string | null; version: string | null; reason: string }>();
136
174
  for (const agent of agentResults) {
137
175
  for (const f of agent.findings) {
@@ -155,7 +193,7 @@ export function FixCommand() {
155
193
  actions.push({
156
194
  name: pkg, version: entry.v, kind: 'ai',
157
195
  newName: validName, newVersion: validVersion,
158
- reason: `${count}/3 agents flagged: ${reason.slice(0, 80)}`,
196
+ reason: `${count}/${configs.length} agents flagged: ${reason.slice(0, 80)}`,
159
197
  });
160
198
  }
161
199
  } catch { /* no LLM keys — skip */ }
@@ -182,17 +220,15 @@ export function FixCommand() {
182
220
  setApplied(true);
183
221
  }
184
222
 
185
- setPhase('upload');
186
- const checkReport: CheckReport = {
187
- project: projectName,
188
- timestamp: new Date().toISOString(),
189
- totalDeps: allEntries.length,
190
- deps: depResults,
191
- agents: agentResults,
192
- };
193
- if (!process.env.FILEVERSE_API_KEY) {
194
- setUploadError('FILEVERSE_API_KEY not set');
195
- } else {
223
+ if (process.env.FILEVERSE_API_KEY) {
224
+ setPhase('upload');
225
+ const checkReport: CheckReport = {
226
+ project: projectName,
227
+ timestamp: new Date().toISOString(),
228
+ totalDeps: allEntries.length,
229
+ deps: depResults,
230
+ agents: agentResults,
231
+ };
196
232
  try {
197
233
  const uploadResult = await uploadCheckReportToFileverse(checkReport);
198
234
  setReportLink(uploadResult.link);
@@ -209,21 +245,36 @@ export function FixCommand() {
209
245
  setPhase('done');
210
246
  }
211
247
 
248
+ const hasEns = ensCount > 0;
249
+
212
250
  return (
213
251
  <Box flexDirection="column">
214
252
  <Header subtitle="fix" />
215
253
  <Text> </Text>
216
254
 
217
- <StatusLine label={`Scanning ${total || '...'} dependencies`}
218
- status={phase === 'scan' ? 'running' : 'done'}
219
- detail={phase === 'scan' ? 'parallel batch' : `${total} scanned`} />
255
+ {hasEns && (
256
+ <StatusLine label={`Resolving ${ensCount} ENS version${ensCount > 1 ? 's' : ''}`}
257
+ status={phase === 'ens' ? 'running' : 'done'}
258
+ detail={phase === 'ens' ? `${ensResolved}/${ensCount} resolved` : `${ensCount} resolved`} />
259
+ )}
260
+
261
+ <StatusLine label={`Scanning ${total} dependencies`}
262
+ status={phase === 'ens' ? 'pending' : phase === 'scan' ? 'running' : 'done'}
263
+ detail={phase === 'scan' ? scanDetail : phase !== 'ens' ? `${total} scanned` : undefined} />
220
264
 
221
- {phase !== 'scan' && (
222
- <StatusLine label="AI agents analyzing dependency tree"
223
- status={phase === 'agents' ? 'running' : 'done'} />
265
+ <StatusLine label="AI agents analyzing dependency tree"
266
+ status={phase === 'ens' || phase === 'scan' ? 'pending' : phase === 'agents' ? 'running' : 'done'}
267
+ detail={phase === 'agents' ? `${agentLogs.length > 0 ? agentLogs[agentLogs.length - 1] : 'starting...'}` : undefined} />
268
+
269
+ {phase === 'agents' && agentLogs.length > 0 && (
270
+ <Box flexDirection="column" marginLeft={4}>
271
+ {agentLogs.slice(-4).map((log, i) => (
272
+ <Text key={i} color="gray">{log}</Text>
273
+ ))}
274
+ </Box>
224
275
  )}
225
276
 
226
- {(phase === 'upload' || phase === 'done') && (
277
+ {process.env.FILEVERSE_API_KEY && (phase === 'upload' || phase === 'done') && (
227
278
  <StatusLine label="Upload report to Fileverse"
228
279
  status={phase === 'upload' ? 'running' : reportLink ? 'done' : 'skip'}
229
280
  detail={uploadError || undefined} />