opmsec 0.1.3 → 0.1.5
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/.env.example +1 -0
- package/.husky/pre-commit +1 -0
- package/README.md +71 -275
- package/bun.lock +5 -5
- package/docs/architecture/agents.mdx +11 -59
- package/docs/architecture/benchmarks.mdx +20 -46
- package/docs/architecture/overview.mdx +31 -38
- package/docs/architecture/scanner.mdx +11 -37
- package/docs/cli/audit.mdx +9 -12
- package/docs/cli/check.mdx +12 -26
- package/docs/cli/fix.mdx +10 -30
- package/docs/cli/info.mdx +12 -19
- package/docs/cli/install.mdx +27 -39
- package/docs/cli/push.mdx +40 -57
- package/docs/cli/register-agent.mdx +21 -53
- package/docs/cli/view.mdx +12 -29
- package/docs/concepts/ens-records.mdx +44 -0
- package/docs/concepts/multi-agent-consensus.mdx +18 -36
- package/docs/concepts/on-chain-registry.mdx +22 -49
- package/docs/concepts/security-model.mdx +20 -52
- package/docs/concepts/zk-agent-verification.mdx +26 -64
- package/docs/contract/events.mdx +13 -74
- package/docs/contract/functions.mdx +40 -126
- package/docs/contract/overview.mdx +17 -36
- package/docs/introduction.mdx +22 -25
- package/docs/mint.json +3 -2
- package/docs/quickstart.mdx +34 -70
- package/docs/system-design.png +0 -0
- package/package.json +7 -6
- package/packages/cli/src/commands/author-view.tsx +87 -2
- package/packages/cli/src/commands/check.tsx +18 -5
- package/packages/cli/src/commands/fix.tsx +25 -12
- package/packages/cli/src/commands/info.tsx +92 -4
- package/packages/cli/src/commands/install.tsx +327 -23
- package/packages/cli/src/commands/push.tsx +112 -0
- package/packages/cli/src/commands/register-agent.tsx +72 -31
- package/packages/cli/src/index.tsx +7 -5
- package/packages/cli/src/services/ens-records.ts +525 -0
- package/packages/cli/src/services/version.ts +156 -5
- package/packages/core/src/benchmarks.ts +116 -0
- package/packages/core/src/constants.ts +18 -6
- package/packages/core/src/model-rankings.ts +40 -15
- package/packages/core/src/types.ts +10 -0
- package/packages/core/src/utils.ts +136 -1
- package/packages/scanner/src/index.ts +2 -1
- package/packages/scanner/src/queue/memory-queue.ts +7 -2
- package/packages/scanner/src/services/benchmark-runner.ts +86 -1
- package/packages/scanner/src/services/fileverse.ts +61 -12
- package/packages/scanner/src/services/openrouter.ts +18 -7
- package/packages/web/.next/BUILD_ID +1 -0
- package/packages/web/.next/app-path-routes-manifest.json +4 -0
- package/packages/web/.next/diagnostics/build-diagnostics.json +6 -0
- package/packages/web/.next/diagnostics/framework.json +1 -0
- package/packages/web/.next/export-marker.json +6 -0
- package/packages/web/.next/images-manifest.json +58 -0
- package/packages/web/.next/next-minimal-server.js.nft.json +1 -0
- package/packages/web/.next/next-server.js.nft.json +1 -0
- package/packages/web/.next/prerender-manifest.json +54 -4
- package/packages/web/.next/required-server-files.json +320 -0
- package/packages/web/.next/routes-manifest.json +53 -1
- package/packages/web/.next/server/app/_not-found/page.js +2 -0
- package/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- package/packages/web/.next/server/app/_not-found.html +1 -0
- package/packages/web/.next/server/app/_not-found.meta +8 -0
- package/packages/web/.next/server/app/_not-found.rsc +18 -0
- package/packages/web/.next/server/app/index.html +6 -0
- package/packages/web/.next/server/app/index.meta +7 -0
- package/packages/web/.next/server/app/index.rsc +22 -0
- package/packages/web/.next/server/app/page.js +24 -24
- package/packages/web/.next/server/app/page.js.nft.json +1 -0
- package/packages/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/packages/web/.next/server/chunks/611.js +6 -0
- package/packages/web/.next/server/chunks/778.js +30 -0
- package/packages/web/.next/server/functions-config-manifest.json +4 -0
- package/packages/web/.next/server/interception-route-rewrite-manifest.js +1 -1
- package/packages/web/.next/server/next-font-manifest.js +1 -1
- package/packages/web/.next/server/next-font-manifest.json +1 -1
- package/packages/web/.next/server/pages/404.html +1 -0
- package/packages/web/.next/server/pages/500.html +1 -0
- package/packages/web/.next/server/pages/_app.js +1 -0
- package/packages/web/.next/server/pages/_app.js.nft.json +1 -0
- package/packages/web/.next/server/pages/_document.js +1 -0
- package/packages/web/.next/server/pages/_document.js.nft.json +1 -0
- package/packages/web/.next/server/pages/_error.js +19 -0
- package/packages/web/.next/server/pages/_error.js.nft.json +1 -0
- package/packages/web/.next/server/webpack-runtime.js +2 -2
- package/packages/web/.next/static/0esGzFBCzREfVwijEGDfL/_buildManifest.js +1 -0
- package/packages/web/.next/static/0esGzFBCzREfVwijEGDfL/_ssgManifest.js +1 -0
- package/packages/web/.next/static/chunks/174-5b5efcb3b8efcc01.js +1 -0
- package/packages/web/.next/static/chunks/255-0dc49b7a6e8e5c05.js +1 -0
- package/packages/web/.next/static/chunks/4bd1b696-382748cc942d8a14.js +1 -0
- package/packages/web/.next/static/chunks/app/_not-found/page-0da542be7eb33a64.js +1 -0
- package/packages/web/.next/static/chunks/app/layout-de8e841104500505.js +1 -0
- package/packages/web/.next/static/chunks/app/layout.js +37 -7
- package/packages/web/.next/static/chunks/app/page-7e086379698b9fb0.js +1 -0
- package/packages/web/.next/static/chunks/app/page.js +297 -1
- package/packages/web/.next/static/chunks/framework-ac73abd125e371fe.js +1 -0
- package/packages/web/.next/static/chunks/main-4e8d71b5ef7ee7e3.js +1 -0
- package/packages/web/.next/static/chunks/main-app-dd261207182e5a23.js +1 -0
- package/packages/web/.next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
- package/packages/web/.next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
- package/packages/web/.next/static/chunks/webpack-0dcd67569eb46132.js +1 -0
- package/packages/web/.next/static/chunks/webpack.js +2 -2
- package/packages/web/.next/static/css/102562cf2d0ae9b0.css +3 -0
- package/packages/web/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- package/packages/web/.next/static/media/747892c23ea88013-s.woff2 +0 -0
- package/packages/web/.next/static/media/8d697b304b401681-s.woff2 +0 -0
- package/packages/web/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
- package/packages/web/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
- package/packages/web/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
- package/packages/web/.next/static/webpack/16f18baa938a434c.webpack.hot-update.json +1 -0
- package/packages/web/.next/static/webpack/5fe9fe8578f9c3d2.webpack.hot-update.json +1 -0
- package/packages/web/.next/static/webpack/73c7d02260cc80e4.webpack.hot-update.json +1 -0
- package/packages/web/.next/static/webpack/a2d85d19aa028de1.webpack.hot-update.json +1 -0
- package/packages/web/.next/static/webpack/app/{layout.73e341375c8d429e.hot-update.js → layout.16f18baa938a434c.hot-update.js} +1 -1
- package/packages/web/.next/static/webpack/app/{layout.6fee6306e0f98869.hot-update.js → layout.5fe9fe8578f9c3d2.hot-update.js} +1 -1
- package/packages/web/.next/static/webpack/app/layout.653e365406c0d9ac.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/layout.6800169a899e3a8b.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/layout.73c7d02260cc80e4.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/layout.a2d85d19aa028de1.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/page.653e365406c0d9ac.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/page.6800169a899e3a8b.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/page.73c7d02260cc80e4.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/app/page.a2d85d19aa028de1.hot-update.js +22 -0
- package/packages/web/.next/static/webpack/{webpack.6fee6306e0f98869.hot-update.js → webpack.16f18baa938a434c.hot-update.js} +2 -2
- package/packages/web/.next/static/webpack/{webpack.73e341375c8d429e.hot-update.js → webpack.5fe9fe8578f9c3d2.hot-update.js} +2 -2
- package/packages/web/.next/static/webpack/webpack.653e365406c0d9ac.hot-update.js +12 -0
- package/packages/web/.next/static/webpack/webpack.6800169a899e3a8b.hot-update.js +12 -0
- package/packages/web/.next/static/webpack/webpack.73c7d02260cc80e4.hot-update.js +12 -0
- package/packages/web/.next/static/webpack/webpack.a2d85d19aa028de1.hot-update.js +12 -0
- package/packages/web/.next/trace +2 -5
- package/packages/web/app/globals.css +197 -51
- package/packages/web/app/layout.tsx +6 -3
- package/packages/web/app/page.tsx +791 -309
- package/packages/web/bun.lock +66 -105
- package/packages/web/next.config.ts +8 -1
- package/packages/web/package.json +5 -2
- package/packages/web/postcss.config.mjs +2 -2
- package/packages/web/public/apple-icon.png +1 -0
- package/packages/web/public/dependency-bottleneck.png +0 -0
- package/packages/web/public/icon-dark-32x32.png +1 -0
- package/packages/web/public/icon-light-32x32.png +1 -0
- package/packages/web/public/icon.svg +1 -0
- package/packages/web/public/nextjs-cve-announcement.png +0 -0
- package/packages/web/public/phantomraven-npm-attack.png +0 -0
- package/packages/web/public/placeholder-logo.png +1 -0
- package/packages/web/public/placeholder-logo.svg +1 -0
- package/packages/web/public/placeholder-user.jpg +1 -0
- package/packages/web/public/placeholder.jpg +1 -0
- package/packages/web/public/placeholder.svg +1 -0
- package/packages/web/public/react-cve-meme.png +0 -0
- package/packages/web/public/wallet-drain-exploit.png +0 -0
- package/packages/web/styles/globals.css +125 -0
- package/packages/web/.next/server/vendor-chunks/@swc.js +0 -55
- package/packages/web/.next/server/vendor-chunks/next.js +0 -3010
- package/packages/web/.next/static/chunks/app-pages-internals.js +0 -182
- package/packages/web/.next/static/chunks/main-app.js +0 -1882
- package/packages/web/.next/static/css/app/layout.css +0 -1237
- package/packages/web/.next/static/webpack/633457081244afec._.hot-update.json +0 -1
- package/packages/web/.next/static/webpack/app/page.6fee6306e0f98869.hot-update.js +0 -22
- package/packages/web/.next/static/webpack/app/page.73e341375c8d429e.hot-update.js +0 -22
- package/packages/web/tailwind.config.ts +0 -48
- /package/packages/web/.next/static/chunks/{polyfills.js → polyfills-42372ed130431b0a.js} +0 -0
- /package/packages/web/.next/static/webpack/{6fee6306e0f98869.webpack.hot-update.json → 653e365406c0d9ac.webpack.hot-update.json} +0 -0
- /package/packages/web/.next/static/webpack/{73e341375c8d429e.webpack.hot-update.json → 6800169a899e3a8b.webpack.hot-update.json} +0 -0
|
@@ -10,7 +10,10 @@ import { verifyChecksum } from '../services/signature';
|
|
|
10
10
|
import { resolveENSName } from '../services/ens';
|
|
11
11
|
import { checkPackageWithChainPatrol } from '../services/chainpatrol';
|
|
12
12
|
import { queryOSV, getOSVSeverity, getFixedVersion, type OSVVulnerability } from '../services/osv';
|
|
13
|
-
import { resolveVersion } from '../services/version';
|
|
13
|
+
import { resolveVersion, findSafeVersion, isENSVersion, type ResolvedVersion } from '../services/version';
|
|
14
|
+
import { resolveAddress } from '../services/ens';
|
|
15
|
+
import { readOPMRecords, readPackageENSRecords } from '../services/ens-records';
|
|
16
|
+
import type { OPMENSRecords } from '@opm/core';
|
|
14
17
|
import { execSync } from 'child_process';
|
|
15
18
|
import * as fs from 'fs';
|
|
16
19
|
import * as path from 'path';
|
|
@@ -19,6 +22,7 @@ type StepStatus = 'pending' | 'running' | 'done' | 'error' | 'skip';
|
|
|
19
22
|
|
|
20
23
|
interface Steps {
|
|
21
24
|
resolve: StepStatus;
|
|
25
|
+
ens: StepStatus;
|
|
22
26
|
cve: StepStatus;
|
|
23
27
|
onchain: StepStatus;
|
|
24
28
|
signature: StepStatus;
|
|
@@ -31,6 +35,7 @@ interface SecurityResult {
|
|
|
31
35
|
name: string;
|
|
32
36
|
version: string;
|
|
33
37
|
resolvedVersion: string;
|
|
38
|
+
resolved?: ResolvedVersion;
|
|
34
39
|
cves: OSVVulnerability[];
|
|
35
40
|
info?: OnChainPackageInfo;
|
|
36
41
|
signatureValid?: boolean;
|
|
@@ -40,6 +45,9 @@ interface SecurityResult {
|
|
|
40
45
|
warning: boolean;
|
|
41
46
|
blockReason?: string;
|
|
42
47
|
safestVersion?: string;
|
|
48
|
+
autoBumped?: boolean;
|
|
49
|
+
autoBumpedFrom?: string;
|
|
50
|
+
autoBumpReason?: string;
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
interface InstallCommandProps {
|
|
@@ -76,12 +84,17 @@ export function InstallCommand({ packageName, version }: InstallCommandProps) {
|
|
|
76
84
|
// ─── Single package install with full security pipeline ───────────────────────
|
|
77
85
|
|
|
78
86
|
function SingleInstall({ packageName, version }: { packageName: string; version?: string }) {
|
|
87
|
+
const isEns = version ? isENSVersion(version) : false;
|
|
88
|
+
|
|
79
89
|
const [steps, setSteps] = useState<Steps>({
|
|
80
|
-
resolve: 'pending',
|
|
90
|
+
resolve: 'pending', ens: isEns ? 'pending' : 'skip',
|
|
91
|
+
cve: 'pending', onchain: 'pending',
|
|
81
92
|
signature: 'pending', chainpatrol: 'pending', report: 'pending',
|
|
82
93
|
install: 'pending',
|
|
83
94
|
});
|
|
84
95
|
const [result, setResult] = useState<SecurityResult | null>(null);
|
|
96
|
+
const [ensDetail, setEnsDetail] = useState<string | undefined>(undefined);
|
|
97
|
+
const [ensRecords, setEnsRecords] = useState<OPMENSRecords>({});
|
|
85
98
|
const [error, setError] = useState<string | null>(null);
|
|
86
99
|
const [done, setDone] = useState(false);
|
|
87
100
|
|
|
@@ -99,11 +112,51 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
99
112
|
blocked: false, warning: false,
|
|
100
113
|
};
|
|
101
114
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
// ── Resolve version (+ ENS if applicable) ──
|
|
116
|
+
if (isEns) {
|
|
117
|
+
update('resolve', 'done');
|
|
118
|
+
update('ens', 'running');
|
|
119
|
+
try {
|
|
120
|
+
const resolved = await resolveVersion(packageName, r.version, (msg) => setEnsDetail(msg));
|
|
121
|
+
r.resolved = resolved;
|
|
122
|
+
r.resolvedVersion = resolved.version;
|
|
123
|
+
r.ensName = resolved.ensName;
|
|
124
|
+
setResult({ ...r });
|
|
125
|
+
setEnsDetail(`${resolved.ensName} → v${resolved.version} (${resolved.reason})`);
|
|
126
|
+
|
|
127
|
+
if (resolved.ensName) {
|
|
128
|
+
const [opmRecs, pkgRecs] = await Promise.allSettled([
|
|
129
|
+
readOPMRecords(resolved.ensName),
|
|
130
|
+
readPackageENSRecords(resolved.ensName, packageName),
|
|
131
|
+
]);
|
|
132
|
+
const merged: OPMENSRecords = {};
|
|
133
|
+
if (pkgRecs.status === 'fulfilled') Object.assign(merged, pkgRecs.value);
|
|
134
|
+
if (opmRecs.status === 'fulfilled') {
|
|
135
|
+
for (const [k, v] of Object.entries(opmRecs.value)) {
|
|
136
|
+
if (v && !(merged as any)[k]) (merged as any)[k] = v;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
setEnsRecords(merged);
|
|
140
|
+
}
|
|
106
141
|
|
|
142
|
+
update('ens', 'done');
|
|
143
|
+
} catch (err: any) {
|
|
144
|
+
setEnsDetail(err?.message || 'ENS resolution failed');
|
|
145
|
+
update('ens', 'error');
|
|
146
|
+
setError(err?.message || 'ENS resolution failed');
|
|
147
|
+
update('install', 'error');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
update('resolve', 'running');
|
|
152
|
+
const resolved = await resolveVersion(packageName, r.version);
|
|
153
|
+
r.resolved = resolved;
|
|
154
|
+
r.resolvedVersion = resolved.version;
|
|
155
|
+
setResult({ ...r });
|
|
156
|
+
update('resolve', 'done');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── CVE check ──
|
|
107
160
|
update('cve', 'running');
|
|
108
161
|
r.cves = await queryOSV(packageName, r.resolvedVersion);
|
|
109
162
|
const cveCounts = categorizeCVEs(r.cves);
|
|
@@ -116,6 +169,7 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
116
169
|
setResult({ ...r });
|
|
117
170
|
update('cve', 'done');
|
|
118
171
|
|
|
172
|
+
// ── On-chain registry lookup ──
|
|
119
173
|
update('onchain', 'running');
|
|
120
174
|
try {
|
|
121
175
|
const info = await getPackageInfo(packageName, r.resolvedVersion);
|
|
@@ -133,12 +187,35 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
133
187
|
setResult({ ...r });
|
|
134
188
|
update('onchain', 'done');
|
|
135
189
|
|
|
190
|
+
// ── Auto-bump: try to find a safe version instead of blocking ──
|
|
191
|
+
if (r.blocked && !isEns) {
|
|
192
|
+
const safe = await findSafeVersion(packageName, r.resolvedVersion, r.cves);
|
|
193
|
+
if (safe) {
|
|
194
|
+
r.autoBumped = true;
|
|
195
|
+
r.autoBumpedFrom = r.resolvedVersion;
|
|
196
|
+
r.autoBumpReason = safe.reason;
|
|
197
|
+
r.resolvedVersion = safe.version;
|
|
198
|
+
r.blocked = false;
|
|
199
|
+
r.blockReason = undefined;
|
|
200
|
+
r.warning = true;
|
|
201
|
+
|
|
202
|
+
r.cves = await queryOSV(packageName, safe.version).catch(() => []);
|
|
203
|
+
try {
|
|
204
|
+
const newInfo = await getPackageInfo(packageName, safe.version);
|
|
205
|
+
if (newInfo.exists) r.info = newInfo;
|
|
206
|
+
} catch { /* keep existing */ }
|
|
207
|
+
|
|
208
|
+
setResult({ ...r });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Signature verification ──
|
|
136
213
|
if (r.info?.exists) {
|
|
137
214
|
update('signature', 'running');
|
|
138
215
|
r.signatureValid = r.info.signature !== '0x'
|
|
139
216
|
? verifyChecksum(r.info.checksum, r.info.signature, r.info.author)
|
|
140
217
|
: false;
|
|
141
|
-
if (r.info.author) {
|
|
218
|
+
if (r.info.author && !r.ensName) {
|
|
142
219
|
r.ensName = await resolveENSName(r.info.author).catch(() => null) || undefined;
|
|
143
220
|
}
|
|
144
221
|
setResult({ ...r });
|
|
@@ -147,6 +224,7 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
147
224
|
update('signature', 'skip');
|
|
148
225
|
}
|
|
149
226
|
|
|
227
|
+
// ── ChainPatrol check ──
|
|
150
228
|
if (!r.info?.exists) {
|
|
151
229
|
update('chainpatrol', 'running');
|
|
152
230
|
const cp = await checkPackageWithChainPatrol(packageName).catch(() => null);
|
|
@@ -161,12 +239,14 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
161
239
|
update('chainpatrol', 'skip');
|
|
162
240
|
}
|
|
163
241
|
|
|
242
|
+
// ── Fileverse report ──
|
|
164
243
|
if (r.info?.reportURI && !r.info.reportURI.startsWith('local://')) {
|
|
165
244
|
update('report', 'done');
|
|
166
245
|
} else {
|
|
167
246
|
update('report', 'skip');
|
|
168
247
|
}
|
|
169
248
|
|
|
249
|
+
// ── Block or install ──
|
|
170
250
|
if (r.blocked) {
|
|
171
251
|
setError(`Blocked: ${r.blockReason || 'security risk detected'}`);
|
|
172
252
|
update('install', 'error');
|
|
@@ -175,7 +255,7 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
175
255
|
|
|
176
256
|
update('install', 'running');
|
|
177
257
|
try {
|
|
178
|
-
const target = `${packageName}
|
|
258
|
+
const target = `${packageName}@${r.resolvedVersion}`;
|
|
179
259
|
execSync(`npm install ${target}`, { encoding: 'utf-8', stdio: 'pipe', cwd: process.cwd() });
|
|
180
260
|
} catch { /* non-fatal */ }
|
|
181
261
|
update('install', 'done');
|
|
@@ -191,12 +271,55 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
191
271
|
return (
|
|
192
272
|
<Box flexDirection="column">
|
|
193
273
|
<Header subtitle="install" />
|
|
194
|
-
{result &&
|
|
274
|
+
{result && (
|
|
275
|
+
<Box>
|
|
276
|
+
<Text color="white" bold> {result.name}@{result.resolvedVersion}</Text>
|
|
277
|
+
{result.ensName && result.resolved?.source === 'ens' && (
|
|
278
|
+
<Text color="cyan"> via {result.ensName}</Text>
|
|
279
|
+
)}
|
|
280
|
+
{result.autoBumped && (
|
|
281
|
+
<Text color="yellow"> (bumped from {result.autoBumpedFrom})</Text>
|
|
282
|
+
)}
|
|
283
|
+
</Box>
|
|
284
|
+
)}
|
|
195
285
|
<Text> </Text>
|
|
196
286
|
|
|
197
287
|
<StatusLine label="Resolve version" status={steps.resolve}
|
|
198
288
|
detail={steps.resolve === 'done' ? result?.resolvedVersion : undefined} />
|
|
199
289
|
|
|
290
|
+
{isEns && (
|
|
291
|
+
<StatusLine label="Resolve ENS author" status={steps.ens} detail={ensDetail} />
|
|
292
|
+
)}
|
|
293
|
+
{steps.ens === 'done' && result?.resolved?.source === 'ens' && (
|
|
294
|
+
<Box flexDirection="column" marginLeft={4}>
|
|
295
|
+
<Box>
|
|
296
|
+
<Text color="gray">Author: </Text>
|
|
297
|
+
<Text color="green">{result.ensName}</Text>
|
|
298
|
+
{result.resolved.authorAddress && (
|
|
299
|
+
<Text color="gray"> ({truncateAddress(result.resolved.authorAddress)})</Text>
|
|
300
|
+
)}
|
|
301
|
+
<Text color="green"> on-chain</Text>
|
|
302
|
+
</Box>
|
|
303
|
+
<Box>
|
|
304
|
+
<Text color="gray">Version: </Text>
|
|
305
|
+
<Text color="cyan">{result.resolvedVersion}</Text>
|
|
306
|
+
<Text color="gray"> (safest on-chain version)</Text>
|
|
307
|
+
</Box>
|
|
308
|
+
{ensRecords.fileverse && (
|
|
309
|
+
<Box>
|
|
310
|
+
<Text color="gray">Report: </Text>
|
|
311
|
+
<Text color="green">{ensRecords.fileverse.length > 50 ? ensRecords.fileverse.slice(0, 50) + '...' : ensRecords.fileverse}</Text>
|
|
312
|
+
</Box>
|
|
313
|
+
)}
|
|
314
|
+
{ensRecords.riskScore && (
|
|
315
|
+
<Box>
|
|
316
|
+
<Text color="gray">Risk: </Text>
|
|
317
|
+
<Text color="cyan">{ensRecords.riskScore}/100 (from ENS)</Text>
|
|
318
|
+
</Box>
|
|
319
|
+
)}
|
|
320
|
+
</Box>
|
|
321
|
+
)}
|
|
322
|
+
|
|
200
323
|
<StatusLine label="Query CVE database (OSV)" status={steps.cve}
|
|
201
324
|
detail={steps.cve === 'done'
|
|
202
325
|
? (result?.cves.length
|
|
@@ -231,6 +354,22 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
231
354
|
</Box>
|
|
232
355
|
)}
|
|
233
356
|
|
|
357
|
+
{result?.autoBumped && (
|
|
358
|
+
<Box flexDirection="column" marginLeft={4} marginTop={0}>
|
|
359
|
+
<Box>
|
|
360
|
+
<Text color="yellow">↑ Auto-bumped: </Text>
|
|
361
|
+
<Text color="red">{result.autoBumpedFrom}</Text>
|
|
362
|
+
<Text color="yellow"> → </Text>
|
|
363
|
+
<Text color="green" bold>{result.resolvedVersion}</Text>
|
|
364
|
+
</Box>
|
|
365
|
+
{result.autoBumpReason && (
|
|
366
|
+
<Box marginLeft={2}>
|
|
367
|
+
<Text color="gray">{result.autoBumpReason}</Text>
|
|
368
|
+
</Box>
|
|
369
|
+
)}
|
|
370
|
+
</Box>
|
|
371
|
+
)}
|
|
372
|
+
|
|
234
373
|
<StatusLine label="On-chain registry lookup" status={steps.onchain}
|
|
235
374
|
detail={steps.onchain === 'done' && result?.info?.exists
|
|
236
375
|
? `${result.info.aggregateScore}/100 (${classifyRisk(result.info.aggregateScore)})`
|
|
@@ -269,14 +408,30 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
269
408
|
<Box flexDirection="column" marginTop={1}>
|
|
270
409
|
<Text color="gray">────────────────────────────────────────</Text>
|
|
271
410
|
<Text color="white" bold> Security Summary</Text>
|
|
411
|
+
{result.resolved?.source === 'ens' && (
|
|
412
|
+
<Box marginLeft={2}>
|
|
413
|
+
<Text color="gray">Resolved: </Text>
|
|
414
|
+
<Text color="green">{result.ensName}</Text>
|
|
415
|
+
<Text color="gray"> → </Text>
|
|
416
|
+
<Text color="cyan">{result.resolvedVersion}</Text>
|
|
417
|
+
</Box>
|
|
418
|
+
)}
|
|
419
|
+
{result.autoBumped && (
|
|
420
|
+
<Box marginLeft={2}>
|
|
421
|
+
<Text color="gray">Bumped: </Text>
|
|
422
|
+
<Text color="red">{result.autoBumpedFrom}</Text>
|
|
423
|
+
<Text color="gray"> → </Text>
|
|
424
|
+
<Text color="green">{result.resolvedVersion}</Text>
|
|
425
|
+
</Box>
|
|
426
|
+
)}
|
|
272
427
|
{result.info?.exists && (
|
|
273
428
|
<Box marginLeft={2}>
|
|
274
|
-
<Text color="gray">Risk:
|
|
429
|
+
<Text color="gray">Risk: </Text>
|
|
275
430
|
<RiskBadge level={classifyRisk(result.info.aggregateScore)} score={result.info.aggregateScore} />
|
|
276
431
|
</Box>
|
|
277
432
|
)}
|
|
278
433
|
<Box marginLeft={2}>
|
|
279
|
-
<Text color="gray">CVEs:
|
|
434
|
+
<Text color="gray">CVEs: </Text>
|
|
280
435
|
{result.cves.length > 0 ? (
|
|
281
436
|
<Text color={severeCount > 0 ? 'red' : 'yellow'}>
|
|
282
437
|
{result.cves.length} known ({cveCounts.critical > 0 ? `${cveCounts.critical} critical, ` : ''}{cveCounts.high} high, {cveCounts.medium} medium, {cveCounts.low} low)
|
|
@@ -287,19 +442,25 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
287
442
|
</Box>
|
|
288
443
|
{result.info?.exists && (
|
|
289
444
|
<Box marginLeft={2}>
|
|
290
|
-
<Text color="gray">Signature
|
|
445
|
+
<Text color="gray">Signature:</Text>
|
|
291
446
|
<Text color={result.signatureValid ? 'green' : 'red'}>
|
|
292
|
-
{result.signatureValid ? 'verified' : 'unverified'}
|
|
447
|
+
{' '}{result.signatureValid ? 'verified' : 'unverified'}
|
|
293
448
|
</Text>
|
|
294
449
|
</Box>
|
|
295
450
|
)}
|
|
296
451
|
{result.ensName && (
|
|
297
452
|
<Box marginLeft={2}>
|
|
298
|
-
<Text color="gray">Author:
|
|
453
|
+
<Text color="gray">Author: </Text>
|
|
299
454
|
<Text color="green">{result.ensName}</Text>
|
|
300
455
|
</Box>
|
|
301
456
|
)}
|
|
302
|
-
{
|
|
457
|
+
{ensRecords.fileverse && (
|
|
458
|
+
<Box marginLeft={2}>
|
|
459
|
+
<Text color="gray">Fileverse:</Text>
|
|
460
|
+
<Text color="green"> {ensRecords.fileverse.length > 45 ? ensRecords.fileverse.slice(0, 45) + '...' : ensRecords.fileverse}</Text>
|
|
461
|
+
</Box>
|
|
462
|
+
)}
|
|
463
|
+
{result.warning && !result.blocked && !result.autoBumped && (
|
|
303
464
|
<Box marginLeft={2}>
|
|
304
465
|
<Text color="yellow">⚠ Vulnerabilities detected — review before using in production</Text>
|
|
305
466
|
</Box>
|
|
@@ -311,7 +472,7 @@ function SingleInstall({ packageName, version }: { packageName: string; version?
|
|
|
311
472
|
<Text color="yellow"> to fix known CVEs</Text>
|
|
312
473
|
</Box>
|
|
313
474
|
)}
|
|
314
|
-
{result.warning && result.safestVersion && (
|
|
475
|
+
{result.warning && result.safestVersion && !result.autoBumped && (
|
|
315
476
|
<Box marginLeft={2}>
|
|
316
477
|
<Text color="yellow">⚠ Consider using safest on-chain version: {result.safestVersion}</Text>
|
|
317
478
|
</Box>
|
|
@@ -338,6 +499,11 @@ interface BulkDepResult {
|
|
|
338
499
|
blocked: boolean;
|
|
339
500
|
blockReason?: string;
|
|
340
501
|
suggestedUpgrade?: string;
|
|
502
|
+
ensResolved?: boolean;
|
|
503
|
+
ensName?: string;
|
|
504
|
+
autoBumped?: boolean;
|
|
505
|
+
originalVersion?: string;
|
|
506
|
+
autoBumpReason?: string;
|
|
341
507
|
}
|
|
342
508
|
|
|
343
509
|
function BulkInstall() {
|
|
@@ -346,6 +512,9 @@ function BulkInstall() {
|
|
|
346
512
|
const [error, setError] = useState<string | null>(null);
|
|
347
513
|
const [installStatus, setInstallStatus] = useState<StepStatus>('pending');
|
|
348
514
|
const [total, setTotal] = useState(0);
|
|
515
|
+
const [ensCount, setEnsCount] = useState(0);
|
|
516
|
+
const [ensResolvingStatus, setEnsResolvingStatus] = useState<StepStatus>('skip');
|
|
517
|
+
const [ensResolvedCount, setEnsResolvedCount] = useState(0);
|
|
349
518
|
|
|
350
519
|
useEffect(() => {
|
|
351
520
|
runBulk().catch((err) => setError(String(err)));
|
|
@@ -368,15 +537,79 @@ function BulkInstall() {
|
|
|
368
537
|
return;
|
|
369
538
|
}
|
|
370
539
|
|
|
540
|
+
// ── Phase 1: Batch-resolve all ENS names in parallel ──
|
|
541
|
+
const ensEntries = entries.filter(([, ver]) => isENSVersion(String(ver)));
|
|
542
|
+
setEnsCount(ensEntries.length);
|
|
543
|
+
|
|
544
|
+
const ensCache = new Map<string, { address: string; version: string }>();
|
|
545
|
+
|
|
546
|
+
if (ensEntries.length > 0) {
|
|
547
|
+
setEnsResolvingStatus('running');
|
|
548
|
+
|
|
549
|
+
const uniqueEnsNames = [...new Set(ensEntries.map(([, v]) => String(v)))];
|
|
550
|
+
const ensResults = await Promise.allSettled(
|
|
551
|
+
uniqueEnsNames.map(async (ensName) => {
|
|
552
|
+
const addr = await resolveAddress(ensName);
|
|
553
|
+
return { ensName, address: addr };
|
|
554
|
+
}),
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
const ensAddresses = new Map<string, string>();
|
|
558
|
+
for (const result of ensResults) {
|
|
559
|
+
if (result.status === 'fulfilled' && result.value.address) {
|
|
560
|
+
ensAddresses.set(result.value.ensName, result.value.address);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const ensVersionResults = await Promise.allSettled(
|
|
565
|
+
ensEntries.map(async ([name, ensName]) => {
|
|
566
|
+
const addr = ensAddresses.get(String(ensName));
|
|
567
|
+
if (!addr) return null;
|
|
568
|
+
const resolved = await resolveVersion(name, String(ensName));
|
|
569
|
+
return { name, ensName: String(ensName), resolved };
|
|
570
|
+
}),
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
for (const result of ensVersionResults) {
|
|
574
|
+
if (result.status === 'fulfilled' && result.value) {
|
|
575
|
+
const { name, ensName, resolved } = result.value;
|
|
576
|
+
ensCache.set(name, {
|
|
577
|
+
address: resolved.authorAddress || '',
|
|
578
|
+
version: resolved.version,
|
|
579
|
+
});
|
|
580
|
+
setEnsResolvedCount((c) => c + 1);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
setEnsResolvingStatus('done');
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// ── Phase 2: Scan each dependency ──
|
|
371
588
|
const checked: BulkDepResult[] = [];
|
|
372
589
|
|
|
373
590
|
for (const [name, verRange] of entries) {
|
|
374
|
-
const
|
|
591
|
+
const rawVerStr = String(verRange);
|
|
592
|
+
const isEns = isENSVersion(rawVerStr);
|
|
593
|
+
|
|
594
|
+
let rawVersion: string;
|
|
595
|
+
let ensName: string | undefined;
|
|
596
|
+
let ensResolved = false;
|
|
597
|
+
|
|
598
|
+
if (isEns && ensCache.has(name)) {
|
|
599
|
+
const cached = ensCache.get(name)!;
|
|
600
|
+
rawVersion = cached.version;
|
|
601
|
+
ensName = rawVerStr;
|
|
602
|
+
ensResolved = true;
|
|
603
|
+
} else {
|
|
604
|
+
rawVersion = rawVerStr.replace(/^[\^~]/, '');
|
|
605
|
+
}
|
|
606
|
+
|
|
375
607
|
const entry: BulkDepResult = {
|
|
376
608
|
name, version: rawVersion,
|
|
377
609
|
cves: [], cvesCritical: 0, cvesHigh: 0,
|
|
378
610
|
onChain: false, score: null,
|
|
379
611
|
blocked: false,
|
|
612
|
+
ensResolved, ensName,
|
|
380
613
|
};
|
|
381
614
|
|
|
382
615
|
const [osvResult, infoResult] = await Promise.allSettled([
|
|
@@ -406,6 +639,25 @@ function BulkInstall() {
|
|
|
406
639
|
}
|
|
407
640
|
}
|
|
408
641
|
|
|
642
|
+
// ── Auto-bump blocked deps ──
|
|
643
|
+
if (entry.blocked && !ensResolved) {
|
|
644
|
+
const safe = await findSafeVersion(name, rawVersion, entry.cves);
|
|
645
|
+
if (safe) {
|
|
646
|
+
entry.autoBumped = true;
|
|
647
|
+
entry.originalVersion = rawVersion;
|
|
648
|
+
entry.autoBumpReason = safe.reason;
|
|
649
|
+
entry.version = safe.version;
|
|
650
|
+
entry.blocked = false;
|
|
651
|
+
entry.blockReason = undefined;
|
|
652
|
+
|
|
653
|
+
const newCves = await queryOSV(name, safe.version).catch(() => []);
|
|
654
|
+
entry.cves = newCves;
|
|
655
|
+
const newCounts = categorizeCVEs(newCves);
|
|
656
|
+
entry.cvesCritical = newCounts.critical;
|
|
657
|
+
entry.cvesHigh = newCounts.high;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
409
661
|
checked.push(entry);
|
|
410
662
|
setDeps([...checked]);
|
|
411
663
|
}
|
|
@@ -419,6 +671,22 @@ function BulkInstall() {
|
|
|
419
671
|
return;
|
|
420
672
|
}
|
|
421
673
|
|
|
674
|
+
// Update package.json with resolved versions before installing
|
|
675
|
+
const bumpedDeps = checked.filter((d) => d.autoBumped || d.ensResolved);
|
|
676
|
+
if (bumpedDeps.length > 0) {
|
|
677
|
+
const freshPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
678
|
+
for (const dep of bumpedDeps) {
|
|
679
|
+
const resolved = `^${dep.version}`;
|
|
680
|
+
if (freshPkg.dependencies && dep.name in freshPkg.dependencies) {
|
|
681
|
+
freshPkg.dependencies[dep.name] = resolved;
|
|
682
|
+
}
|
|
683
|
+
if (freshPkg.devDependencies && dep.name in freshPkg.devDependencies) {
|
|
684
|
+
freshPkg.devDependencies[dep.name] = resolved;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
fs.writeFileSync(pkgPath, JSON.stringify(freshPkg, null, 2) + '\n');
|
|
688
|
+
}
|
|
689
|
+
|
|
422
690
|
setInstallStatus('running');
|
|
423
691
|
try {
|
|
424
692
|
execSync('npm install', { encoding: 'utf-8', stdio: 'pipe', cwd: process.cwd() });
|
|
@@ -427,8 +695,9 @@ function BulkInstall() {
|
|
|
427
695
|
}
|
|
428
696
|
|
|
429
697
|
const blockedDeps = deps.filter((d) => d.blocked);
|
|
430
|
-
const
|
|
431
|
-
const
|
|
698
|
+
const bumpedDeps = deps.filter((d) => d.autoBumped || d.ensResolved);
|
|
699
|
+
const warnDeps = deps.filter((d) => !d.blocked && !d.autoBumped && !d.ensResolved && (d.cvesHigh > 0 || (d.score !== null && d.score >= MEDIUM_RISK_THRESHOLD)));
|
|
700
|
+
const safeDeps = deps.filter((d) => !d.blocked && !d.autoBumped && !d.ensResolved && d.cvesHigh === 0 && (d.score === null || d.score < MEDIUM_RISK_THRESHOLD));
|
|
432
701
|
const totalCves = deps.reduce((s, d) => s + d.cves.length, 0);
|
|
433
702
|
|
|
434
703
|
return (
|
|
@@ -436,13 +705,48 @@ function BulkInstall() {
|
|
|
436
705
|
<Header subtitle="install" />
|
|
437
706
|
<Text> </Text>
|
|
438
707
|
|
|
708
|
+
{ensCount > 0 && (
|
|
709
|
+
<StatusLine label={`Resolve ${ensCount} ENS author(s)`} status={ensResolvingStatus}
|
|
710
|
+
detail={ensResolvingStatus === 'done' ? `${ensResolvedCount} resolved` : ensResolvingStatus === 'running' ? 'resolving...' : undefined} />
|
|
711
|
+
)}
|
|
712
|
+
|
|
439
713
|
<StatusLine label={`Scanning ${total} dependencies`} status={scanning ? 'running' : 'done'}
|
|
440
714
|
detail={!scanning ? `${deps.length} checked` : `${deps.length}/${total}`} />
|
|
441
715
|
|
|
442
716
|
{deps.length > 0 && (
|
|
443
717
|
<Box flexDirection="column" marginTop={1}>
|
|
444
|
-
{
|
|
718
|
+
{bumpedDeps.length > 0 && (
|
|
445
719
|
<Box flexDirection="column">
|
|
720
|
+
<Text color="cyan" bold> ENS / AUTO-BUMPED ({bumpedDeps.length})</Text>
|
|
721
|
+
{bumpedDeps.map((d) => (
|
|
722
|
+
<Box key={d.name} flexDirection="column" marginLeft={2}>
|
|
723
|
+
<Box>
|
|
724
|
+
{d.ensResolved ? (
|
|
725
|
+
<Text color="cyan">◈ </Text>
|
|
726
|
+
) : (
|
|
727
|
+
<Text color="yellow">↑ </Text>
|
|
728
|
+
)}
|
|
729
|
+
<Text color="white" bold>{d.name}</Text>
|
|
730
|
+
<Text color="green">@{d.version}</Text>
|
|
731
|
+
{d.ensResolved && d.ensName && (
|
|
732
|
+
<Text color="cyan"> via {d.ensName}</Text>
|
|
733
|
+
)}
|
|
734
|
+
{d.autoBumped && d.originalVersion && (
|
|
735
|
+
<Text color="yellow"> bumped from {d.originalVersion}</Text>
|
|
736
|
+
)}
|
|
737
|
+
</Box>
|
|
738
|
+
{d.autoBumpReason && (
|
|
739
|
+
<Box marginLeft={4}>
|
|
740
|
+
<Text color="gray">{d.autoBumpReason}</Text>
|
|
741
|
+
</Box>
|
|
742
|
+
)}
|
|
743
|
+
</Box>
|
|
744
|
+
))}
|
|
745
|
+
</Box>
|
|
746
|
+
)}
|
|
747
|
+
|
|
748
|
+
{blockedDeps.length > 0 && (
|
|
749
|
+
<Box flexDirection="column" marginTop={bumpedDeps.length > 0 ? 1 : 0}>
|
|
446
750
|
<Text color="red" bold> BLOCKED ({blockedDeps.length})</Text>
|
|
447
751
|
{blockedDeps.map((d) => (
|
|
448
752
|
<Box key={d.name} flexDirection="column" marginLeft={2}>
|
|
@@ -476,7 +780,7 @@ function BulkInstall() {
|
|
|
476
780
|
)}
|
|
477
781
|
|
|
478
782
|
{warnDeps.length > 0 && (
|
|
479
|
-
<Box flexDirection="column" marginTop={blockedDeps.length > 0 ? 1 : 0}>
|
|
783
|
+
<Box flexDirection="column" marginTop={(blockedDeps.length + bumpedDeps.length) > 0 ? 1 : 0}>
|
|
480
784
|
<Text color="yellow" bold> WARNING ({warnDeps.length})</Text>
|
|
481
785
|
{warnDeps.map((d) => (
|
|
482
786
|
<Box key={d.name} marginLeft={2}>
|
|
@@ -491,7 +795,7 @@ function BulkInstall() {
|
|
|
491
795
|
)}
|
|
492
796
|
|
|
493
797
|
{safeDeps.length > 0 && (
|
|
494
|
-
<Box flexDirection="column" marginTop={(blockedDeps.length + warnDeps.length) > 0 ? 1 : 0}>
|
|
798
|
+
<Box flexDirection="column" marginTop={(blockedDeps.length + warnDeps.length + bumpedDeps.length) > 0 ? 1 : 0}>
|
|
495
799
|
<Text color="green" bold> SAFE ({safeDeps.length})</Text>
|
|
496
800
|
{safeDeps.map((d) => (
|
|
497
801
|
<Box key={d.name} marginLeft={2}>
|
|
@@ -518,7 +822,7 @@ function BulkInstall() {
|
|
|
518
822
|
|
|
519
823
|
<Box marginTop={1}>
|
|
520
824
|
<Text color={blockedDeps.length > 0 ? 'red' : totalCves > 0 ? 'yellow' : 'green'} bold>
|
|
521
|
-
{deps.length} packages scanned: {blockedDeps.length} blocked, {warnDeps.length} warnings, {totalCves} CVEs
|
|
825
|
+
{deps.length} packages scanned: {blockedDeps.length} blocked, {bumpedDeps.length} resolved, {warnDeps.length} warnings, {totalCves} CVEs
|
|
522
826
|
</Text>
|
|
523
827
|
</Box>
|
|
524
828
|
|