upstream-radar 0.34.0 → 0.36.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 +87 -24
- package/dist/src/cli.js +12 -4
- package/dist/src/cli.js.map +1 -1
- package/dist/src/dependency-index.d.ts +32 -0
- package/dist/src/dependency-index.d.ts.map +1 -1
- package/dist/src/dependency-index.js +230 -0
- package/dist/src/dependency-index.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/upstream-alignment.d.ts +67 -0
- package/dist/src/upstream-alignment.d.ts.map +1 -0
- package/dist/src/upstream-alignment.js +263 -0
- package/dist/src/upstream-alignment.js.map +1 -0
- package/dist/src/upstream-observer.d.ts +20 -0
- package/dist/src/upstream-observer.d.ts.map +1 -1
- package/dist/src/upstream-observer.js +156 -4
- package/dist/src/upstream-observer.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.zh-CN.md +54 -18
- package/package.json +1 -1
- package/schemas/reverse-dependency-index.schema.json +132 -0
- package/schemas/upstream-downstream-ir.schema.json +87 -0
package/README.md
CHANGED
|
@@ -4,27 +4,74 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/upstream-radar)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**The upstream dependency radar built into [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) plugins.**
|
|
8
8
|
|
|
9
|
-
Upstream Radar
|
|
9
|
+
Upstream Radar is not another package-name vulnerability scanner. It follows one DSH plugin from admission to maintenance:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
| Product job | What Radar establishes |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| **DSH compatibility / admission** | Whether the exact published bundle can be registered and loaded by the DSH releases you care about. |
|
|
14
|
+
| **Real dependency graph** | Which exact package versions and physical paths the plugin brings into the DSH profile, including unresolved edges. |
|
|
15
|
+
| **Continuous upstream monitoring** | Whether an advisory, npm release, DSH/Cordis change, or breaking signal changes the old → new situation. |
|
|
16
|
+
| **Author-facing repair** | Which plugin, dependency path, version, lockfile, or DSH declaration gives the author a concrete next fix. |
|
|
17
|
+
|
|
18
|
+
The deterministic scanner establishes package, graph, advisory, and compatibility facts. Only a meaningful affected change is handed to the DSH Agent for read-only, project-specific analysis; the model does not guess version matches or replace the evidence.
|
|
19
|
+
|
|
20
|
+
## The product loop
|
|
21
|
+
|
|
22
|
+
```mermaid
|
|
23
|
+
flowchart TD
|
|
24
|
+
A["DSH plugin source or exact npm artifact"] --> B["DSH compatibility / admission check"]
|
|
25
|
+
B --> C["Build the real plugin → dependency graph"]
|
|
26
|
+
C --> D["Monitor advisories, npm, DSH and Cordis changes"]
|
|
27
|
+
D --> E{"Meaningful affected change?"}
|
|
28
|
+
E -- "No" --> F["Update observation point and stay quiet"]
|
|
29
|
+
E -- "Yes" --> G["Calculate exact old → new impact paths"]
|
|
30
|
+
G --> H["Send bounded evidence to the DSH Agent"]
|
|
31
|
+
H --> I["Return a repairable action to the plugin author"]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This is the boundary: Radar decides **what changed and which exact path is involved**; DSH decides **what that means for the project**. A later website can visualize the saved graph, but the evidence and impact index are already useful without one.
|
|
35
|
+
|
|
36
|
+
## The upstream/downstream alignment IR
|
|
37
|
+
|
|
38
|
+
Every observer snapshot now carries a small, machine-readable alignment record:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
upstream: Git commit + package.json coordinate
|
|
42
|
+
downstream: npm coordinate + lockfile graph root + graph coverage
|
|
43
|
+
result: aligned | mismatch | unknown
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This catches a class of problems that a vulnerability scanner cannot: the source
|
|
47
|
+
package, published package, and dependency graph may no longer describe the
|
|
48
|
+
same thing. For example, the public DSH/Feishu target currently reports:
|
|
12
49
|
|
|
13
|
-
|
|
50
|
+
```text
|
|
51
|
+
source: dsh-lark-bot@0.15.8
|
|
52
|
+
published: dsh-feishu-bot@0.15.8
|
|
53
|
+
graph root: dsh-lark-bot@0.15.8
|
|
54
|
+
result: mismatch
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That is not a claim of malware or runtime incompatibility. It is an evidence
|
|
58
|
+
gap: Radar cannot safely say that the source it watched produced the artifact
|
|
59
|
+
users install. The IR is stored in `observations.json`, rendered in the first
|
|
60
|
+
baseline report, and defined in [`schemas/upstream-downstream-ir.schema.json`](schemas/upstream-downstream-ir.schema.json).
|
|
14
61
|
|
|
15
62
|
## Try it in 60 seconds
|
|
16
63
|
|
|
17
64
|
```bash
|
|
18
65
|
# No DSH profile, API key, or network state required
|
|
19
|
-
npx --yes upstream-radar@0.
|
|
66
|
+
npx --yes upstream-radar@0.36.0 demo
|
|
20
67
|
|
|
21
68
|
# Scan a public DSH plugin repository without installing it
|
|
22
|
-
npx --yes upstream-radar@0.
|
|
69
|
+
npx --yes upstream-radar@0.36.0 scan \
|
|
23
70
|
https://github.com/PlutoKeating/dsh-lark-bot \
|
|
24
71
|
--fail-on never
|
|
25
72
|
|
|
26
|
-
# Review
|
|
27
|
-
npx --yes upstream-radar@0.
|
|
73
|
+
# Review a real browser plugin users would install, then check two DSH releases
|
|
74
|
+
npx --yes upstream-radar@0.36.0 review dsh-plugin dsh-cloudflare-browser-run@0.1.1 \
|
|
28
75
|
--dsh-version 0.1.0-rc.6,0.1.0-rc.7
|
|
29
76
|
```
|
|
30
77
|
|
|
@@ -36,6 +83,7 @@ These are real, reproducible cases in this repository—not synthetic “vulnera
|
|
|
36
83
|
|
|
37
84
|
| Case | Finding | Why it matters |
|
|
38
85
|
| --- | --- | --- |
|
|
86
|
+
| [`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. |
|
|
39
87
|
| [50-plugin batch](examples/dsh/reports/dsh-batch-50-2026-08-17.md) | 0 confirmed runtime dependency vulnerabilities; 3 lockfile root-version mismatches | Monitoring can be wrong even when the vulnerability count is zero. |
|
|
40
88
|
| [`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.” |
|
|
41
89
|
| [DSH-TUI source vs npm](examples/dsh/reports/dsh-tui-source-vs-npm-2026-08-18.md) | Source has `prepare`; published artifact does not | Source-only and artifact-only reviews answer different questions. |
|
|
@@ -43,30 +91,27 @@ These are real, reproducible cases in this repository—not synthetic “vulnera
|
|
|
43
91
|
|
|
44
92
|
We report a confirmed vulnerability only when the affected exact version and runtime path are supported by the available evidence. Development-only hits, missing data, and advisory-source outages remain visibly different states.
|
|
45
93
|
|
|
46
|
-
## The
|
|
94
|
+
## The dependency graph behind every alert
|
|
47
95
|
|
|
48
96
|
```text
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
upstream commit, package, or advisory changes
|
|
56
|
-
↓
|
|
57
|
-
affected-plugin paths and author-facing next action
|
|
58
|
-
↓
|
|
59
|
-
optional DSH Agent analysis only when a meaningful change exists
|
|
97
|
+
plugin@1.0.0
|
|
98
|
+
├── framework@2.4.7
|
|
99
|
+
│ ├── parser@3.2.1
|
|
100
|
+
│ └── archive@1.8.0
|
|
101
|
+
└── logger@4.0.2
|
|
102
|
+
└── parser@2.9.0 ← the affected physical node
|
|
60
103
|
```
|
|
61
104
|
|
|
105
|
+
Two copies of `parser` are different nodes. An alert names the exact version and path that entered the DSH profile; it does not page every plugin that happens to use the same package name.
|
|
106
|
+
|
|
62
107
|
For a collection of saved reports, build the reverse index that turns an upstream package update into affected plugins:
|
|
63
108
|
|
|
64
109
|
```bash
|
|
65
|
-
npx --yes upstream-radar@0.
|
|
110
|
+
npx --yes upstream-radar@0.36.0 graph reverse ./reports \
|
|
66
111
|
--output reverse-dependency-index.json
|
|
67
112
|
|
|
68
113
|
# Ask: which plugins currently depend on this exact package?
|
|
69
|
-
npx --yes upstream-radar@0.
|
|
114
|
+
npx --yes upstream-radar@0.36.0 graph reverse ./reports \
|
|
70
115
|
--package parser@2.9.0
|
|
71
116
|
```
|
|
72
117
|
|
|
@@ -78,12 +123,30 @@ plugin@1.0.0 → logger@4.0.2 → parser@2.9.0
|
|
|
78
123
|
|
|
79
124
|
It also preserves whether the graph is complete or has unresolved optional/peer edges. A later website can visualize this index; the index and evidence remain the product foundation.
|
|
80
125
|
|
|
126
|
+
To route an upstream old → new change to that index, pass it to the always-on
|
|
127
|
+
observer:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx --yes upstream-radar@0.36.0 observe ./targets.yml \
|
|
131
|
+
--reverse-index ./reverse-dependency-index.json \
|
|
132
|
+
--state ./observations.json \
|
|
133
|
+
--report ./upstream-radar-observer.md
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The observer matches by package name, not only by the new exact version. If
|
|
137
|
+
`parser@1.0.0` becomes `parser@2.0.0` upstream while a downstream plugin still
|
|
138
|
+
uses `parser@1.0.0`, the report names that plugin and its path as a possible
|
|
139
|
+
impact. `complete` or `incomplete` coverage stays attached to the result; this
|
|
140
|
+
is an evidence-based routing signal, not a claim that the plugin is already
|
|
141
|
+
broken. See the persisted index definition in
|
|
142
|
+
[`schemas/reverse-dependency-index.schema.json`](schemas/reverse-dependency-index.schema.json).
|
|
143
|
+
|
|
81
144
|
## GitHub Action
|
|
82
145
|
|
|
83
146
|
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.
|
|
84
147
|
|
|
85
148
|
```yaml
|
|
86
|
-
- uses: MicroMilo/upstream-radar@v0.
|
|
149
|
+
- uses: MicroMilo/upstream-radar@v0.36.0
|
|
87
150
|
with:
|
|
88
151
|
config: upstream-radar.config.json
|
|
89
152
|
fail-on: high
|
|
@@ -107,7 +170,7 @@ See the [consumer workflow](examples/github-actions/consumer/README.md) for conf
|
|
|
107
170
|
pnpm add upstream-radar
|
|
108
171
|
|
|
109
172
|
# Generate a reviewable DSH profile inventory from the installed profile
|
|
110
|
-
npx --yes upstream-radar@0.
|
|
173
|
+
npx --yes upstream-radar@0.36.0 setup
|
|
111
174
|
```
|
|
112
175
|
|
|
113
176
|
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
|
@@ -14,7 +14,7 @@ import { checkDshProfile, renderDshProfileCheck, renderDshProfileCheckSummary }
|
|
|
14
14
|
import { createDemoReport, renderDemo } from './demo.js';
|
|
15
15
|
import { GitHubReleaseClient } from './github-release.js';
|
|
16
16
|
import { parseNpmLockGraph, parsePnpmLockGraph } from './graph.js';
|
|
17
|
-
import { buildReverseDependencyIndex, findReverseDependencyEntry, parseReverseDependencyObservations } from './dependency-index.js';
|
|
17
|
+
import { buildReverseDependencyIndex, findReverseDependencyEntry, parseReverseDependencyIndex, parseReverseDependencyObservations } from './dependency-index.js';
|
|
18
18
|
import { createRadarConfigFromDshProfile, createRadarConfigFromNpmLock, createRadarConfigFromPnpmLock, discoverDshProfiles, refreshRadarConfigFromConfiguredProfile, resolveDshProfileDirectory, writeDshPatch, writeRadarConfig } from './init.js';
|
|
19
19
|
import { parsePackageManifestSnapshot, parseRadarConfig } from './inventory.js';
|
|
20
20
|
import { inspectNpmPackage } from './npm.js';
|
|
@@ -202,6 +202,7 @@ should remain visible without failing CI.
|
|
|
202
202
|
Usage:
|
|
203
203
|
upstream-radar observe <targets.yml|github-url> [--state <observations.json>]
|
|
204
204
|
[--report <report.md>] [--dsh-version <v1>,<v2>,...]
|
|
205
|
+
[--reverse-index <index.json>]
|
|
205
206
|
[--dsh-agent-command <executable>]
|
|
206
207
|
[--dsh-agent-arg <argument>] [--llm-env-file <path>] [--retry-pending]
|
|
207
208
|
[--ecosystem <dsh|codex|pi>] [--id <id>] [--package <name>]
|
|
@@ -462,7 +463,7 @@ Usage:
|
|
|
462
463
|
upstream-radar doctor [config.json] [options]
|
|
463
464
|
upstream-radar scan <directory-or-github-url> [--json] [--fail-on <warn|review|block|never>]
|
|
464
465
|
upstream-radar inspect [npm:]<package>@<exact-version> [--deep] [--json] [--fail-on <warn|review|block|never>]
|
|
465
|
-
upstream-radar observe <targets.yml|github-url> [--state <observations.json>] [--report <report.md>] [--dsh-version <v1>,<v2>,...] [--dsh-agent-command <executable>] [--dsh-agent-arg <argument>] [--llm-env-file <path>] [--retry-pending] [--ecosystem <dsh|codex|pi>] [--id <id>] [--package <name>] [--package-path <path>] [--lockfile <path>] [--lockfile-type <npm|pnpm>] [--ref <branch>] [--json]
|
|
466
|
+
upstream-radar observe <targets.yml|github-url> [--state <observations.json>] [--report <report.md>] [--dsh-version <v1>,<v2>,...] [--reverse-index <index.json>] [--dsh-agent-command <executable>] [--dsh-agent-arg <argument>] [--llm-env-file <path>] [--retry-pending] [--ecosystem <dsh|codex|pi>] [--id <id>] [--package <name>] [--package-path <path>] [--lockfile <path>] [--lockfile-type <npm|pnpm>] [--ref <branch>] [--json]
|
|
466
467
|
upstream-radar graph <npm-lock|pnpm-lock> <lockfile> [--root <package>@<exact-version>] [--json]
|
|
467
468
|
upstream-radar graph reverse <reports-directory> [--package <name>@<exact-version>] [--output <index.json>] [--json]
|
|
468
469
|
upstream-radar profile-check [profile-directory] [--patch <path>] [--report <path>] [--summary] [--json]
|
|
@@ -1749,6 +1750,7 @@ async function runObserve(args) {
|
|
|
1749
1750
|
let agentCommand;
|
|
1750
1751
|
let agentArgs = [];
|
|
1751
1752
|
let llmEnvFile;
|
|
1753
|
+
let reverseIndexPath;
|
|
1752
1754
|
let registry;
|
|
1753
1755
|
let retryPending = false;
|
|
1754
1756
|
let json = false;
|
|
@@ -1769,7 +1771,7 @@ async function runObserve(args) {
|
|
|
1769
1771
|
else if (argument === '--retry-pending') {
|
|
1770
1772
|
retryPending = true;
|
|
1771
1773
|
}
|
|
1772
|
-
else if (argument === '--ecosystem' || argument === '--id' || argument === '--package' || argument === '--package-path' || argument === '--lockfile' || argument === '--lockfile-type' || argument === '--ref' || argument === '--dsh-version' || argument === '--state' || argument === '--report' || argument === '--dsh-agent-command' || argument === '--dsh-agent-arg' || argument === '--llm-env-file' || argument === '--registry') {
|
|
1774
|
+
else if (argument === '--ecosystem' || argument === '--id' || argument === '--package' || argument === '--package-path' || argument === '--lockfile' || argument === '--lockfile-type' || argument === '--ref' || argument === '--dsh-version' || argument === '--state' || argument === '--report' || argument === '--reverse-index' || argument === '--dsh-agent-command' || argument === '--dsh-agent-arg' || argument === '--llm-env-file' || argument === '--registry') {
|
|
1773
1775
|
const value = args[index + 1];
|
|
1774
1776
|
if (value === undefined || (value.startsWith('-') && argument !== '--dsh-agent-arg'))
|
|
1775
1777
|
throw new Error(`${argument} requires a value`);
|
|
@@ -1793,6 +1795,8 @@ async function runObserve(args) {
|
|
|
1793
1795
|
statePath = value;
|
|
1794
1796
|
else if (argument === '--report')
|
|
1795
1797
|
reportPath = value;
|
|
1798
|
+
else if (argument === '--reverse-index')
|
|
1799
|
+
reverseIndexPath = value;
|
|
1796
1800
|
else if (argument === '--dsh-agent-command')
|
|
1797
1801
|
agentCommand = value;
|
|
1798
1802
|
else if (argument === '--dsh-agent-arg')
|
|
@@ -1801,7 +1805,7 @@ async function runObserve(args) {
|
|
|
1801
1805
|
llmEnvFile = value;
|
|
1802
1806
|
else
|
|
1803
1807
|
registry = value;
|
|
1804
|
-
if (argument !== '--state' && argument !== '--report' && argument !== '--dsh-agent-command' && argument !== '--dsh-agent-arg' && argument !== '--llm-env-file' && argument !== '--registry')
|
|
1808
|
+
if (argument !== '--state' && argument !== '--report' && argument !== '--reverse-index' && argument !== '--dsh-agent-command' && argument !== '--dsh-agent-arg' && argument !== '--llm-env-file' && argument !== '--registry')
|
|
1805
1809
|
inlineOptionsUsed = true;
|
|
1806
1810
|
index += 1;
|
|
1807
1811
|
}
|
|
@@ -1839,6 +1843,9 @@ async function runObserve(args) {
|
|
|
1839
1843
|
config = parseObserverConfigText(targetText);
|
|
1840
1844
|
}
|
|
1841
1845
|
const previousState = await loadObservationState(statePath);
|
|
1846
|
+
const reverseDependencyIndex = reverseIndexPath === undefined
|
|
1847
|
+
? undefined
|
|
1848
|
+
: parseReverseDependencyIndex(await readJson(reverseIndexPath), reverseIndexPath);
|
|
1842
1849
|
const source = new UpstreamObserverClient({
|
|
1843
1850
|
...(process.env.GITHUB_TOKEN === undefined ? {} : { githubToken: process.env.GITHUB_TOKEN }),
|
|
1844
1851
|
...(registry === undefined ? {} : { registry }),
|
|
@@ -1855,6 +1862,7 @@ async function runObserve(args) {
|
|
|
1855
1862
|
}
|
|
1856
1863
|
const result = await runObserver(config, previousState, {
|
|
1857
1864
|
source,
|
|
1865
|
+
...(reverseDependencyIndex === undefined ? {} : { reverseDependencyIndex }),
|
|
1858
1866
|
retryPending,
|
|
1859
1867
|
artifactReviewer: async (spec, target) => reviewObserverArtifact(spec, target, registry),
|
|
1860
1868
|
...(agentOptions === undefined ? {} : {
|