dsh-vet 0.1.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/LICENSE +21 -0
- package/README.md +136 -0
- package/bin/dsh-vet.mjs +7 -0
- package/docs/calibration-v0.1.md +58 -0
- package/docs/dsh-vet-v1.md +159 -0
- package/docs/rules/dep.floating-range.md +26 -0
- package/docs/rules/dep.install-scripts.md +26 -0
- package/docs/rules/dep.typosquat-proximity.md +30 -0
- package/docs/rules/egress.outbound-endpoints.md +29 -0
- package/docs/rules/egress.secret-adjacent.md +31 -0
- package/docs/rules/obf.charcode-chain.md +24 -0
- package/docs/rules/obf.dynamic-require.md +27 -0
- package/docs/rules/obf.encoded-payload.md +29 -0
- package/docs/rules/obf.eval-detect.md +26 -0
- package/docs/rules/obf.unparseable.md +25 -0
- package/docs/rules/perm.network-client.md +27 -0
- package/docs/rules/perm.seam-mismatch.md +34 -0
- package/docs/rules/perm.subprocess-spawn.md +28 -0
- package/docs/rules/perm.undeclared-fs-write.md +32 -0
- package/docs/rules/perm.unreachable-files.md +26 -0
- package/lib/index.d.mts +297 -0
- package/lib/index.mjs +1741 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Roger Deng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# dsh-vet
|
|
2
|
+
|
|
3
|
+
Security vetting for DeepSeek Harness (DSH) plugins: permission & supply-chain
|
|
4
|
+
audits before install, graded via the open [`dsh-vet/v1`](docs/dsh-vet-v1.md)
|
|
5
|
+
report standard.
|
|
6
|
+
|
|
7
|
+
> **Status: v0.1 complete.** Contract draft, reference scanner + CLI, rule
|
|
8
|
+
> set with public rationales, and an 11-package calibration sweep are in;
|
|
9
|
+
> the `0.1.0` npm release is the remaining step.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
Requires Node ≥ 20.
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install -g dsh-vet # or: pnpm add -g dsh-vet / bun add -g dsh-vet
|
|
17
|
+
dsh-vet --help
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Prefer not installing? `npx dsh-vet <specifier>` runs the same scanner with
|
|
21
|
+
zero footprint. The scanner is the only thing that runs — dsh-vet never
|
|
22
|
+
installs the plugin it audits.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npx dsh-vet <specifier> # npm package, git URL, or local path
|
|
28
|
+
npx dsh-vet --json <specifier> # dsh-vet/v1 report on stdout
|
|
29
|
+
npx dsh-vet --strict <specifier> # exit 1 on findings >= high (confidence >= medium)
|
|
30
|
+
npx dsh-vet --rules dep.install-scripts <specifier>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Any completed report exits `0` — grades describe findings, they do not gate.
|
|
34
|
+
Scanner failures exit non-zero. The scanner runs locally, reads the npm
|
|
35
|
+
registry for dependency metadata only, and never transmits audited code.
|
|
36
|
+
|
|
37
|
+
Shipped rules (each with a public rationale under
|
|
38
|
+
[`docs/rules/`](docs/rules)):
|
|
39
|
+
|
|
40
|
+
| Family | Rules |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `perm.*` | seam-mismatch, undeclared-fs-write, subprocess-spawn, network-client, unreachable-files |
|
|
43
|
+
| `dep.*` | install-scripts, floating-range, typosquat-proximity |
|
|
44
|
+
| `obf.*` | eval-detect, dynamic-require, encoded-payload, charcode-chain, unparseable |
|
|
45
|
+
| `egress.*` | outbound-endpoints, secret-adjacent |
|
|
46
|
+
|
|
47
|
+
dsh-vet audits itself with the same scanner:
|
|
48
|
+
[`examples/dsh-vet.report.json`](examples/dsh-vet.report.json) is generated
|
|
49
|
+
from the exact tarball that ships (`npm pack` → scan), seams declared in
|
|
50
|
+
package.json. It is not an A-by-cheating report — every signal the scanner
|
|
51
|
+
finds in itself is in there.
|
|
52
|
+
|
|
53
|
+
## Why
|
|
54
|
+
|
|
55
|
+
DSH's everything-is-a-plugin architecture is its greatest strength and its
|
|
56
|
+
largest attack surface: a plugin you install can register tools, touch the
|
|
57
|
+
filesystem, and open network connections. The community's single most-upvoted
|
|
58
|
+
feature request asks for marketplace standards and review mechanisms
|
|
59
|
+
([deepseek-harness#1115](https://github.com/deepseek-ai/deepseek-harness/discussions/1115)),
|
|
60
|
+
and incidents like a Full Access session deleting a user's home directory
|
|
61
|
+
([#461](https://github.com/deepseek-ai/deepseek-harness/discussions/461)) show
|
|
62
|
+
the stakes. The official marketplace will take time; trust tooling cannot.
|
|
63
|
+
|
|
64
|
+
`dsh-vet` exists so that "should I install this plugin?" has a shared,
|
|
65
|
+
machine-readable answer instead of vibes.
|
|
66
|
+
|
|
67
|
+
## What it checks (v0.1 scope)
|
|
68
|
+
|
|
69
|
+
- **Capability surface** — which Cordis seams a plugin injects (`fs`, `shell`,
|
|
70
|
+
`web`, …) versus what its manifest and README claim
|
|
71
|
+
- **Supply chain** — dependency tree, install scripts, `postinstall` hooks,
|
|
72
|
+
typosquat-adjacent package names
|
|
73
|
+
- **Obfuscated behavior** — `eval` / `new Function` / dynamic `require` /
|
|
74
|
+
encoded payloads
|
|
75
|
+
- **Data egress** — outbound endpoints reachable from code that can read
|
|
76
|
+
secrets, session data, or the DSH home directory
|
|
77
|
+
|
|
78
|
+
## The `dsh-vet/v1` report standard
|
|
79
|
+
|
|
80
|
+
The differentiating piece is not another scanner — it is
|
|
81
|
+
[`docs/dsh-vet-v1.md`](docs/dsh-vet-v1.md): an implementation-agnostic,
|
|
82
|
+
deterministic JSON report contract (findings with severity **and confidence**,
|
|
83
|
+
derived A–F grades) that any scanner may emit and any marketplace, CI job, or
|
|
84
|
+
UI may consume, in the spirit of the community's `dsh-doctor/v1` contract.
|
|
85
|
+
The TypeScript reference types ship from this package; third-party emitters
|
|
86
|
+
are welcome and listed here once verified.
|
|
87
|
+
|
|
88
|
+
## How it differs
|
|
89
|
+
|
|
90
|
+
| Tool | Form | Focus |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `dsh-plugin-vetting` | dsh plugin | install-time static heuristics |
|
|
93
|
+
| `dsh-audit` | CLI | ecosystem-wide catalog scoring (maintenance / docs / npm, security veto) |
|
|
94
|
+
| `dsh-plugin-audit` | dsh plugin | per-plugin permission profiling + runtime sentinel |
|
|
95
|
+
| `plugin_vet` skill pack | agent skills | audit methodology + gate |
|
|
96
|
+
| **`dsh-vet`** | library + CLI + CI action | the open `dsh-vet/v1` report contract, a reference scanner, and author-side badges — the shared trust layer others can emit and consume |
|
|
97
|
+
|
|
98
|
+
## Findings are signals, not verdicts
|
|
99
|
+
|
|
100
|
+
A report describes what code does, not what its author intended. Low-confidence
|
|
101
|
+
findings never lower a grade, and every finding carries evidence and a
|
|
102
|
+
remediation. If you believe a finding about your plugin is wrong, open a
|
|
103
|
+
[false-positive dispute](.github/ISSUE_TEMPLATE/false-positive.md) — disputed
|
|
104
|
+
rules get re-examined and the rule set gets corrected in public.
|
|
105
|
+
|
|
106
|
+
## Roadmap
|
|
107
|
+
|
|
108
|
+
- **v0.1** — contract frozen; reference CLI (`dsh vet <pkg>`) with the four
|
|
109
|
+
check families above
|
|
110
|
+
- **v0.2** — GitHub Action + badge so plugin authors self-audit and publish
|
|
111
|
+
their grade
|
|
112
|
+
- **v0.3** — marketplace integrations render `dsh-vet/v1` reports; contract
|
|
113
|
+
adopted by at least one third-party emitter
|
|
114
|
+
|
|
115
|
+
The detailed, trackable plan — task breakdowns, recorded decisions,
|
|
116
|
+
definitions of done, risks, and kill criteria — lives in
|
|
117
|
+
[ROADMAP.md](ROADMAP.md).
|
|
118
|
+
|
|
119
|
+
## Develop
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
pnpm install
|
|
123
|
+
pnpm test # vitest
|
|
124
|
+
pnpm build # tsdown → lib/
|
|
125
|
+
pnpm verify # typecheck + test + build + pack check
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Security
|
|
129
|
+
|
|
130
|
+
Reporting a vulnerability in dsh-vet itself: see [SECURITY.md](SECURITY.md).
|
|
131
|
+
dsh-vet runs locally, reads the npm registry for dependency metadata, and
|
|
132
|
+
never transmits audited code or results anywhere.
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
[MIT](LICENSE)
|
package/bin/dsh-vet.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# v0.1 calibration sweep
|
|
2
|
+
|
|
3
|
+
ROADMAP T6 requires a sweep of ~10 real ecosystem plugins with tuning until
|
|
4
|
+
zero **obvious** false positives. Swept on 2026-08-30 with scanner 0.1.0;
|
|
5
|
+
reports are committed under [`examples/sweep/`](../examples/sweep).
|
|
6
|
+
|
|
7
|
+
## Packages
|
|
8
|
+
|
|
9
|
+
| Package | Grade | Graded findings |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `dsh-searxng@0.1.1` | C | network-client (no `web` seam declared) |
|
|
12
|
+
| `@linxin666/dsh-doctor@0.3.6` | C | secret-adjacent¹, network-client, subprocess-spawn, undeclared-fs-write¹ |
|
|
13
|
+
| `dsh-zcf@0.5.3` | C | secret-adjacent¹, network-client, subprocess-spawn, undeclared-fs-write¹ |
|
|
14
|
+
| `@mlgbnb/dsh-archive-manager@1.0.7` | C | network-client, undeclared-fs-write¹ |
|
|
15
|
+
| `@michengai/dsh-skills-manager@0.1.30` | C | network-client, undeclared-fs-write¹ |
|
|
16
|
+
| `dsh-wechat@0.7.2` | C | network-client, undeclared-fs-write¹ |
|
|
17
|
+
| `dsh-find-plugin@0.3.7` | C | network-client |
|
|
18
|
+
| `dsh-better-sidebar@0.17.1` | C | secret-adjacent¹, encoded-payload¹, network-client, subprocess-spawn, undeclared-fs-write¹ |
|
|
19
|
+
| `@linxin666/dsh-live-stats@0.1.20` | A | — |
|
|
20
|
+
| `@aiwayds/dsh-tui-pi@1.1.0` | C | secret-adjacent¹, network-client, subprocess-spawn, undeclared-fs-write¹ |
|
|
21
|
+
| `dsh-fabric-host@0.1.11` | A | — |
|
|
22
|
+
|
|
23
|
+
¹ Emitted at `low` confidence (or includes only low-confidence parts) —
|
|
24
|
+
reported, never grade-affecting, per the contract.
|
|
25
|
+
|
|
26
|
+
Every capability finding was hand-checked against the package's code; every
|
|
27
|
+
one describes real code shape. No scanner failures, no criticals.
|
|
28
|
+
|
|
29
|
+
## What the sweep says about the ecosystem
|
|
30
|
+
|
|
31
|
+
- No swept package declares `dsh.seams` yet (the vocabulary ships with
|
|
32
|
+
dsh-vet), so honest plugins that legitimately use the network or spawn
|
|
33
|
+
processes read as C until they declare. That is the designed adoption
|
|
34
|
+
path, not a defect: the remediation in each finding is "declare the seam
|
|
35
|
+
or drop the capability".
|
|
36
|
+
- Bundled web UIs put `process.env` reads and `fetch` in one file; the
|
|
37
|
+
`egress.secret-adjacent` finding fires at `low` confidence there — signal,
|
|
38
|
+
not verdict, exactly as documented.
|
|
39
|
+
|
|
40
|
+
## Tuning performed
|
|
41
|
+
|
|
42
|
+
1. **`obf.encoded-payload`: hex literals now require ≥ 8 distinct
|
|
43
|
+
characters.** `dsh-better-sidebar`'s repeated-digit palette strings
|
|
44
|
+
(`8888…`) are tables, not encodings; encoded payloads use the hex
|
|
45
|
+
alphabet broadly. This removed 2 of 3 findings on that package.
|
|
46
|
+
2. **`dep.typosquat-proximity`: popular-name list verified against npm.**
|
|
47
|
+
`dsh-ankh-guard` was removed (not installable — a name that cannot be
|
|
48
|
+
installed cannot be typosquatted); verified-active names were added
|
|
49
|
+
(`dsh-zcf`, `dsh-wechat`, `dsh-better-sidebar`, `dsh-find-plugin`). The
|
|
50
|
+
list is now all-real by construction and re-checked each sweep.
|
|
51
|
+
|
|
52
|
+
## Accepted residuals
|
|
53
|
+
|
|
54
|
+
- One 60-char high-diversity hex literal in `dsh-better-sidebar`
|
|
55
|
+
(`4e79a7…` — concatenated d3 category colors). Indistinguishable from a
|
|
56
|
+
real encoding by charset statistics; stays as a medium/**low** finding.
|
|
57
|
+
- Typosquat distance-2 findings remain possible on short names; they emit
|
|
58
|
+
at `low` severity and cannot lower a grade.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# The `dsh-vet/v1` report contract
|
|
2
|
+
|
|
3
|
+
Status: **draft** — open for community input before v0.1 freezes it.
|
|
4
|
+
Reference TypeScript types: `src/contract.ts` (shipped from this package).
|
|
5
|
+
|
|
6
|
+
`dsh-vet/v1` defines a machine-readable audit report for a DeepSeek Harness
|
|
7
|
+
(DSH) plugin. It is implementation-agnostic: any scanner may emit it, and any
|
|
8
|
+
marketplace, CI job, or UI may consume it. It follows the community pattern
|
|
9
|
+
proven by `dsh-doctor/v1`: freeze a small, boring contract first, compete on
|
|
10
|
+
implementations.
|
|
11
|
+
|
|
12
|
+
## Design rules
|
|
13
|
+
|
|
14
|
+
1. **Findings are signals, not verdicts.** A report describes what code does.
|
|
15
|
+
It does not assert maliciousness, and it never names intent.
|
|
16
|
+
2. **Confidence gates severity.** Only findings with confidence `medium` or
|
|
17
|
+
`high` may lower a grade. A `low`-confidence finding is reported but cannot
|
|
18
|
+
cost the audited plugin a grade — false positives must not damage authors.
|
|
19
|
+
3. **Reports are deterministic.** `findings` are sorted (worst severity
|
|
20
|
+
first, then rule id ascending); `summary` is always derived from
|
|
21
|
+
`findings`, never asserted by the emitter. Two runs over the same artifact
|
|
22
|
+
with the same scanner version produce byte-identical reports (timestamps
|
|
23
|
+
aside).
|
|
24
|
+
4. **Consumers must ignore unknown fields.** Emitters may attach extra fields
|
|
25
|
+
(encouraged to prefix them `x-`); consumers must tolerate them. Emitters
|
|
26
|
+
must not change the meaning of fields defined here.
|
|
27
|
+
|
|
28
|
+
## Report envelope
|
|
29
|
+
|
|
30
|
+
```jsonc
|
|
31
|
+
{
|
|
32
|
+
"schema": "dsh-vet/v1", // literal; required
|
|
33
|
+
"target": { /* what was audited */ },
|
|
34
|
+
"scanner": { /* who audited, when */ },
|
|
35
|
+
"summary": { /* derived grade + counts */ },
|
|
36
|
+
"findings": [ /* zero or more findings, sorted */ }
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `target`
|
|
41
|
+
|
|
42
|
+
| Field | Type | Notes |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `kind` | `'npm-package' \| 'git-repo' \| 'local-path'` | |
|
|
45
|
+
| `specifier` | `string` | e.g. `dsh-vault@1.10.6`, a git URL, or a path |
|
|
46
|
+
| `resolved.version` | `string?` | version actually audited |
|
|
47
|
+
| `resolved.commit` | `string?` | for `git-repo` targets |
|
|
48
|
+
| `resolved.integrity` | `string?` | subresource integrity of the exact artifact scanned, when available |
|
|
49
|
+
|
|
50
|
+
### `scanner`
|
|
51
|
+
|
|
52
|
+
| Field | Type | Notes |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `name` | `string` | emitting implementation, e.g. `dsh-vet` |
|
|
55
|
+
| `version` | `string` | its version |
|
|
56
|
+
| `ranAt` | `string` | RFC 3339 timestamp of the run |
|
|
57
|
+
|
|
58
|
+
### `finding`
|
|
59
|
+
|
|
60
|
+
| Field | Type | Notes |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `id` | `string` | rule id; see below |
|
|
63
|
+
| `title` | `string` | one line, human |
|
|
64
|
+
| `severity` | `'critical' \| 'high' \| 'medium' \| 'low' \| 'info'` | |
|
|
65
|
+
| `confidence` | `'high' \| 'medium' \| 'low'` | |
|
|
66
|
+
| `evidence` | `VetEvidence[]` | at least one item |
|
|
67
|
+
| `remediation` | `string?` | how to make the finding go away |
|
|
68
|
+
| `references` | `string[]?` | URLs to rule documentation |
|
|
69
|
+
|
|
70
|
+
`VetEvidence` = `{ file, line?, snippet?, note? }`. Snippets must be minimal
|
|
71
|
+
and must never include secrets.
|
|
72
|
+
|
|
73
|
+
#### Severity definitions
|
|
74
|
+
|
|
75
|
+
- **critical** — direct credential/secret exfiltration, code execution during
|
|
76
|
+
install, or destructive filesystem operations outside any declared scope
|
|
77
|
+
- **high** — capability that contradicts the plugin's declared purpose
|
|
78
|
+
(undeclared broad fs/shell/net), obfuscated payloads, obvious typosquat
|
|
79
|
+
- **medium** — suspicious-but-explainable egress, `eval` of non-literal
|
|
80
|
+
input, install scripts without evident need
|
|
81
|
+
- **low** — hygiene: unpinned dependencies, missing license, unscoped
|
|
82
|
+
permissions in the manifest
|
|
83
|
+
- **info** — purely informational; never affects the grade
|
|
84
|
+
|
|
85
|
+
### Rule ids
|
|
86
|
+
|
|
87
|
+
Pattern: `^[a-z0-9-]+(\.[a-z0-9-]+)+$` — two or more dot-separated lowercase
|
|
88
|
+
segments. Reference rules use two segments (`perm.broad-fs-write`,
|
|
89
|
+
`dep.postinstall-script`); third-party rule sets prefix their own vendor
|
|
90
|
+
segment (`acme.eval-detect`). The pattern is deliberately open-ended about
|
|
91
|
+
segment count: a closed single-segment pattern is exactly how `dsh-doctor/v1`
|
|
92
|
+
initially broke vendor-prefixed check ids — do not repeat that.
|
|
93
|
+
|
|
94
|
+
### `summary`
|
|
95
|
+
|
|
96
|
+
`counts` = per-severity finding totals. `grade` is derived:
|
|
97
|
+
|
|
98
|
+
| Grade | Condition (over findings with confidence ≥ `medium`) |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `A` | none, or only `info`/`low`-severity findings |
|
|
101
|
+
| `B` | worst graded finding is `low` |
|
|
102
|
+
| `C` | worst graded finding is `medium` |
|
|
103
|
+
| `D` | worst graded finding is `high` |
|
|
104
|
+
| `F` | at least one `critical` |
|
|
105
|
+
| `X` | scan incomplete or errored — never presented as the plugin's grade |
|
|
106
|
+
|
|
107
|
+
## CLI recommendations (non-normative)
|
|
108
|
+
|
|
109
|
+
Emitters that ship a CLI should exit `0` whenever a report was produced —
|
|
110
|
+
findings are data, not failures — and non-zero only on scanner failure.
|
|
111
|
+
A `--strict` flag may exit `1` when findings of severity ≥ `high` with
|
|
112
|
+
confidence ≥ `medium` exist, for CI gating.
|
|
113
|
+
|
|
114
|
+
## Example
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"schema": "dsh-vet/v1",
|
|
119
|
+
"target": {
|
|
120
|
+
"kind": "npm-package",
|
|
121
|
+
"specifier": "fixture-plugin@1.2.3",
|
|
122
|
+
"resolved": { "version": "1.2.3", "integrity": "sha512-…" }
|
|
123
|
+
},
|
|
124
|
+
"scanner": { "name": "dsh-vet", "version": "0.1.0", "ranAt": "2026-09-01T00:00:00Z" },
|
|
125
|
+
"summary": { "grade": "C", "counts": { "critical": 0, "high": 0, "medium": 1, "low": 1, "info": 1 } },
|
|
126
|
+
"findings": [
|
|
127
|
+
{
|
|
128
|
+
"id": "egress.endpoint-reach",
|
|
129
|
+
"title": "Sends data to an endpoint from code that can read the DSH home directory",
|
|
130
|
+
"severity": "medium",
|
|
131
|
+
"confidence": "high",
|
|
132
|
+
"evidence": [{ "file": "host-half.js", "line": 42, "note": "fetch to https://example.example/collect" }],
|
|
133
|
+
"remediation": "Document the endpoint and what is sent, or remove the call."
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "dep.unpinned",
|
|
137
|
+
"title": "Dependency ranges are not pinned",
|
|
138
|
+
"severity": "low",
|
|
139
|
+
"confidence": "medium",
|
|
140
|
+
"evidence": [{ "file": "package.json" }]
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"id": "meta.no-license",
|
|
144
|
+
"title": "No license field",
|
|
145
|
+
"severity": "info",
|
|
146
|
+
"confidence": "high",
|
|
147
|
+
"evidence": [{ "file": "package.json" }]
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Versioning
|
|
154
|
+
|
|
155
|
+
`/v1` freezes at the v0.1 release of this package. Backward-compatible
|
|
156
|
+
additions (new optional fields, new rule ids) stay in `/v1`; semantic changes
|
|
157
|
+
get `/v2` with a migration note. Discussion happens in
|
|
158
|
+
[GitHub Discussions](https://github.com/rogerdigital/dsh-vet/discussions)
|
|
159
|
+
and the [dsh-plugin topic](https://github.com/topics/dsh-plugin).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# dep.floating-range
|
|
2
|
+
|
|
3
|
+
Runtime dependencies that resolve to whatever the registry serves.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
`dependencies` entries whose spec is `*`, `latest`, `x`, or empty — anything
|
|
8
|
+
the registry can satisfy with a version that did not exist when the plugin
|
|
9
|
+
was last reviewed. `^` and `~` ranges are **not** flagged: they are normal
|
|
10
|
+
npm practice and stay within a reviewed major/minor.
|
|
11
|
+
|
|
12
|
+
Only `dependencies` are checked; `devDependencies` do not execute on the
|
|
13
|
+
installing user's machine.
|
|
14
|
+
|
|
15
|
+
## Severity / confidence policy
|
|
16
|
+
|
|
17
|
+
**medium / high** — the spec's supply-chain tier.
|
|
18
|
+
|
|
19
|
+
## False positives
|
|
20
|
+
|
|
21
|
+
Rare; the floating set is deliberately minimal. If an ecosystem convention
|
|
22
|
+
emerges around another spec form, it gets listed here.
|
|
23
|
+
|
|
24
|
+
## Remediation
|
|
25
|
+
|
|
26
|
+
Pin to an exact version or a narrow range.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# dep.install-scripts
|
|
2
|
+
|
|
3
|
+
Runs code at install time.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
`preinstall`, `install`, and `postinstall` entries in package.json
|
|
8
|
+
`scripts`. These execute on the **installing** user's machine, before the
|
|
9
|
+
plugin has been audited or sandboxed by anything.
|
|
10
|
+
|
|
11
|
+
## Severity / confidence policy
|
|
12
|
+
|
|
13
|
+
**medium / high.** Presence is a fact from the manifest. The spec calls
|
|
14
|
+
install scripts without evident need *medium*; static analysis cannot see
|
|
15
|
+
"need", so the finding carries a remediation instead of a guess.
|
|
16
|
+
|
|
17
|
+
## False positives
|
|
18
|
+
|
|
19
|
+
Genuine build-from-source cases (native modules). The remediation documents
|
|
20
|
+
the expectation: explain the script in the README, or move the work to
|
|
21
|
+
publish time.
|
|
22
|
+
|
|
23
|
+
## Remediation
|
|
24
|
+
|
|
25
|
+
Remove the lifecycle script, or move the work to a prepublish/build step and
|
|
26
|
+
document why install-time execution is required.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# dep.typosquat-proximity
|
|
2
|
+
|
|
3
|
+
Dependency name is one or two edits from a popular `dsh-*` package.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
Each runtime dependency is compared (bounded Levenshtein distance) against a
|
|
8
|
+
curated list of popular `dsh-*` package names. Every list entry is verified
|
|
9
|
+
to exist on npm — a name that cannot be installed cannot be typosquatted —
|
|
10
|
+
and the list is re-checked during calibration sweeps. Exact matches to a
|
|
11
|
+
popular name are skipped.
|
|
12
|
+
|
|
13
|
+
## Severity / confidence policy
|
|
14
|
+
|
|
15
|
+
- Distance 1 (one insert/delete/substitute away): **high / medium.**
|
|
16
|
+
- Distance 2: **low / medium** — reported, cannot lower a grade.
|
|
17
|
+
|
|
18
|
+
Confidence stays medium because proximity is a heuristic, not proof: the
|
|
19
|
+
dependency's repository, publisher, and download history decide.
|
|
20
|
+
|
|
21
|
+
## False positives
|
|
22
|
+
|
|
23
|
+
Legitimate adjacent names (a monorepo's `-cli`/`-core` companions, or a name
|
|
24
|
+
that happens to differ by one edit). Dispute with the package's provenance
|
|
25
|
+
and, if the name is established, it joins the popular list.
|
|
26
|
+
|
|
27
|
+
## Remediation
|
|
28
|
+
|
|
29
|
+
Verify the dependency is the package you mean — exact spelling, real
|
|
30
|
+
repository, real publisher.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# egress.outbound-endpoints
|
|
2
|
+
|
|
3
|
+
Outbound endpoints the code can contact.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
Literal hosts and URLs passed to the network-client calls tracked by
|
|
8
|
+
`perm.network-client`. One evidence entry per unique endpoint (URLs reduce
|
|
9
|
+
to their host), sorted, with the first call site.
|
|
10
|
+
|
|
11
|
+
## Severity / confidence policy
|
|
12
|
+
|
|
13
|
+
**info / high.** This rule is inventory, never a judgment: contacting a host
|
|
14
|
+
is not wrong, but every host is a data-flow decision the author owns, and
|
|
15
|
+
consumers of the report deserve the list. Grading is left to
|
|
16
|
+
`egress.secret-adjacent` when endpoints meet secret reads.
|
|
17
|
+
|
|
18
|
+
Endpoints with non-literal (runtime-computed) targets cannot be listed —
|
|
19
|
+
that gap shows up as `obf.dynamic-require`-style opacity elsewhere.
|
|
20
|
+
|
|
21
|
+
## False positives
|
|
22
|
+
|
|
23
|
+
Endpoints that are never contacted in practice (dead code paths) still
|
|
24
|
+
appear; reachability from an entry point is visible in the report via
|
|
25
|
+
`perm.unreachable-files`.
|
|
26
|
+
|
|
27
|
+
## Remediation
|
|
28
|
+
|
|
29
|
+
Document each endpoint in the README.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# egress.secret-adjacent
|
|
2
|
+
|
|
3
|
+
Network calls reachable from code that reads secrets.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
Files that read secrets — `process.env`, `os.homedir()`, or credential-like
|
|
8
|
+
file paths (`.env*`, `credentials`, `auth.json`, `token`, `.dsh/`, `.ssh`,
|
|
9
|
+
`.npmrc`, `.netrc`) — and any network-client call in that file or in files
|
|
10
|
+
statically reachable **from** it.
|
|
11
|
+
|
|
12
|
+
## Severity / confidence policy
|
|
13
|
+
|
|
14
|
+
- **high** severity. Secrets plus egress in one static graph is the shape of
|
|
15
|
+
exfiltration; it is also the shape of every API client that authenticates,
|
|
16
|
+
so severity stays at high, not critical — critical requires proof of data
|
|
17
|
+
flow, which static analysis does not have.
|
|
18
|
+
- Confidence **medium** when the same file reads a secret and calls a
|
|
19
|
+
literal endpoint (the common, explainable API-client case); **low** when
|
|
20
|
+
the connection only exists through imports.
|
|
21
|
+
|
|
22
|
+
## False positives
|
|
23
|
+
|
|
24
|
+
Any legitimate authenticated client. This finding is the report's most
|
|
25
|
+
important *signal, not verdict* case: read the endpoints, decide if the data
|
|
26
|
+
flow matches the plugin's documented purpose.
|
|
27
|
+
|
|
28
|
+
## Remediation
|
|
29
|
+
|
|
30
|
+
Keep secret reads and network clients in separate modules, or document the
|
|
31
|
+
intended data flow for each endpoint.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# obf.charcode-chain
|
|
2
|
+
|
|
3
|
+
Builds strings from character codes.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
`String.fromCharCode(...)` calls with 8+ numeric literal arguments. The
|
|
8
|
+
finding's note shows the decoded preview, so the reviewer sees what is being
|
|
9
|
+
hidden from grep.
|
|
10
|
+
|
|
11
|
+
## Severity / confidence policy
|
|
12
|
+
|
|
13
|
+
**medium / medium.** Building a readable string through charcodes is not a
|
|
14
|
+
normal authoring pattern; there is a credible benign population (obfuscating
|
|
15
|
+
game spoilers, license checks) small enough to justify the confidence.
|
|
16
|
+
|
|
17
|
+
## False positives
|
|
18
|
+
|
|
19
|
+
Table-driven encoders (font subsets, protocol codecs) that feed arrays, not
|
|
20
|
+
inline literals — the literal-argument requirement excludes most of them.
|
|
21
|
+
|
|
22
|
+
## Remediation
|
|
23
|
+
|
|
24
|
+
Write the string literal directly.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# obf.dynamic-require
|
|
2
|
+
|
|
3
|
+
Loads modules through a computed specifier.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
`require(x)` and `import(x)` where `x` is not a plain string literal —
|
|
8
|
+
runtime variables, or concatenations that static evaluation can recover
|
|
9
|
+
(`'./mod-' + 'ule.js'`).
|
|
10
|
+
|
|
11
|
+
## Severity / confidence policy
|
|
12
|
+
|
|
13
|
+
- Concatenation that statically resolves to one value: **medium / medium.**
|
|
14
|
+
The loaded module is knowable, but the graph hides from naive review.
|
|
15
|
+
- Fully runtime-dependent specifier: **medium / low** (ROADMAP D2 — depends
|
|
16
|
+
on runtime values, so it never lowers a grade by itself).
|
|
17
|
+
|
|
18
|
+
## False positives
|
|
19
|
+
|
|
20
|
+
Optional-dependency loaders (`require(name)` in a try/catch) and plugin
|
|
21
|
+
systems that resolve names from config. Those are exactly the patterns the
|
|
22
|
+
finding asks you to make visible — the specifier is recoverable, say it in
|
|
23
|
+
the source.
|
|
24
|
+
|
|
25
|
+
## Remediation
|
|
26
|
+
|
|
27
|
+
Use plain specifiers so the module graph stays auditable.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# obf.encoded-payload
|
|
2
|
+
|
|
3
|
+
Long base64/hex string literals in shipped code.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
String literals of 48+ characters with no whitespace whose entire charset is
|
|
8
|
+
base64 (letters + digits + `/` `=`, actually mixing cases with digits) or hex
|
|
9
|
+
— and for hex, using at least 8 distinct characters. Palette and table data
|
|
10
|
+
(think `8888…`, d3 category colors) reuse a handful of digits; encoded
|
|
11
|
+
payloads use the alphabet broadly. The diversity floor was calibrated on the
|
|
12
|
+
v0.1 ecosystem sweep ([calibration record](../calibration-v0.1.md)).
|
|
13
|
+
|
|
14
|
+
## Severity / confidence policy
|
|
15
|
+
|
|
16
|
+
**medium / low.** An encoded blob is often legitimate (fonts, hashes,
|
|
17
|
+
fixtures) — hence low confidence: reported, never grade-affecting on its own.
|
|
18
|
+
It matters when it sits next to `eval` or egress, which is exactly what a
|
|
19
|
+
reviewer checks next.
|
|
20
|
+
|
|
21
|
+
## False positives
|
|
22
|
+
|
|
23
|
+
Sourcemaps-in-strings, embedded assets, test vectors. The finding is
|
|
24
|
+
inventory for reviewers; a blob with a visible decoder gets disputed in one
|
|
25
|
+
message.
|
|
26
|
+
|
|
27
|
+
## Remediation
|
|
28
|
+
|
|
29
|
+
Decode payloads to plain assets, or ship them as files a reviewer can open.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# obf.eval-detect
|
|
2
|
+
|
|
3
|
+
Evaluates dynamically built code.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
`eval(...)` calls and `new Function(...)` constructions in shipped JS.
|
|
8
|
+
|
|
9
|
+
## Severity / confidence policy
|
|
10
|
+
|
|
11
|
+
- Argument is not a string literal (variable, concatenation, decoding result):
|
|
12
|
+
**medium / medium** — the spec's "eval of non-literal input" tier.
|
|
13
|
+
- Argument is a constant string: **info / high** — inert (the code is visible
|
|
14
|
+
in the literal), still reported because eval-shaped code is where payload
|
|
15
|
+
injection starts.
|
|
16
|
+
|
|
17
|
+
## False positives
|
|
18
|
+
|
|
19
|
+
DSLs and template engines that genuinely need runtime compilation. Declare
|
|
20
|
+
the case in your README; if the pattern is common enough, the rule learns a
|
|
21
|
+
documented exception.
|
|
22
|
+
|
|
23
|
+
## Remediation
|
|
24
|
+
|
|
25
|
+
Replace `eval`/`new Function` with direct code; dynamic evaluation defeats
|
|
26
|
+
static audit.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# obf.unparseable
|
|
2
|
+
|
|
3
|
+
Shipped JS files that no standard parser accepts.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
Files with a `.js`/`.mjs`/`.cjs` extension (or Node-shebang bin scripts) that
|
|
8
|
+
fail to parse both as an ECMAScript module and as a script under acorn.
|
|
9
|
+
|
|
10
|
+
## Severity / confidence policy
|
|
11
|
+
|
|
12
|
+
**medium / high.** That the file does not parse is certain; what it actually
|
|
13
|
+
runs is not. Shipped-unparseable JS usually means generated or deliberately
|
|
14
|
+
mangled code that plain review tooling cannot see into — the finding asks
|
|
15
|
+
for source that reviewers can read.
|
|
16
|
+
|
|
17
|
+
## False positives
|
|
18
|
+
|
|
19
|
+
New syntax acorn has not learned yet (bleeding-edge proposals), or files
|
|
20
|
+
that are data with a misleading extension. Dispute with the syntax; the
|
|
21
|
+
finder tracks acorn's version.
|
|
22
|
+
|
|
23
|
+
## Remediation
|
|
24
|
+
|
|
25
|
+
Ship parseable source, or source maps that let tooling see the real code.
|