opmsec 0.1.52 → 0.1.53
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.
|
|
3
|
+
"version": "0.1.53",
|
|
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": "
|
|
27
|
-
"react-devtools-core": "
|
|
28
|
-
"terminal-image": "
|
|
29
|
-
"viem": "
|
|
26
|
+
"react": "18.3.1",
|
|
27
|
+
"react-devtools-core": "6.1.5",
|
|
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": "
|
|
36
|
+
"signature": "0x9483cfcd3b920b1626e8055ca922ba69b04497d9d9234e4b9111439ad36d85b9028965909c76ee9af8bec3c8740575a38f2670bb1ea424210971a29368250fc21c",
|
|
36
37
|
"author": "0x2a3942EbDd8c5ea3E66D3fC4301F56d0F15d4bE2",
|
|
37
38
|
"ensName": "djpaiethg.eth",
|
|
38
|
-
"checksum": "
|
|
39
|
+
"checksum": "0xba72bf9125deddae2758f962c6c62567d6d23b819a1f7d716ba679b19287b4ef"
|
|
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
|
|
51
|
-
...deps.map(([n, v]) => ({ n, v:
|
|
52
|
-
...devDeps.map(([n, v]) => ({ n, 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' ?
|
|
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>('
|
|
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
|
|
48
|
-
...deps.map(([n, v]) => ({ n, v:
|
|
49
|
-
...devDeps.map(([n, v]) => ({ n, 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(
|
|
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}
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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} />
|