dsh-plugin-guide 0.4.6 → 0.5.1
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/SKILL.md +6 -0
- package/docs/boost.md +14 -0
- package/docs/entry.md +44 -1
- package/docs/flow.md +20 -0
- package/docs/levels.md +22 -0
- package/docs/rules.md +27 -2
- package/docs/security.md +427 -0
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -56,3 +56,9 @@ It defines the three layers and the difference between a check and advice.
|
|
|
56
56
|
This project is non-official and has no certification, accreditation, security
|
|
57
57
|
audit, or market gate. It does not score or rank plugins. When in doubt, link to
|
|
58
58
|
the source document and state what was not run.
|
|
59
|
+
|
|
60
|
+
For plugin security — capabilities, credentials, egress, install scripts, and
|
|
61
|
+
supply chain — read `docs/security.md`. The security scope is static and shipped
|
|
62
|
+
in `dsh-standards` 1.6.0+; `DSC-15`/`DSC-16`/`DSC-17` can block only under
|
|
63
|
+
`--scope security`, `DSC-21` is WARN-only, and runtime security needs a sandbox,
|
|
64
|
+
not a checker.
|
package/docs/boost.md
CHANGED
|
@@ -90,6 +90,20 @@ pitfalls”), record it as a WARN or a note. Do not invent a number.
|
|
|
90
90
|
loader/context-level fixtures.
|
|
91
91
|
- BOOST is not a market gate and must not become a certification.
|
|
92
92
|
|
|
93
|
+
<a id="security-boundary"></a>
|
|
94
|
+
## Security boundary
|
|
95
|
+
|
|
96
|
+
BOOST measures Cordis practice, not security. A reversible effect or a seam is
|
|
97
|
+
good engineering, and it reduces blast radius, but it is not a sandbox: the
|
|
98
|
+
plugin still shares the host process, the user, and the network. The static
|
|
99
|
+
`DSC-15`–`DSC-22` checks run in a separate `--scope security` (1.6.0+); outside
|
|
100
|
+
it they are capped to WARN in the separate `security` block. `DSC-15`/`16`/`17`
|
|
101
|
+
can block only under that scope, and `DSC-21` is WARN-only. They never change a
|
|
102
|
+
BOOST result and none of them proves a plugin safe. Runtime questions — actual
|
|
103
|
+
egress, file reads, prompt corruption — require a sandbox with a network policy
|
|
104
|
+
and capture, not an evidence card. Keep the security claim `not-run` instead of
|
|
105
|
+
borrowing BOOST evidence for it. See [security.md](security.md#scope).
|
|
106
|
+
|
|
93
107
|
## Run the audit
|
|
94
108
|
|
|
95
109
|
```sh
|
package/docs/entry.md
CHANGED
|
@@ -160,6 +160,10 @@ pass.
|
|
|
160
160
|
fs write, or subprocess spawn. Do that work in `apply()` and clean up with
|
|
161
161
|
`ctx.effect()`.
|
|
162
162
|
- README says how to install and what it needs.
|
|
163
|
+
- No `preinstall`/`install`/`postinstall`/`prepare` script in the published
|
|
164
|
+
package; if a build is needed, run it in CI, not on the user's machine.
|
|
165
|
+
- No `eval`/`new Function`/`vm`; credentials go through `ctx.credentials`, not
|
|
166
|
+
a backing file; third-party endpoints are disclosed.
|
|
163
167
|
- No “official”, “certified”, “secure”, or ranking claim.
|
|
164
168
|
|
|
165
169
|
## Declaration fields
|
|
@@ -303,7 +307,46 @@ export function apply(ctx) {
|
|
|
303
307
|
}
|
|
304
308
|
```
|
|
305
309
|
|
|
310
|
+
<a id="security-checks"></a>
|
|
311
|
+
## Security checks (DSC-15 to DSC-22)
|
|
312
|
+
|
|
313
|
+
ENTRY is a compatibility gate; it does not answer "is this safe to install". The
|
|
314
|
+
static security scope ships in `dsh-standards` 1.6.0+ and evaluates eight
|
|
315
|
+
checks; with `--scope security` the report's `checks[]` carries all 22
|
|
316
|
+
(`DSC-01`–`DSC-14` plus `DSC-15`–`DSC-22`).
|
|
317
|
+
|
|
318
|
+
| Check | What it looks for | Verdict in `--scope security` |
|
|
319
|
+
|---|---|---|
|
|
320
|
+
| `DSC-15` | `preinstall`/`install`/`postinstall`/`prepare` that download, decode, or execute (command string and referenced in-package script body, ≤512 KB) | blocking |
|
|
321
|
+
| `DSC-16` | `eval`, `new Function`, `vm`, `module._compile`, `process.binding`; bare `child_process` is WARN in 1.6.1 | blocking |
|
|
322
|
+
| `DSC-17` | reads of `~/.ssh`, `~/.aws`, `.npmrc`, `.netrc`, or credential backing files | blocking |
|
|
323
|
+
| `DSC-18` | non-https, userinfo, bare-IP, webhook/paste/tunnel egress targets | WARN (clue) |
|
|
324
|
+
| `DSC-19` | minified blobs, large base64 blocks, wasm/native without source | advisory |
|
|
325
|
+
| `DSC-20` | tool/web text flowing into the system prompt without a boundary | advisory (static v1 + runtime hook v2) |
|
|
326
|
+
| `DSC-21` | typosquat names, fake scopes, unpinned or script-bearing dependencies | mandatory but **WARN-only, never blocking** (listing included) |
|
|
327
|
+
| `DSC-22` | `dist.integrity`, signatures, provenance attestations | advisory |
|
|
328
|
+
|
|
329
|
+
`--scope security` is released in 1.6.0+; 1.5.x and earlier exit `3` for the
|
|
330
|
+
unknown scope. Outside that scope the eight are not in `checks[]`; a security
|
|
331
|
+
`FAIL` is capped to WARN in the separate `security` block, never enters
|
|
332
|
+
`buckets.blocking`, and cannot change an ENTRY verdict. Only `15`/`16`/`17` can
|
|
333
|
+
block (and only under `--scope security`); `21` never blocks; `18`/`19`/`20`/`22`
|
|
334
|
+
are advice. `DSC-16` subprocess FAIL is scoped to 1.6.2; 1.6.1 keeps a bare
|
|
335
|
+
`child_process` import at WARN.
|
|
336
|
+
|
|
337
|
+
```sh
|
|
338
|
+
# 1.6.0+; exits 3 only on 1.5.x and earlier
|
|
339
|
+
npx dsh-standards ./my-plugin --scope security --format json
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The author, user, and market checklists — with copyable manifests, commands,
|
|
343
|
+
and the boundary between static checks and a runtime sandbox — are in
|
|
344
|
+
[security.md](security.md). ENTRY's own declaration fields are not enforcement,
|
|
345
|
+
and neither is a `DSC-11` PASS.
|
|
346
|
+
|
|
306
347
|
## What ENTRY does not do
|
|
307
348
|
|
|
308
349
|
It is not a security audit, sandbox, full contract audit, market gate, score, or
|
|
309
|
-
ranking. Those are separate concerns; do not borrow their authority.
|
|
350
|
+
ranking. Those are separate concerns; do not borrow their authority. The static
|
|
351
|
+
`DSC-15`–`DSC-22` checks are a separate security scope, covered in
|
|
352
|
+
[security.md](security.md), not part of the ENTRY verdict.
|
package/docs/flow.md
CHANGED
|
@@ -201,6 +201,26 @@ the original `command[]`, `exitCode`, the last 20 lines of stderr, and the
|
|
|
201
201
|
absolute log path. Then attach the clause anchor the journey maps to (for
|
|
202
202
|
example `J2.toggle → 32-DSH-FLOW #R2`).
|
|
203
203
|
|
|
204
|
+
<a id="security-boundary"></a>
|
|
205
|
+
## Security boundary
|
|
206
|
+
|
|
207
|
+
J5 checks that permissions are declared consistently; it is static preflight,
|
|
208
|
+
not a security test. The static `DSC-15`–`DSC-22` checks live behind a separate
|
|
209
|
+
`--scope security`, shipped in `dsh-standards` 1.6.0+ (1.5.x and earlier exit
|
|
210
|
+
`3`), and they never turn a FLOW journey into a security pass. Outside that
|
|
211
|
+
scope a security finding is advisory: it is capped to WARN in the separate
|
|
212
|
+
`security` block, never enters `buckets.blocking`, and cannot change the FLOW
|
|
213
|
+
verdict. Under `--scope security` only `DSC-15`/`DSC-16`/`DSC-17` can block;
|
|
214
|
+
`DSC-21` is WARN-only.
|
|
215
|
+
|
|
216
|
+
Static checks also cannot observe what a plugin does at runtime — which URLs it
|
|
217
|
+
reaches, which files it reads, or whether a prompt was corrupted. Those answers
|
|
218
|
+
need a sandbox with a network policy plus capture, and a sandbox that only
|
|
219
|
+
limits subprocess file writes is not that boundary. So do not write “FLOW
|
|
220
|
+
passes, therefore it is safe”; write what was observed, and keep the security
|
|
221
|
+
claim `not-run` until it was actually run. Details:
|
|
222
|
+
[security.md](security.md#scope).
|
|
223
|
+
|
|
204
224
|
## Status, not a score
|
|
205
225
|
|
|
206
226
|
| Status | Meaning |
|
package/docs/levels.md
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
| an ENTRY question | [entry.md](entry.md) — package shape, runtime surface, declarations |
|
|
21
21
|
| a FLOW journey to record | [flow.md](flow.md) — the `dsh-flow-evidence/2` file and J1–J8 evidence |
|
|
22
22
|
| a BOOST practice to evidence | [boost.md](boost.md) — the thirteen-field evidence card |
|
|
23
|
+
| a security question — what can a plugin do, is this safe to install, what must a market disclose | [security.md](security.md) — author/user/market checklists and the `DSC-15`–`DSC-22` boundary |
|
|
23
24
|
|
|
24
25
|
The two reference docs exist because a report ID alone does not tell you what to
|
|
25
26
|
change: [rules.md](rules.md) maps IDs to fixes, and [recipes.md](recipes.md)
|
|
@@ -73,6 +74,26 @@ Start here: [BOOST guide](boost.md) and the canonical
|
|
|
73
74
|
The card fields are in
|
|
74
75
|
[boost.md § evidence, not a badge](boost.md#thirteen-fields).
|
|
75
76
|
|
|
77
|
+
## Security is not a fourth layer
|
|
78
|
+
|
|
79
|
+
Security checks are a **scope**, not a level, and they ship in `dsh-standards`
|
|
80
|
+
1.6.0+ (1.5.x and earlier exit `3` for the unknown scope): `DSC-15`–`DSC-22`
|
|
81
|
+
sit behind `--scope security`. They never change an ENTRY, FLOW, or BOOST
|
|
82
|
+
verdict. Outside that scope a security `FAIL` is capped to WARN in the separate
|
|
83
|
+
`security` block and does not enter `buckets.blocking`; under it only
|
|
84
|
+
`DSC-15`/`16`/`17` can block, and `DSC-21` is WARN-only.
|
|
85
|
+
|
|
86
|
+
- Static checks only match known source shapes; they cannot see runtime egress,
|
|
87
|
+
file reads, or prompt corruption. Runtime security needs a sandbox with a
|
|
88
|
+
network policy and capture.
|
|
89
|
+
- No security `PASS` means “safe”. It means “no known pattern matched in this
|
|
90
|
+
static pass”.
|
|
91
|
+
- The three layers stay about compatibility, user journeys, and Cordis practice.
|
|
92
|
+
Do not turn them into a security certification, and do not add a “security
|
|
93
|
+
level” to the vocabulary.
|
|
94
|
+
|
|
95
|
+
Read [security.md](security.md) for the author, user, and market checklists.
|
|
96
|
+
|
|
76
97
|
## Run the checks
|
|
77
98
|
|
|
78
99
|
```sh
|
|
@@ -94,5 +115,6 @@ The legacy build skill/CLI is [`dsh-primer`](https://github.com/PerryLink/dsh-pr
|
|
|
94
115
|
- `static` ≠ `runtime`
|
|
95
116
|
- `not-run` ≠ `pass`
|
|
96
117
|
- `boost-evidenced` ≠ plugin grade
|
|
118
|
+
- security check ≠ security audit
|
|
97
119
|
- `A–D` grades (where they exist) are orthogonal to ENTRY/FLOW/BOOST
|
|
98
120
|
- No official, certified, verified, audited, secure, recommended, or ranked claim
|
package/docs/rules.md
CHANGED
|
@@ -22,8 +22,10 @@ incomplete/not-run, `3` usage error. `WARN` is not a clean pass and
|
|
|
22
22
|
The JSON report also carries a sibling `advice[]` array: one entry per live
|
|
23
23
|
detail, with `title`, `why`, `fix`, `docsUrl`, `severity`, and `evidence`. It is
|
|
24
24
|
additive and never changes the verdict (turn it off with `--no-advice`). The
|
|
25
|
-
`docsUrl` follows the checker's `DOCS_BASE
|
|
26
|
-
|
|
25
|
+
`docsUrl` follows the checker's `DOCS_BASE`: 1.6.1 pins `v0.4.6`, and the 1.6.2
|
|
26
|
+
release moves it to guide `v0.5.0`. The `docs/security.md` anchors used by the
|
|
27
|
+
`C-15`–`C-22` advice exist in 0.5.0; if a link points at an older page, this
|
|
28
|
+
table is the current mapping.
|
|
27
29
|
|
|
28
30
|
<a id="scope-matrix"></a>
|
|
29
31
|
## Which checks run in which scope
|
|
@@ -35,6 +37,7 @@ at an older page, this table is the current mapping.
|
|
|
35
37
|
| `--scope entry --entry-claim static` | 01, 02, 11, 12, 13 (+ 03 for listing) | 04–09 are attempted and printed (usually `ERROR`/`NA`), 14 is `NA`; a `DSC-11`/`DSC-12`/`DSC-13` FAIL is downgraded to `WARN` |
|
|
36
38
|
| `--scope entry --entry-claim runtime` | + 14 | 14 carries the runtime verdict, not 04–09 |
|
|
37
39
|
| `--scope mvp` | 01, 02, 04–09 | the MVP subset, labelled `MVP-13` |
|
|
40
|
+
| `--scope security` (1.6.0+) | + 15, 16, 17, 21; 18/19/20/22 advisory | `checks[]` becomes 22 (14 + 8); DS-C 01–14 are unchanged and the report gains an additive `security` block. `15`/`16`/`17` blocking, `21` mandatory but WARN-only |
|
|
38
41
|
|
|
39
42
|
<a id="table"></a>
|
|
40
43
|
## The check table
|
|
@@ -55,10 +58,27 @@ at an older page, this table is the current mapping.
|
|
|
55
58
|
| **DSC-12** | C-12 credentials + seams | Blocking (ds-c); `WARN` in ENTRY. | Read credentials through `ctx.credentials`, declare `credentials:list` for `listRecords()` and `credentials:provider` for a provider, and never read the backing file. | [entry.md § credentials and seams](entry.md#credentials-and-seams) |
|
|
56
59
|
| **DSC-13** | C-13 labels + mirror | Blocking (ds-c); `WARN` in ENTRY, advisory as a WARN. | Use only the five labels, add the label each capability requires, point `dsh.upstream` at an https URL that is not this package **and** add `mirror`, and drop endorsement words. | [entry.md § labels and mirror](entry.md#labels-and-mirror) |
|
|
57
60
|
| **DSC-14** | C-14 entry run | Blocking only for a runtime claim (`--scope entry`). | Supply the real host: `--host` for a bundle (G0–G3) or `--runtime` for a plugin (L1); a client or preset cannot claim runtime. | [entry.md § runtime surface](entry.md#runtime-surface) |
|
|
61
|
+
| **DSC-15** | C-15 lifecycle scripts | **Blocking only under `--scope security`** (1.6.0+); not run in the default scopes. It reads the referenced in-package script body (≤512 KB) as well as the command string. | Ship no `preinstall`/`install`/`postinstall`/`prepare`; if one is unavoidable, no download-to-shell, `base64 -d`, or `node -e`. | [security.md § author 4](security.md#author-files) |
|
|
62
|
+
| **DSC-16** | C-16 dynamic execution | **Blocking only under `--scope security`**. `eval`/`Function`/`vm`/`_compile`/`process.binding` FAIL; a bare `child_process` import is WARN in 1.6.1, and the dangerous-subprocess FAIL is 1.6.2. | Remove `eval`, `new Function`, `vm.runIn*`, `module._compile`, and `process.binding`; dynamic `import()` takes a static path only. | [security.md § author 4](security.md#author-files) |
|
|
63
|
+
| **DSC-17** | C-17 sensitive files | **Blocking only under `--scope security`**. | Read credentials through `ctx.credentials`; never read `~/.ssh`, `~/.aws`, `.npmrc`, `.netrc`, or a credential backing file. | [security.md § author 2](security.md#author-credentials) |
|
|
64
|
+
| **DSC-18** | C-18 egress endpoints | **WARN** under `--scope security` (clue, not blocking). | Use a constant `https` allowlist, `redirect: 'manual'`, no credentials across origins, and disclose third-party endpoints. | [security.md § author 3](security.md#author-egress) |
|
|
65
|
+
| **DSC-19** | C-19 obfuscation/binary | **Advisory** under `--scope security`. | No minified blobs or binary-only code; ship wasm/native with source, build method, and `sha256`. | [security.md § author 4](security.md#author-files) |
|
|
66
|
+
| **DSC-20** | C-20 prompt injection | **Advisory**: static v1 (external text into `systemPrompt`, instruction-like literals) plus a runtime hook in v2; never blocking. | Treat tool/web/file/session text as untrusted, validate `output.schema`, and wrap external text before it reaches a prompt. | [security.md § author 5](security.md#author-prompt) |
|
|
67
|
+
| **DSC-21** | C-21 supply chain | **Mandatory under `--scope security`, but WARN-only and never blocking** (listing included). | Commit the lockfile, pin dependencies, reject typosquat/lookalike scopes, and treat an unpinned or script-bearing dependency as `not-run`. | [security.md § author 6](security.md#author-publish) |
|
|
68
|
+
| **DSC-22** | C-22 provenance/integrity | **Advisory**, never blocking. | Publish with provenance/attestations; `dist.integrity` only binds the tarball to the registry, not the code to a source. | [security.md § author 6](security.md#author-publish) |
|
|
58
69
|
|
|
59
70
|
`DSC-14` has no `C-14` in the clause text; it is the ENTRY run-set gate
|
|
60
71
|
(`ENT-14` in 31-DSH-ENTRY). Treat it as "did the thing actually start".
|
|
61
72
|
|
|
73
|
+
`DSC-15`–`DSC-22` shipped in `dsh-standards` 1.6.0; 1.5.x and earlier treat
|
|
74
|
+
`--scope security` as an unknown scope and exit `3`. Only `--scope security`
|
|
75
|
+
makes `15`/`16`/`17` blocking; `21` is mandatory but WARN-only and never blocks
|
|
76
|
+
(listing included). In every other scope the eight stay out of `checks[]`; their
|
|
77
|
+
FAILs are capped to WARN in the separate `security` block and never change the
|
|
78
|
+
DS-C verdict. They are static evidence: three can fail mechanically, one only
|
|
79
|
+
warns, four only return clues, and no PASS proves a plugin safe. The
|
|
80
|
+
author/user/market checklists are in [security.md](security.md).
|
|
81
|
+
|
|
62
82
|
<a id="warn"></a>
|
|
63
83
|
## Two things that look like passes but are not
|
|
64
84
|
|
|
@@ -89,6 +109,11 @@ them:
|
|
|
89
109
|
- whether a `red-team` label was actually used for red-team work (`C-13.5`
|
|
90
110
|
only asks for `dsh.usageScope`)
|
|
91
111
|
- browser rendering of a client bundle (`G4` is a static shape check)
|
|
112
|
+
- runtime network egress, file reads, or prompt corruption — `DSC-18`/`DSC-20`
|
|
113
|
+
are static clues; observing the behavior needs a sandbox with a network policy
|
|
114
|
+
and capture (see [security.md](security.md#scope))
|
|
115
|
+
- whether a dependency's own install script ran, or whether a package is
|
|
116
|
+
malicious. `DSC-15`/`DSC-21` cover known shapes, not intent.
|
|
92
117
|
|
|
93
118
|
The checker's default `--runtime-version` is `0.2.2-alpha.2`. Pass
|
|
94
119
|
`--runtime-version <the host you target>` when that differs, or `DSC-02` will
|
package/docs/security.md
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Security: what a plugin can do, and what the checks cannot prove
|
|
2
|
+
|
|
3
|
+
> **Read this first.** A DSH plugin runs in the host's process, as the same
|
|
4
|
+
> user, with the host's full permissions. The host does not sandbox a loaded
|
|
5
|
+
> plugin: the sandbox only restricts file writes made by tool subprocesses, and
|
|
6
|
+
> network/process visibility is outside its vocabulary. `dsh.capabilities` is a
|
|
7
|
+
> declaration the host does not read or enforce. So a plugin can open and write
|
|
8
|
+
> files, spawn processes, reach the network, and read the credential store
|
|
9
|
+
> whether or not it declares that it does.
|
|
10
|
+
|
|
11
|
+
`DSC-15`–`DSC-22` are the **static security checks** shipped in
|
|
12
|
+
`dsh-standards` **1.6.0**; `--scope security` is a released flag
|
|
13
|
+
from 1.6.0 on (1.5.x and earlier treat `security` as an unknown scope and exit
|
|
14
|
+
`3`; 1.6.1 supports it).
|
|
15
|
+
With `--scope security`, `checks[]` carries all **22** checks (`DSC-01`–`DSC-14`
|
|
16
|
+
plus `DSC-15`–`DSC-22`); the default `ds-c` run still carries 14 and reports the
|
|
17
|
+
eight in the additive `security` block. Three can block (`15`, `16`, `17`), one
|
|
18
|
+
is mandatory but **WARN-only and never blocks** (`21`, listing included), and
|
|
19
|
+
four only produce clues (`18`, `19`, `20`, `22`); all eight reduce risk, and
|
|
20
|
+
**none of them proves a plugin is safe**.
|
|
21
|
+
|
|
22
|
+
The `security` block is a **separate view**, not a fourth verdict. In the default
|
|
23
|
+
scope a security `FAIL` is capped to `WARN` inside `security.checks[]`; it is
|
|
24
|
+
listed only in `security.warn[]` (`security.blocking[]` stays empty), does not
|
|
25
|
+
enter `buckets.blocking` or `conclusion.headline`, and changes neither `status`
|
|
26
|
+
nor the exit code. Only `--scope security` puts `15`/`16`/`17` into `checks[]`,
|
|
27
|
+
where a `FAIL` enters `buckets.blocking`, the headline, `status=fail` and exit
|
|
28
|
+
`1`. `DSC-21` never FAILs, so it never blocks in either profile. Read the scope,
|
|
29
|
+
and read the block's `checks[]`, not just its four arrays — see
|
|
30
|
+
[Reading the `security` block](#security-block).
|
|
31
|
+
|
|
32
|
+
## The trust boundary in one table
|
|
33
|
+
|
|
34
|
+
| Fact | What follows |
|
|
35
|
+
|---|---|
|
|
36
|
+
| plugin and host share a process | `import node:fs`, `fetch`, `child_process`, and `process.env` are always reachable |
|
|
37
|
+
| `dsh.capabilities` is a declaration | declaring `[]` does not remove a capability; over-declaring is a WARN, under-declaring is a static FAIL |
|
|
38
|
+
| the sandbox covers tool subprocess file writes only | it is not a boundary around plugin code, and not a network policy |
|
|
39
|
+
| the checker executes the plugin | `--runtime`/C-4/C-14 import it; the check path has no sandbox and inherits `HOME` |
|
|
40
|
+
| static checks see known source shapes | they cannot see runtime egress, dynamic URLs, or whether a prompt was really corrupted |
|
|
41
|
+
| runtime safety needs containment | a network-policy sandbox plus capture/audit is the only way to observe what code actually does |
|
|
42
|
+
|
|
43
|
+
<a id="author"></a>
|
|
44
|
+
## Author guide
|
|
45
|
+
|
|
46
|
+
<a id="author-least-privilege"></a>
|
|
47
|
+
### 1. Least privilege and the capability declaration (DSC-16, DSC-18)
|
|
48
|
+
|
|
49
|
+
Declare only the closed-set IDs the plugin really touches. Every static trigger
|
|
50
|
+
needs its entry (`C-11.2`), and an entry with no trigger is an over-declaration
|
|
51
|
+
WARN (`C-11.5`). There are 15 IDs: `fs:read`, `fs:write`, `shell`, `subprocess`,
|
|
52
|
+
`network:egress`, `network:ingress`, `credentials:read`, `credentials:list`,
|
|
53
|
+
`credentials:provider`, `llm:adapter`, `sessions:read`, `system-prompt:write`,
|
|
54
|
+
`tools:register`, `remote:expose`, `plugin:install`.
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"dsh": {
|
|
59
|
+
"capabilities": ["tools:register"]
|
|
60
|
+
},
|
|
61
|
+
"security": {
|
|
62
|
+
"status": "draft",
|
|
63
|
+
"note": "non-host disclosure only; not read, not enforced",
|
|
64
|
+
"permissions": {
|
|
65
|
+
"files": [],
|
|
66
|
+
"network": [],
|
|
67
|
+
"credentials": [],
|
|
68
|
+
"shell": false,
|
|
69
|
+
"subprocess": false
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`dsh.permissions` and `security.permissions` are **not host fields**. The
|
|
76
|
+
standard is not fixed, so this guide prints a `security.permissions` block
|
|
77
|
+
marked `draft`: it is disclosure for a human or a market, never enforcement.
|
|
78
|
+
If you do not want to make the disclosure claim, delete the block — an absent
|
|
79
|
+
field means "not declared", not "no permissions". `dsh.commitments` and
|
|
80
|
+
`dsh.disclosure` are the same kind of optional disclosure: `false` means "no
|
|
81
|
+
promise", absent means "not declared". Do not fill every commitment `true`; an
|
|
82
|
+
unsupported claim is worse than a missing one.
|
|
83
|
+
|
|
84
|
+
<a id="author-credentials"></a>
|
|
85
|
+
### 2. Credentials and sensitive files (DSC-17)
|
|
86
|
+
|
|
87
|
+
Resolve a reference per operation, through the seam, and never print, log,
|
|
88
|
+
persist, or put a value in `argv`. `describe()` is the only shape a UI should
|
|
89
|
+
see. Do not read `$DSH_HOME/.credentials.yaml`, `~/.ssh`, `~/.aws`, `~/.netrc`,
|
|
90
|
+
`.npmrc`, or any other backing file, and do not enumerate unrelated records.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
94
|
+
|
|
95
|
+
const ref = credentialRef('MY_API_KEY')
|
|
96
|
+
const resolved = await ctx.credentials.resolve(ref) // re-resolve per operation
|
|
97
|
+
if (!resolved) throw new Error('MY_API_KEY is not configured') // never print resolved.value
|
|
98
|
+
|
|
99
|
+
// configuration UI: presence and writability only
|
|
100
|
+
const info = await ctx.credentials.describe(ref)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Environment scrubbing applies to `ctx.subprocess`: the child gets a scrubbed
|
|
104
|
+
parent environment, and an explicit `env` entry is a deliberate opt-in. Do not
|
|
105
|
+
forward a token "just in case".
|
|
106
|
+
|
|
107
|
+
<a id="author-egress"></a>
|
|
108
|
+
### 3. Egress endpoints (DSC-18)
|
|
109
|
+
|
|
110
|
+
Use a constant allowlist of exact hostnames and `https` only. Never fetch a URL
|
|
111
|
+
that came from a model, a tool result, or a user message; use
|
|
112
|
+
`redirect: 'manual'` and refuse a redirect unless every `Location` hop passes the
|
|
113
|
+
same allowlist (bounded), and never send a credential across an origin. Disclose
|
|
114
|
+
third-party endpoints, what is retained, and whether session text is forwarded.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
const ALLOW = new Set(['api.example.com'])
|
|
118
|
+
|
|
119
|
+
export function checkedUrl(raw: string): URL {
|
|
120
|
+
const url = new URL(raw)
|
|
121
|
+
if (url.protocol !== 'https:' || !ALLOW.has(url.hostname)) {
|
|
122
|
+
throw new Error('blocked endpoint')
|
|
123
|
+
}
|
|
124
|
+
return url
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const res = await fetch(checkedUrl('https://api.example.com/v1/x'), {
|
|
128
|
+
redirect: 'manual',
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The host has no egress policy and `web_fetch` can reach private addresses. This
|
|
133
|
+
allowlist is the plugin's own discipline, not a sandbox. If you must call a
|
|
134
|
+
third party, say so in `dsh.disclosure.endpoints` (when that field exists) and
|
|
135
|
+
in the README.
|
|
136
|
+
|
|
137
|
+
<a id="author-files"></a>
|
|
138
|
+
### 4. Files, subprocesses, and dynamic execution (DSC-15, DSC-16, DSC-17, DSC-19)
|
|
139
|
+
|
|
140
|
+
- Use `ctx.fs`: `resolve()` the target, then `contains(workspace, target)`.
|
|
141
|
+
Reject `..`, absolute paths, and symlinks (check with `lstat` first), bound the
|
|
142
|
+
size, and keep temp files at `0700`/`0600` (ACL on Windows).
|
|
143
|
+
- Do not start a subprocess by default. When you must, use
|
|
144
|
+
`ctx.subprocess.spawn` with an `argv` array, an absolute executable, `cwd`
|
|
145
|
+
inside the workspace, the scrubbed environment, a timeout/abort, and bounded
|
|
146
|
+
output. Never build a shell command string.
|
|
147
|
+
- Never use `eval`, `new Function`, `vm.runIn*`, `module._compile`, or
|
|
148
|
+
`process.binding`. Dynamic `import()` takes a static path only. Do not publish
|
|
149
|
+
minified blobs or binary-only code; if you ship wasm/native, ship the source,
|
|
150
|
+
the build method, and a `sha256`.
|
|
151
|
+
- Lifecycle scripts are the install-time RCE surface (`DSC-15`): ship none. If a
|
|
152
|
+
build truly needs one, never pipe a download into a shell, and never hide it
|
|
153
|
+
behind `base64 -d` or `node -e`.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const workspace = await ctx.fs.resolve(process.cwd())
|
|
157
|
+
const target = await ctx.fs.resolve(userPath)
|
|
158
|
+
if (!ctx.fs.contains(workspace, target)) throw new Error('path escapes the workspace')
|
|
159
|
+
|
|
160
|
+
const child = ctx.subprocess.spawn({
|
|
161
|
+
argv: ['/usr/bin/git', 'status'],
|
|
162
|
+
cwd: process.cwd(),
|
|
163
|
+
stdio: { stdin: 'ignore', stdout: { maxBytes: 64_000 }, stderr: { maxBytes: 64_000 } },
|
|
164
|
+
graceMs: 5_000,
|
|
165
|
+
signal,
|
|
166
|
+
})
|
|
167
|
+
const outcome = await child.done
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The sandbox still only covers subprocess file writes; a plugin's own `fetch` and
|
|
171
|
+
`node:fs` bypass every seam. Do not read a declaration as isolation.
|
|
172
|
+
|
|
173
|
+
<a id="author-prompt"></a>
|
|
174
|
+
### 5. Tool output and prompt injection (DSC-20)
|
|
175
|
+
|
|
176
|
+
Tool results, web pages, files, and session text are **untrusted data**. Validate
|
|
177
|
+
them against the tool's `output.schema` (a mismatch is `INVALID_TOOL_OUTPUT`),
|
|
178
|
+
and wrap external text with a fixed untrusted prefix, a per-call random boundary,
|
|
179
|
+
and JSON escaping before it reaches a prompt. Never put external text in the
|
|
180
|
+
system prompt or turn it into instructions. Instruction hierarchy belongs in
|
|
181
|
+
code: system > user > external data. Model output stays data; validate it, do
|
|
182
|
+
not shell it, and do not let it choose an arbitrary URL.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { randomUUID } from 'node:crypto'
|
|
186
|
+
|
|
187
|
+
const wrap = (s: string) => {
|
|
188
|
+
const id = randomUUID() // per call, so reused wrappers get a fresh boundary
|
|
189
|
+
return `<untrusted-${id}>\n${JSON.stringify(s).replaceAll('<', '\\u003c')}\n</untrusted-${id}>`
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Rendering is part of the same boundary: use `textContent`/React escaping, never
|
|
194
|
+
`innerHTML`, and escape log and error text too. Wrapping lowers the success rate
|
|
195
|
+
of injection; it is not isolation.
|
|
196
|
+
|
|
197
|
+
<a id="author-publish"></a>
|
|
198
|
+
### 6. Dependencies, build, and publish (DSC-15, DSC-21, DSC-22)
|
|
199
|
+
|
|
200
|
+
Commit the lockfile and install with `npm ci --ignore-scripts` (or
|
|
201
|
+
`--frozen-lockfile`). Keep dependencies minimal and check for typosquats —
|
|
202
|
+
lookalike scopes and edit-distance names around `dsh`/`@deepseek-ai/dsh`. Use a
|
|
203
|
+
`files` allowlist, publish the build output, pin Node and the package manager,
|
|
204
|
+
and make a tag rebuild reproducibly.
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
npm ci --ignore-scripts && npm test && npm pack --dry-run
|
|
208
|
+
npm audit --omit=dev && npm audit signatures
|
|
209
|
+
# CI only: trusted publishing, provenance, no long-lived token
|
|
210
|
+
npm publish --access public --provenance
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Publish with npm trusted publishing (OIDC) and `--provenance`; keep 2FA on,
|
|
214
|
+
protect tags, require review, and never reuse a version (deprecate a bad one).
|
|
215
|
+
Check that `package.json` has no `preinstall`/`install`/`postinstall`/`prepare`:
|
|
216
|
+
|
|
217
|
+
```sh
|
|
218
|
+
node -e "const s=require('./package.json').scripts||{}; if(s.preinstall||s.install||s.postinstall||s.prepare) process.exit(1)"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`dist.integrity` only proves the tarball matches the registry, and an
|
|
222
|
+
attestation needs signature verification plus a bound build source. Neither is
|
|
223
|
+
a code-safety proof.
|
|
224
|
+
|
|
225
|
+
<a id="author-response"></a>
|
|
226
|
+
### 7. Vulnerability response (process, not a DSC)
|
|
227
|
+
|
|
228
|
+
Ship a `SECURITY.md` with a private channel (GitHub Security Advisories), the
|
|
229
|
+
supported versions, a 7-day first response, and 90-day coordinated disclosure.
|
|
230
|
+
Do not take vulnerability reports in public issues. After a fix, publish a
|
|
231
|
+
patch release, an advisory, and credit; revoke and rotate leaked tokens; for a
|
|
232
|
+
malicious version, deprecate it, ask the market to delist, and keep the
|
|
233
|
+
evidence.
|
|
234
|
+
|
|
235
|
+
```md
|
|
236
|
+
# Security Policy
|
|
237
|
+
|
|
238
|
+
## Supported versions
|
|
239
|
+
| Version | Supported |
|
|
240
|
+
|---|---|
|
|
241
|
+
| 0.1.x | yes |
|
|
242
|
+
|
|
243
|
+
## Reporting
|
|
244
|
+
Use <repo>/security/advisories/new; do not open a public issue.
|
|
245
|
+
Include the version, DSH version, impact, and a minimal repro. First response within 7 days.
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
<a id="user"></a>
|
|
249
|
+
## User guide
|
|
250
|
+
|
|
251
|
+
The checks tell you what a package declares; they do not tell you what it does.
|
|
252
|
+
Assume a loaded plugin can do anything the user can. The static checks that
|
|
253
|
+
touch this path are `DSC-15` (lifecycle scripts), `DSC-17` (credential files),
|
|
254
|
+
`DSC-21` (typosquat/lookalike names, WARN-only) and `DSC-22`
|
|
255
|
+
(integrity/provenance) before install; `DSC-18` only ever produces a static clue
|
|
256
|
+
about egress. Under `--scope security`, `DSC-15`/`DSC-16`/`DSC-17` can block;
|
|
257
|
+
outside it every security finding is capped to WARN in the separate `security`
|
|
258
|
+
block.
|
|
259
|
+
|
|
260
|
+
**Before installing.** Read the manifest: `dsh.capabilities`, `dsh.labels`,
|
|
261
|
+
`scripts`, `files`, `engines.dsh`, and the source pin. Then look at the source
|
|
262
|
+
and the publisher: an exact npm version with `dist.integrity`, or a git
|
|
263
|
+
40-hex commit; how recent the release is; whether `dist.signatures` /
|
|
264
|
+
`dist.attestations` exist; and whether an install script is present. An empty
|
|
265
|
+
capabilities list means **not declared**, not "no capabilities". A label such as
|
|
266
|
+
`prompt-modification`, `third-party-model-provider`, `remote-access`,
|
|
267
|
+
`red-team`, or `mirror` is the author telling you which risk to check, not a
|
|
268
|
+
clearance.
|
|
269
|
+
|
|
270
|
+
```sh
|
|
271
|
+
npm view pkg@1.2.3 version dsh scripts dist.integrity dist.signatures dist.attestations repository.url time.modified --json
|
|
272
|
+
|
|
273
|
+
# the released static security scope (1.6.0+; does not run the plugin)
|
|
274
|
+
npx dsh-standards check ./pkg --scope security --format json
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**During install.** Start from a throwaway profile (`dsh plugin --profile lab
|
|
278
|
+
...` initializes it), add `--ignore-scripts`, and understand that it stops
|
|
279
|
+
install scripts only, not the code the host loads. The GUI build approval runs
|
|
280
|
+
a script with your permissions, but the approval list shows only the package
|
|
281
|
+
name, not the script body: inspect the referenced script first (`npm pack`, then
|
|
282
|
+
read `scripts/*`). The CLI inherits your whole environment, so launch it from a
|
|
283
|
+
clean shell with no tokens. A new session defaults to `workspace-write` + `ask`;
|
|
284
|
+
do not switch to `danger-full-access` with `never`.
|
|
285
|
+
`DSH_PERMISSION_MODE=read-only` constrains tool subprocesses, not plugins.
|
|
286
|
+
|
|
287
|
+
```sh
|
|
288
|
+
dsh plugin --profile lab add pkg@1.2.3 --ignore-scripts
|
|
289
|
+
dsh --profile lab --dump-config
|
|
290
|
+
dsh plugin --profile lab remove pkg
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**After install.** Return to `workspace-write`, watch the install log and the
|
|
294
|
+
session's tool calls, and remove the package when you no longer need it. Check
|
|
295
|
+
the profile's `package.json`/lockfile and the credential records. If something
|
|
296
|
+
looks wrong, disable it, keep the logs and the package hash, and report through
|
|
297
|
+
its `SECURITY.md` (for a malicious npm package, also npm support). Do not
|
|
298
|
+
publish the details first.
|
|
299
|
+
|
|
300
|
+
<a id="market"></a>
|
|
301
|
+
## Market / repository guide
|
|
302
|
+
|
|
303
|
+
**Record facts before listing.** `name@version`; source (npm integrity or git
|
|
304
|
+
40-hex plus subdir); `dsh.bundle.patch` (no `!!js`), `files`, `engines.dsh`, and
|
|
305
|
+
whether `scripts` contains an install hook; publisher vs `repository`;
|
|
306
|
+
`SECURITY.md`, 2FA, protected tags; publish time; provenance/attestations;
|
|
307
|
+
capability/label validity; license (advisory). Do not install or execute the
|
|
308
|
+
package to fill the row, and do not grade, score, or rank it. These facts map
|
|
309
|
+
to `DSC-15`–`DSC-22`: a row records them, it does not certify them.
|
|
310
|
+
|
|
311
|
+
```sh
|
|
312
|
+
npm view pkg@1.2.3 --json
|
|
313
|
+
mkdir -p inspect && npm pack pkg@1.2.3 --ignore-scripts --pack-destination ./inspect && tar -tzf inspect/*.tgz
|
|
314
|
+
git clone --filter=blob:none --no-checkout <url> r && git -C r archive <40hex> | tar -t
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Show risk, not a badge.** Put capabilities in the same view as labels and mark
|
|
318
|
+
them "author declaration, not enforced"; mark unknowns "not declared / not
|
|
319
|
+
checked / not run". The index's only risk field is labels; capabilities must be
|
|
320
|
+
read from the manifest. Missing data is never a pass. State that static checks
|
|
321
|
+
cover known patterns and are not a security audit. The checker can import the
|
|
322
|
+
plugin, so do not run it against an untrusted package on a host you care about.
|
|
323
|
+
|
|
324
|
+
**Disputes and delisting.** Hard-reject only four cases: not a plugin /
|
|
325
|
+
unparseable, source not pinnable, impersonation or infringement, and a malicious
|
|
326
|
+
security hold (manual review with an appeal). List everything else with the
|
|
327
|
+
finding attached. The author can withdraw at any time; a 90-day expiry removes
|
|
328
|
+
only the label; a 14-day appeal window; keep history for at least two years and
|
|
329
|
+
write the reason to `market-actions.jsonl`.
|
|
330
|
+
|
|
331
|
+
**Wording ban.** Never write "secure", "certified", "official", "audited", or
|
|
332
|
+
"100% risk-free", and never imply certification with a shield or a checkmark.
|
|
333
|
+
Use the fixed disclaimer: "unofficial · not certified · not security-audited ·
|
|
334
|
+
not endorsed · no warranty".
|
|
335
|
+
|
|
336
|
+
<a id="template"></a>
|
|
337
|
+
## Template: minimal low-risk defaults
|
|
338
|
+
|
|
339
|
+
The smallest honest starting point is: no lifecycle scripts, empty
|
|
340
|
+
capabilities, a permissions disclosure marked draft, and a `SECURITY.md` in the
|
|
341
|
+
tarball. The `dsh-scaffold` template ships these defaults.
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"name": "@acme/dsh-hello",
|
|
346
|
+
"version": "0.1.0",
|
|
347
|
+
"license": "Apache-2.0",
|
|
348
|
+
"files": ["lib", "cordis.patch.yml", "SECURITY.md", "LICENSE"],
|
|
349
|
+
"scripts": { "test": "vitest run" },
|
|
350
|
+
"dsh": {
|
|
351
|
+
"capabilities": [],
|
|
352
|
+
"labels": []
|
|
353
|
+
},
|
|
354
|
+
"security": {
|
|
355
|
+
"status": "draft",
|
|
356
|
+
"note": "non-host disclosure only; not read, not enforced",
|
|
357
|
+
"permissions": { "files": [], "network": [], "credentials": [], "shell": false, "subprocess": false }
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
`SECURITY.md` must name the private channel, the supported versions, and the
|
|
363
|
+
response window — see the author section above. Verify the shape before publish:
|
|
364
|
+
|
|
365
|
+
```sh
|
|
366
|
+
node -e "const s=require('./package.json').scripts||{}; if(s.preinstall||s.install||s.postinstall||s.prepare) process.exit(1)"
|
|
367
|
+
npm pack --dry-run # SECURITY.md must be in the file list
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
This shape is the starting point for `DSC-15` (no lifecycle scripts), `DSC-21`
|
|
371
|
+
(no typosquat dependency), and `DSC-22` (provenance publishing); it also gives
|
|
372
|
+
`DSC-17` and `DSC-18` a truthful empty disclosure to read. An empty
|
|
373
|
+
`capabilities` array is correct only while the plugin touches no host surface.
|
|
374
|
+
Once it registers a tool or reads `ctx.fs`, add the ID; a copied template with a
|
|
375
|
+
stale empty array hides the very `C-11.2` failure the check is for.
|
|
376
|
+
|
|
377
|
+
<a id="scope"></a>
|
|
378
|
+
## Where the security checks sit
|
|
379
|
+
|
|
380
|
+
| Scope | What runs | Verdict |
|
|
381
|
+
|---|---|---|
|
|
382
|
+
| `--scope ds-c` (default) / `entry` / `flow` / `boost` | `DSC-01`–`DSC-14`: only the scope's mandatory subset counts (`DSC-10` is advisory; `03`/`14` can be NA/not-run). `DSC-15`–`DSC-22` run in the separate `security` block (`scope=advisory`) | a security `FAIL` is capped to `WARN` in that block; it is not in `checks[]`/`buckets.blocking` and cannot change `status`/exit |
|
|
383
|
+
| `--scope security` (1.6.0+) | all 22 checks in `checks[]`: DSC-01–14 plus DSC-15–22 | `15`/`16`/`17` blocking (FAIL → exit 1); `21` mandatory **WARN-only**, never blocking (listing too); `18` WARN; `19`/`20`/`22` advisory |
|
|
384
|
+
| runtime observation | sandbox with a network policy + capture | the only evidence that code actually egressed, read a file, or rewrote a prompt |
|
|
385
|
+
|
|
386
|
+
`DSC-16` blocks on `eval`/`new Function`/`vm`/`module._compile`/`process.binding`.
|
|
387
|
+
A bare `child_process` import is WARN in 1.6.1; the FAIL for a statically
|
|
388
|
+
dangerous subprocess command is scoped to 1.6.2.
|
|
389
|
+
|
|
390
|
+
The JSON report carries an additive top-level `security` block; the DS-C verdicts
|
|
391
|
+
stay unchanged. A PASS in that block means "no blocking FAIL, no WARN, and the
|
|
392
|
+
mandatory `15`/`16`/`17`/`21` all produced a result", not "safe".
|
|
393
|
+
|
|
394
|
+
<a id="security-block"></a>
|
|
395
|
+
### Reading the `security` block
|
|
396
|
+
|
|
397
|
+
- `conclusion.headline`'s `blocking N` and `buckets.blocking[]` are
|
|
398
|
+
**report-level**: they only count `checks[]` FAILs. `security.blocking[]` is
|
|
399
|
+
the block's own list and can only ever hold `DSC-15`/`DSC-16`/`DSC-17`.
|
|
400
|
+
- **Default scope:** the eight run in `security.checks[]` with
|
|
401
|
+
`security.scope="advisory"`. A `FAIL` is capped to `WARN` (listed in
|
|
402
|
+
`security.warn[]`), `security.blocking=[]`, no security row appears in
|
|
403
|
+
`buckets.blocking`, the headline is unchanged, and `status`/exit stay the same.
|
|
404
|
+
- **`--scope security`:** the eight join `checks[]`. A `15`/`16`/`17` FAIL →
|
|
405
|
+
`security.blocking=[...]` plus `buckets.blocking=[...]`, headline `blocking N`,
|
|
406
|
+
`status=fail`, exit `1`. `DSC-21` findings are WARN-only and never block;
|
|
407
|
+
`18`/`19`/`20`/`22` are advisory. A mandatory check with no result makes
|
|
408
|
+
`security.status=incomplete`.
|
|
409
|
+
- **Partial:** 1.6.1 has no `security.partial[]`. A partly-run check is
|
|
410
|
+
`security.checks[].coverage === "partial"` (and top-level `coverage.partial` /
|
|
411
|
+
`buckets.partial`); `security.notRun[]` only carries `NA`/`ERROR`/full
|
|
412
|
+
`not-run`. Do not read the four arrays as complete coverage.
|
|
413
|
+
|
|
414
|
+
<a id="limits"></a>
|
|
415
|
+
## What no static check can tell you
|
|
416
|
+
|
|
417
|
+
- whether a URL was really fetched, or where a redirect went;
|
|
418
|
+
- whether a file outside the package was read or written;
|
|
419
|
+
- whether a dependency's own install script ran;
|
|
420
|
+
- whether a model was actually steered by injected text;
|
|
421
|
+
- whether the author is trustworthy — signatures are self-asserted evidence
|
|
422
|
+
until the key chain and build provenance are verified by a human.
|
|
423
|
+
|
|
424
|
+
Runtime security needs a sandbox with an egress policy and an audit trail.
|
|
425
|
+
Windows has no `unshare`, so the containment story is weaker there, and a
|
|
426
|
+
sandbox that only limits writes is not a network boundary. Until you have that
|
|
427
|
+
evidence, write `not-run` or `not-measured` — never "secure".
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-guide",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "A practical guide to DSH plugin quality: ENTRY checks, FLOW checks, and BOOST guidance. Community guide; not official, not a certification, not a ranking.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|