upstream-radar 0.38.0 → 0.40.0

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/README.md CHANGED
@@ -10,7 +10,7 @@ Upstream Radar is not another package-name vulnerability scanner. It follows one
10
10
 
11
11
  | Product job | What Radar establishes |
12
12
  | --- | --- |
13
- | **DSH compatibility / admission** | Whether the exact published bundle can be registered and loaded by the DSH releases you care about. |
13
+ | **DSH compatibility / admission** | Whether the exact published bundle can be installed, registered, and loaded by the DSH releases you care about, with an isolated execution lane for behavior evidence. |
14
14
  | **Real dependency graph** | Which exact package versions and physical paths the plugin brings into the DSH profile, including unresolved edges. |
15
15
  | **Continuous upstream monitoring** | Whether an advisory, npm release, DSH/Cordis change, or breaking signal changes the old → new situation. |
16
16
  | **Author-facing repair** | Which plugin, dependency path, version, lockfile, or DSH declaration gives the author a concrete next fix. |
@@ -21,7 +21,7 @@ The deterministic scanner establishes package, graph, advisory, and compatibilit
21
21
 
22
22
  ```mermaid
23
23
  flowchart TD
24
- A["DSH plugin source or exact npm artifact"] --> B["DSH compatibility / admission check"]
24
+ A["DSH plugin source or exact npm artifact"] --> B["Static review + DSH compatibility check"]
25
25
  B --> C["Build the real plugin → dependency graph"]
26
26
  C --> D["Monitor advisories, npm, DSH and Cordis changes"]
27
27
  D --> E{"Meaningful affected change?"}
@@ -63,26 +63,71 @@ baseline report, and defined in [`schemas/upstream-downstream-ir.schema.json`](s
63
63
 
64
64
  ```bash
65
65
  # No DSH profile, API key, or network state required
66
- npx --yes upstream-radar@0.38.0 demo
66
+ npx --yes upstream-radar@0.40.0 demo
67
67
 
68
68
  # Scan a public DSH plugin repository without installing it
69
- npx --yes upstream-radar@0.38.0 scan \
69
+ npx --yes upstream-radar@0.40.0 scan \
70
70
  https://github.com/PlutoKeating/dsh-lark-bot \
71
71
  --fail-on never
72
72
 
73
73
  # Review a real browser plugin users would install, then check two DSH releases
74
- npx --yes upstream-radar@0.38.0 review dsh-plugin dsh-cloudflare-browser-run@0.1.1 \
74
+ npx --yes upstream-radar@0.40.0 review dsh-plugin dsh-cloudflare-browser-run@0.1.1 \
75
75
  --dsh-version 0.1.0-rc.6,0.1.0-rc.7
76
76
  ```
77
77
 
78
78
  The important output is evidence, not a green badge: exact package identity, dependency paths, unresolved edges, install-time scripts, npm integrity/signature/provenance, advisory matches, and DSH load results.
79
79
 
80
+ ## The isolated execution lane
81
+
82
+ Static metadata tells us that a lifecycle script exists; it cannot tell us what
83
+ actually ran. The new [`dsh-install` workflow](.github/workflows/observe-dsh-plugin-install.yml)
84
+ therefore uses a separate, deliberately untrusted lane:
85
+
86
+ ```text
87
+ fresh GitHub-hosted VM (no secrets, read-only repository token)
88
+ → restricted container (no host workspace or Docker socket)
89
+ → npm pack exact package@version with scripts disabled
90
+ → enforce the artifact's declared Node range before plugin code can run
91
+ → record the exact Node and pnpm runtime used by the check
92
+ → DSH installs that same tarball with only the declared dependency-build approvals
93
+ → strace records child processes, network destinations and file writes
94
+ → DSH loads the registered bundle under the same exact release
95
+ → bounded JSON report survives and a non-compatible pair fails the check
96
+ → the container and VM are discarded
97
+ ```
98
+
99
+ Use **Actions → Observe one DSH plugin install** and supply one exact plugin and
100
+ one exact DSH version. The CLI also exposes `probe dsh-install`, but it refuses
101
+ to run unless both `--execute` and `UPSTREAM_RADAR_ISOLATED_RUNNER=1` are present;
102
+ it is not intended as a normal laptop command.
103
+
104
+ This lane is wired into the always-on observer. Radar now watches the official
105
+ `@deepseek-ai/dsh` `next` release channel (current DSH releases are prereleases
106
+ rather than npm `latest`). A new exact DSH publication fans out the
107
+ [nine-plugin maintained corpus](examples/dsh/install-observer/targets.json), one
108
+ fresh VM per plugin. Six of those targets come from an identity-checked,
109
+ [commit-pinned `awesome-dsh-plugin` cohort](examples/dsh/awesome-observer/README.md);
110
+ two additional catalog targets are source-only because their real distribution
111
+ is GitHub rather than npm. A mapped plugin publication retests only that plugin. Unchanged
112
+ evidence keeps `observations.json` byte-stable, persistent source/publish drift
113
+ does not wake the Agent again, and the DSH Agent/API key never enters the execution job.
114
+ Build-script approvals are exact package names stored in the maintained target
115
+ and copied into every report; an absent list approves nothing.
116
+
117
+ The first implementation is intentionally honest about its limit: a container
118
+ shares the hosted VM's kernel, and same-container `strace` evidence is not
119
+ tamper-proof against determined malicious code. The outer VM is disposable and
120
+ secret-free; a Firecracker collector is the later high-assurance backend, not a
121
+ claim made by this report. See the [execution boundary and result semantics](examples/dsh/install-observer/README.md)
122
+ and [`dsh-install-observation.schema.json`](schemas/dsh-install-observation.schema.json).
123
+
80
124
  ## What we have already found
81
125
 
82
126
  These are real, reproducible cases in this repository—not synthetic “vulnerable package” demos.
83
127
 
84
128
  | Case | Finding | Why it matters |
85
129
  | --- | --- | --- |
130
+ | [Live DSH `0.1.1-rc.1` isolated matrix](examples/dsh/install-observer/reports/2026-08-21-dsh-0.1.1-rc.1.md) | Eight exact artifacts install/register/load under their recorded contracts; OpenPencil is stopped before execution because it requires Node ≥24.11 while the isolated runner is Node 22.23; Better Sidebar passes only with its documented `node-pty` build approval and a real native toolchain | The imported awesome cohort found both a genuine runtime-pair incompatibility and a test-environment defect, while preserving the evidence that distinguishes them. |
86
131
  | [`dsh-cloudflare-browser-run@0.1.1`](examples/reports/dsh-cloudflare-browser-run-0.1.1.txt) | 18 resolved packages, 2 unresolved optional Cordis edges, 0 known vulnerabilities, and DSH rc.6/rc.7 both loaded the bundle | A real browser plugin demonstrates the DSH admission boundary and why incomplete edges stay visible. |
87
132
  | [50-plugin batch](examples/dsh/reports/dsh-batch-50-2026-08-17.md) / [real graph corpus](examples/dsh/first-batch/README.md) | 50 source scans, 30 exact npm reviews, 37 real plugin graphs indexed; 13 targets kept as missing evidence | The reverse index is now built from real DSH plugins, and missing graphs are not treated as clean. |
88
133
  | [`dsh-feishu-bot@0.15.8`](examples/dsh/reports/dsh-feishu-bot-0.15.8-review-2026-08-18.md) | 89-package graph, 12 unresolved optional edges, reachable `protobufjs` `postinstall`, DSH rc.6/rc.7 compatible | “No known CVE” is not the same as “no installation trust boundary.” |
@@ -131,11 +176,11 @@ Two copies of `parser` are different nodes. An alert names the exact version and
131
176
  For a collection of saved reports, build the reverse index that turns an upstream package update into affected plugins:
132
177
 
133
178
  ```bash
134
- npx --yes upstream-radar@0.38.0 graph reverse ./reports \
179
+ npx --yes upstream-radar@0.40.0 graph reverse ./reports \
135
180
  --output reverse-dependency-index.json
136
181
 
137
182
  # Ask: which plugins currently depend on this exact package?
138
- npx --yes upstream-radar@0.38.0 graph reverse ./reports \
183
+ npx --yes upstream-radar@0.40.0 graph reverse ./reports \
139
184
  --package parser@2.9.0
140
185
 
141
186
  # Rebuild the checked-in index from the real first 50 DSH plugin reports
@@ -154,7 +199,7 @@ To route an upstream old → new change to that index, pass it to the always-on
154
199
  observer:
155
200
 
156
201
  ```bash
157
- npx --yes upstream-radar@0.38.0 observe ./targets.yml \
202
+ npx --yes upstream-radar@0.40.0 observe ./targets.yml \
158
203
  --reverse-index ./reverse-dependency-index.json \
159
204
  --state ./observations.json \
160
205
  --report ./upstream-radar-observer.md
@@ -180,7 +225,7 @@ replay baseline → one Agent task → quiet run without network access.
180
225
  The repository already contains a reusable, composite Action in [`action.yml`](action.yml). It runs the same frozen Radar check in CI and writes a short Job Summary.
181
226
 
182
227
  ```yaml
183
- - uses: MicroMilo/upstream-radar@v0.38.0
228
+ - uses: MicroMilo/upstream-radar@v0.40.0
184
229
  with:
185
230
  config: upstream-radar.config.json
186
231
  fail-on: high
@@ -194,8 +239,9 @@ See the [consumer workflow](examples/github-actions/consumer/README.md) for conf
194
239
  | --- | --- |
195
240
  | Reconstruct exact npm/pnpm dependency paths | An empty finding list is a safety certificate |
196
241
  | Query OSV and GitHub Advisory evidence for exact versions | A missing provenance statement proves maliciousness |
197
- | Compare source and published artifact evidence | Static review replaces sandboxing or runtime testing |
198
- | Check DSH bundle/profile compatibility without business execution | “Compatible” means the plugin is secure |
242
+ | Compare source and published artifact evidence | Static review replaces runtime evidence |
243
+ | Observe exact install/load behavior in a disposable VM and restricted container | One observed run proves adversarial code is safe |
244
+ | Check DSH bundle/profile compatibility without business actions | “Compatible” means the plugin is secure |
199
245
  | Monitor old → new upstream observations | An LLM can repair evidence that was never collected |
200
246
 
201
247
  ## Install and connect to DSH
@@ -204,7 +250,7 @@ See the [consumer workflow](examples/github-actions/consumer/README.md) for conf
204
250
  pnpm add upstream-radar
205
251
 
206
252
  # Generate a reviewable DSH profile inventory from the installed profile
207
- npx --yes upstream-radar@0.38.0 setup
253
+ npx --yes upstream-radar@0.40.0 setup
208
254
  ```
209
255
 
210
256
  For Feishu/webhook routing, DSH Agent handoff, observer state, report schemas, and troubleshooting, use the [full Chinese guide](docs/README.zh-CN.md). The [architecture notes](docs/architecture.md) explain the boundaries and evidence model.
package/dist/src/cli.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import process from 'node:process';
3
3
  import { spawnSync } from 'node:child_process';
4
- import { access, readFile, readdir, stat, writeFile } from 'node:fs/promises';
4
+ import { access, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
5
5
  import { dirname, join, resolve } from 'node:path';
6
6
  import { renderCompatibilityBenchmark, runCompatibilityBenchmark } from './compatibility-benchmark.js';
7
7
  import { assessCompatibilityChange } from './compatibility.js';
8
8
  import { probeDshLoad, probeDshLoadMatrix, renderDshLoadMatrix, renderDshLoadProbe } from './dsh-probe.js';
9
+ import { observeDshPluginInstall, renderDshInstallObservation, } from './dsh-install-observation.js';
9
10
  import { renderDshPluginReview, reviewDshPlugin } from './dsh-review.js';
10
11
  import { createAnalysisTask, renderAgentAnalysisPrompt } from './dsh-analysis.js';
11
12
  import { createDshCaseReport, renderDshCase } from './dsh-case.js';
@@ -271,9 +272,16 @@ third-party bundles is selected automatically.
271
272
  Usage:
272
273
  upstream-radar probe dsh-load <package.tgz> [--dsh-version <exact-version>] [--json]
273
274
  upstream-radar probe dsh-matrix <package.tgz> --dsh-version <v1>,<v2>,... [--json]
274
-
275
- The probe is bounded and isolated. It is a compatibility/load check, not a
276
- semantic safety review or a substitute for dependency monitoring.
275
+ upstream-radar probe dsh-install [npm:]<package>@<exact-version>
276
+ --dsh-version <exact-version> --isolation-provider <provider> --execute
277
+ [--allow-build <package>]... [--timeout <seconds>] [--report <report.json>] [--json]
278
+
279
+ The load probes disable lifecycle scripts and check bundle registration/load.
280
+ The dsh-install probe deliberately executes the exact plugin's lifecycle scripts
281
+ and loads the bundle while recording Linux process, network, and file-change
282
+ evidence. Run it only inside a disposable, secret-free Linux environment. It
283
+ requires both --execute and UPSTREAM_RADAR_ISOLATED_RUNNER=1.
284
+ Radar records the caller's isolation-provider claim but cannot verify it.
277
285
  `,
278
286
  review: `Upstream Radar — review one exact published DSH plugin in one command
279
287
 
@@ -469,6 +477,7 @@ Usage:
469
477
  upstream-radar profile-check [profile-directory] [--patch <path>] [--report <path>] [--summary] [--json]
470
478
  upstream-radar probe dsh-load <package.tgz> [--dsh-version <exact-version>] [--timeout <seconds>] [--keep-profile] [--json]
471
479
  upstream-radar probe dsh-matrix <package.tgz> --dsh-version <v1>[,<v2>,...] [--timeout <seconds>] [--keep-profile] [--json]
480
+ upstream-radar probe dsh-install [npm:]<package>@<exact-version> --dsh-version <exact-version> --isolation-provider <github-actions-hosted-runner|firecracker|other> --execute [--allow-build <package>]... [--timeout <seconds>] [--report <report.json>] [--json]
472
481
  upstream-radar review dsh-plugin [npm:]<package>@<exact-version> --dsh-version <v1>,<v2>,... [--json]
473
482
  upstream-radar demo [--json]
474
483
  upstream-radar case dsh-web-ui [--json]
@@ -1203,6 +1212,8 @@ async function runDshCase(args) {
1203
1212
  }
1204
1213
  async function runProbe(args) {
1205
1214
  const mode = args[0];
1215
+ if (mode === 'dsh-install')
1216
+ return runDshInstallObservation(args.slice(1));
1206
1217
  if (mode !== 'dsh-load' && mode !== 'dsh-matrix')
1207
1218
  throw new Error('probe requires dsh-load or dsh-matrix');
1208
1219
  const packagePath = args[1];
@@ -1267,6 +1278,85 @@ async function runProbe(args) {
1267
1278
  process.stdout.write(json ? `${JSON.stringify(report, null, 2)}\n` : renderDshLoadMatrix(report));
1268
1279
  return report.result === 'compatible' ? 0 : report.result === 'incompatible' ? 2 : 1;
1269
1280
  }
1281
+ async function runDshInstallObservation(args) {
1282
+ const packageSpec = args[0];
1283
+ if (packageSpec === undefined || packageSpec.startsWith('-')) {
1284
+ throw new Error('probe dsh-install requires an exact npm package');
1285
+ }
1286
+ let dshVersion;
1287
+ let isolationProvider;
1288
+ let timeoutSeconds = 180;
1289
+ let reportPath;
1290
+ const allowedBuilds = [];
1291
+ let execute = false;
1292
+ let json = false;
1293
+ for (let index = 1; index < args.length; index += 1) {
1294
+ const argument = args[index];
1295
+ if (argument === '--execute') {
1296
+ execute = true;
1297
+ }
1298
+ else if (argument === '--json') {
1299
+ json = true;
1300
+ }
1301
+ else if (argument === '--dsh-version' || argument === '--isolation-provider' || argument === '--allow-build' || argument === '--timeout' || argument === '--report') {
1302
+ const value = args[index + 1];
1303
+ if (value === undefined || value.startsWith('-'))
1304
+ throw new Error(`${argument} requires a value`);
1305
+ if (argument === '--dsh-version') {
1306
+ if (dshVersion !== undefined)
1307
+ throw new Error('probe dsh-install accepts only one --dsh-version');
1308
+ dshVersion = value;
1309
+ }
1310
+ else if (argument === '--isolation-provider') {
1311
+ if (value !== 'github-actions-hosted-runner' && value !== 'firecracker' && value !== 'other') {
1312
+ throw new Error('--isolation-provider must be github-actions-hosted-runner, firecracker or other');
1313
+ }
1314
+ isolationProvider = value;
1315
+ }
1316
+ else if (argument === '--timeout') {
1317
+ const parsed = Number(value);
1318
+ if (!Number.isSafeInteger(parsed) || parsed < 30 || parsed > 600) {
1319
+ throw new Error('--timeout must be an integer between 30 and 600 seconds');
1320
+ }
1321
+ timeoutSeconds = parsed;
1322
+ }
1323
+ else if (argument === '--allow-build') {
1324
+ allowedBuilds.push(value);
1325
+ }
1326
+ else {
1327
+ reportPath = value;
1328
+ }
1329
+ index += 1;
1330
+ }
1331
+ else {
1332
+ throw new Error(`unknown option for probe dsh-install: ${argument}`);
1333
+ }
1334
+ }
1335
+ if (dshVersion === undefined)
1336
+ throw new Error('probe dsh-install requires one exact --dsh-version');
1337
+ if (isolationProvider === undefined)
1338
+ throw new Error('probe dsh-install requires --isolation-provider');
1339
+ if (!execute)
1340
+ throw new Error('probe dsh-install requires --execute because third-party code may run');
1341
+ if (process.env.UPSTREAM_RADAR_ISOLATED_RUNNER !== '1') {
1342
+ throw new Error('probe dsh-install requires UPSTREAM_RADAR_ISOLATED_RUNNER=1 in the disposable environment');
1343
+ }
1344
+ const report = await observeDshPluginInstall({
1345
+ packageSpec,
1346
+ dshVersion,
1347
+ allowExecution: true,
1348
+ isolationProvider,
1349
+ allowedBuilds,
1350
+ timeoutMs: timeoutSeconds * 1_000,
1351
+ });
1352
+ if (reportPath !== undefined) {
1353
+ const absoluteReportPath = resolve(reportPath);
1354
+ await mkdir(dirname(absoluteReportPath), { recursive: true });
1355
+ await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`, { mode: 0o600 });
1356
+ }
1357
+ process.stdout.write(json ? `${JSON.stringify(report, null, 2)}\n` : renderDshInstallObservation(report));
1358
+ return report.result === 'compatible' ? 0 : report.result === 'unknown' ? 1 : 2;
1359
+ }
1270
1360
  async function runDshPluginReview(args) {
1271
1361
  if (args[0] !== 'dsh-plugin')
1272
1362
  throw new Error('review requires dsh-plugin');