dsh-plugin-guide 0.5.0 → 0.5.2

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 CHANGED
@@ -58,5 +58,7 @@ 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
59
 
60
60
  For plugin security — capabilities, credentials, egress, install scripts, and
61
- supply chain — read `docs/security.md`. Its checks are static and advisory,
62
- and runtime security needs a sandbox, not a checker.
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
@@ -95,12 +95,13 @@ pitfalls”), record it as a WARN or a note. Do not invent a number.
95
95
 
96
96
  BOOST measures Cordis practice, not security. A reversible effect or a seam is
97
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 proposed
99
- `DSC-15`–`DSC-22` checks are static and advisory outside `--scope security`
100
- (which `dsh-standards` 1.4.x does not ship yet); they never change a BOOST
101
- result and none of them proves a plugin safe. Runtime questions — actual egress,
102
- file reads, prompt corruption — require a sandbox with a network policy and
103
- capture, not an evidence card. Keep the security claim `not-run` instead of
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
104
105
  borrowing BOOST evidence for it. See [security.md](security.md#scope).
105
106
 
106
107
  ## Run the audit
package/docs/entry.md CHANGED
@@ -311,27 +311,31 @@ export function apply(ctx) {
311
311
  ## Security checks (DSC-15 to DSC-22)
312
312
 
313
313
  ENTRY is a compatibility gate; it does not answer "is this safe to install". The
314
- 2026-11 audit proposes eight security checks as a separate scope:
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`).
315
317
 
316
318
  | Check | What it looks for | Verdict in `--scope security` |
317
319
  |---|---|---|
318
- | `DSC-15` | `preinstall`/`install`/`postinstall`/`prepare` that download, decode, or execute | blocking |
319
- | `DSC-16` | `eval`, `new Function`, `vm`, `module._compile`, `process.binding` | blocking |
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 |
320
322
  | `DSC-17` | reads of `~/.ssh`, `~/.aws`, `.npmrc`, `.netrc`, or credential backing files | blocking |
321
323
  | `DSC-18` | non-https, userinfo, bare-IP, webhook/paste/tunnel egress targets | WARN (clue) |
322
324
  | `DSC-19` | minified blobs, large base64 blocks, wasm/native without source | advisory |
323
- | `DSC-20` | tool/web text flowing into the system prompt without a boundary | advisory (runtime, v2) |
324
- | `DSC-21` | typosquat names, fake scopes, unpinned or script-bearing dependencies | blocking in listing |
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) |
325
327
  | `DSC-22` | `dist.integrity`, signatures, provenance attestations | advisory |
326
328
 
327
- These checks are **not released** in `dsh-standards` 1.4.x, and `--scope
328
- security` is a proposed flag; if your checker exits `3` for an unknown scope,
329
- that is expected. Outside that scope they do not run, and a security `FAIL` is
330
- capped at WARN, so it cannot change an ENTRY verdict. Four are mechanical and
331
- four only return clues — none of them proves a plugin is safe.
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.
332
336
 
333
337
  ```sh
334
- # proposed; not available in 1.4.x
338
+ # 1.6.0+; exits 3 only on 1.5.x and earlier
335
339
  npx dsh-standards ./my-plugin --scope security --format json
336
340
  ```
337
341
 
@@ -343,6 +347,6 @@ and neither is a `DSC-11` PASS.
343
347
  ## What ENTRY does not do
344
348
 
345
349
  It is not a security audit, sandbox, full contract audit, market gate, score, or
346
- ranking. Those are separate concerns; do not borrow their authority. The
347
- proposed `DSC-15`–`DSC-22` are a static security scope, covered in
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
348
352
  [security.md](security.md), not part of the ENTRY verdict.
package/docs/flow.md CHANGED
@@ -205,10 +205,13 @@ example `J2.toggle → 32-DSH-FLOW #R2`).
205
205
  ## Security boundary
206
206
 
207
207
  J5 checks that permissions are declared consistently; it is static preflight,
208
- not a security test. The proposed `DSC-15`–`DSC-22` live behind a separate
209
- `--scope security` that `dsh-standards` 1.4.x does not ship yet, and they never
210
- turn a FLOW journey into a security pass. Outside that scope a security finding
211
- is advisory: it is capped at WARN and cannot change the FLOW verdict.
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.
212
215
 
213
216
  Static checks also cannot observe what a plugin does at runtime — which URLs it
214
217
  reaches, which files it reads, or whether a prompt was corrupted. Those answers
package/docs/levels.md CHANGED
@@ -76,10 +76,12 @@ The card fields are in
76
76
 
77
77
  ## Security is not a fourth layer
78
78
 
79
- Security checks are a **scope**, not a level, and they are proposed, not
80
- released: `DSC-15`–`DSC-22` sit behind a `--scope security` that
81
- `dsh-standards` 1.4.x does not have yet. They never change an ENTRY, FLOW, or
82
- BOOST verdict, and outside that scope a security `FAIL` is capped at WARN.
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.
83
85
 
84
86
  - Static checks only match known source shapes; they cannot see runtime egress,
85
87
  file reads, or prompt corruption. Runtime security needs a sandbox with a
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` (currently `v0.4.4`); if it points
26
- at an older page, this table is the current mapping.
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,7 +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` |
38
- | `--scope security` (proposed, not in 1.4.x) | + 15, 16, 17, 21; 18/19/20/22 advisory; 21 listing only | security scope only; DS-C 01–14 are unchanged and the report gains an additive `security` block |
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 |
39
41
 
40
42
  <a id="table"></a>
41
43
  ## The check table
@@ -56,24 +58,26 @@ at an older page, this table is the current mapping.
56
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) |
57
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) |
58
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) |
59
- | **DSC-15** | C-15 lifecycle scripts (proposed) | **Blocking only under `--scope security`**; not run in the default scopes. | 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) |
60
- | **DSC-16** | C-16 dynamic execution (proposed) | **Blocking only under `--scope security`**. | 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) |
61
- | **DSC-17** | C-17 sensitive files (proposed) | **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) |
62
- | **DSC-18** | C-18 egress endpoints (proposed) | **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) |
63
- | **DSC-19** | C-19 obfuscation/binary (proposed) | **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) |
64
- | **DSC-20** | C-20 prompt injection (proposed) | **Advisory** (FLOW runtime, 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) |
65
- | **DSC-21** | C-21 supply chain (proposed) | **Blocking under `--scope security` in the listing profile**; non-blocking outside a listing. | 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) |
66
- | **DSC-22** | C-22 provenance/integrity (proposed) | **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) |
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) |
67
69
 
68
70
  `DSC-14` has no `C-14` in the clause text; it is the ENTRY run-set gate
69
71
  (`ENT-14` in 31-DSH-ENTRY). Treat it as "did the thing actually start".
70
72
 
71
- `DSC-15`–`DSC-22` are **proposed** by the 2026-11 audit and are not in
72
- `dsh-standards` 1.4.x. Only `--scope security` makes `15`/`16`/`17` (and `21`
73
- in a listing) blocking; in every other scope they are absent or capped at WARN,
74
- and none of them changes the existing DS-C verdict. They are static evidence:
75
- four can fail mechanically, four only return clues, and no PASS proves a plugin
76
- safe. The author/user/market checklists are in [security.md](security.md).
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).
77
81
 
78
82
  <a id="warn"></a>
79
83
  ## Two things that look like passes but are not
package/docs/security.md CHANGED
@@ -8,17 +8,26 @@
8
8
  > files, spawn processes, reach the network, and read the credential store
9
9
  > whether or not it declares that it does.
10
10
 
11
- `DSC-15`–`DSC-22` are **proposed** security checks from the 2026-11 audit. They
12
- are not in `dsh-standards` 1.4.x, and `--scope security` is a proposed scope, not
13
- a released flag. When they ship, four are mechanical (`15`, `16`, `17`, `21`)
14
- and four only produce clues (`18`, `19`, `20`, `22`); all eight reduce risk, and
15
- **none of them proves a plugin is safe**. Until then, this page is a checklist,
16
- not a gate.
17
-
18
- The same boundary applies to the existing scopes: outside `--scope security`,
19
- a security `FAIL` can land in `buckets.blocking` while `status` and the exit
20
- code stay `0`, because only the mandatory set decides the verdict. Read the
21
- scope, not the bucket.
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**, a scope, not a fourth layer. 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).
22
31
 
23
32
  ## The trust boundary in one table
24
33
 
@@ -99,9 +108,10 @@ forward a token "just in case".
99
108
  ### 3. Egress endpoints (DSC-18)
100
109
 
101
110
  Use a constant allowlist of exact hostnames and `https` only. Never fetch a URL
102
- that came from a model, a tool result, or a user message; redirects are checked
103
- hop by hop, and a credential is never sent across an origin. Disclose third-party
104
- endpoints, what is retained, and whether session text is forwarded.
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.
105
115
 
106
116
  ```ts
107
117
  const ALLOW = new Set(['api.example.com'])
@@ -174,9 +184,10 @@ not shell it, and do not let it choose an arbitrary URL.
174
184
  ```ts
175
185
  import { randomUUID } from 'node:crypto'
176
186
 
177
- const id = randomUUID()
178
- const wrap = (s: string) =>
179
- `<untrusted-${id}>\n${JSON.stringify(s).replaceAll('<', '\\u003c')}\n</untrusted-${id}>`
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
+ }
180
191
  ```
181
192
 
182
193
  Rendering is part of the same boundary: use `textContent`/React escaping, never
@@ -238,10 +249,13 @@ Include the version, DSH version, impact, and a minimal repro. First response wi
238
249
  ## User guide
239
250
 
240
251
  The checks tell you what a package declares; they do not tell you what it does.
241
- Assume a loaded plugin can do anything the user can. The proposed checks that
242
- touch this path are `DSC-15` (lifecycle scripts), `DSC-21` (source pinning,
243
- typosquat), and `DSC-22` (integrity/provenance) before install; `DSC-17`/`DSC-18`
244
- only ever produce static clues about files and egress.
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.
245
259
 
246
260
  **Before installing.** Read the manifest: `dsh.capabilities`, `dsh.labels`,
247
261
  `scripts`, `files`, `engines.dsh`, and the source pin. Then look at the source
@@ -255,16 +269,20 @@ clearance.
255
269
 
256
270
  ```sh
257
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 --json
258
275
  ```
259
276
 
260
277
  **During install.** Start from a throwaway profile (`dsh plugin --profile lab
261
278
  ...` initializes it), add `--ignore-scripts`, and understand that it stops
262
279
  install scripts only, not the code the host loads. The GUI build approval runs
263
- a script with your permissions: read the script before approving. The CLI
264
- inherits your whole environment, so launch it from a clean shell with no
265
- tokens. A new session defaults to `workspace-write` + `ask`; do not switch to
266
- `danger-full-access` with `never`. `DSH_PERMISSION_MODE=read-only` constrains
267
- tool subprocesses, not plugins.
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.
268
286
 
269
287
  ```sh
270
288
  dsh plugin --profile lab add pkg@1.2.3 --ignore-scripts
@@ -303,12 +321,21 @@ read from the manifest. Missing data is never a pass. State that static checks
303
321
  cover known patterns and are not a security audit. The checker can import the
304
322
  plugin, so do not run it against an untrusted package on a host you care about.
305
323
 
306
- **Disputes and delisting.** Hard-reject only four cases: not a plugin /
307
- unparseable, source not pinnable, impersonation or infringement, and a malicious
308
- security hold (manual review with an appeal). List everything else with the
309
- finding attached. The author can withdraw at any time; a 90-day expiry removes
310
- only the label; a 14-day appeal window; keep history for at least two years and
311
- write the reason to `market-actions.jsonl`.
324
+ **Disputes and delisting.** Hard-reject only four cases, each with its reason
325
+ code: `not-a-plugin`, `source-not-pinnable`, `impersonation-or-infringement`,
326
+ and `security-hold` (manual review with an appeal). List everything else with
327
+ the finding attached. Market actions are not label fields: every listing and
328
+ rejection is one row of `market-actions.jsonl`
329
+ (`ts/subject/action/reasonCode/evidenceUrls/actor/signature/appealUrl`); a
330
+ rejection carries one of the four reason codes above, and the authoritative
331
+ fields, acting roles, and appeal link are in
332
+ `00-live/market-contract/DECISION-TABLE.md` §2 and
333
+ `00-live/market-contract/CONTRACT.md` §7. The author can withdraw at any time
334
+ (`lifecycle` becomes `withdrawn` at `withdrawnAt`); a 90-day expiry counts from
335
+ the record's `checkedAt` (rev1 records: from `generatedAt` to `expiresAt`) and
336
+ removes only the label; a project-initiated withdrawal must carry a reason and
337
+ evidence and opens a 14-day appeal window counted from `withdrawnAt` (shown as
338
+ `under-review` while open). Keep record history for at least two years.
312
339
 
313
340
  **Wording ban.** Never write "secure", "certified", "official", "audited", or
314
341
  "100% risk-free", and never imply certification with a shield or a checkmark.
@@ -316,7 +343,7 @@ Use the fixed disclaimer: "unofficial · not certified · not security-audited
316
343
  not endorsed · no warranty".
317
344
 
318
345
  <a id="template"></a>
319
- ## Template: minimal secure defaults
346
+ ## Template: minimal low-risk defaults
320
347
 
321
348
  The smallest honest starting point is: no lifecycle scripts, empty
322
349
  capabilities, a permissions disclosure marked draft, and a `SECURITY.md` in the
@@ -361,13 +388,37 @@ stale empty array hides the very `C-11.2` failure the check is for.
361
388
 
362
389
  | Scope | What runs | Verdict |
363
390
  |---|---|---|
364
- | `--scope ds-c` (default) / `entry` / `flow` / `boost` | DSC-01–14 mandatory; DSC-15–22 non-mandatory if implemented | a security `FAIL` is recorded in `blocking` but cannot change `status`/exit |
365
- | `--scope security` (proposed, not released) | + DSC-15–22 | `15`/`16`/`17` blocking; `21` blocking in the listing profile; `18` WARN; `19`/`20`/`22` advisory |
391
+ | `--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 |
392
+ | `--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 |
366
393
  | runtime observation | sandbox with a network policy + capture | the only evidence that code actually egressed, read a file, or rewrote a prompt |
367
394
 
368
- When the scope ships, the JSON report gets an additive top-level `security`
369
- block; the DS-C verdicts stay unchanged. A PASS in that block means "no known
370
- shape matched in this static pass", not "safe".
395
+ `DSC-16` blocks on `eval`/`new Function`/`vm`/`module._compile`/`process.binding`.
396
+ A bare `child_process` import is WARN in 1.6.1; the FAIL for a statically
397
+ dangerous subprocess command is scoped to 1.6.2.
398
+
399
+ The JSON report carries an additive top-level `security` block; the DS-C verdicts
400
+ stay unchanged. A PASS in that block means "no blocking FAIL, no WARN, and the
401
+ mandatory `15`/`16`/`17`/`21` all produced a result", not "safe".
402
+
403
+ <a id="security-block"></a>
404
+ ### Reading the `security` block
405
+
406
+ - `conclusion.headline`'s `blocking N` and `buckets.blocking[]` are
407
+ **report-level**: they only count `checks[]` FAILs. `security.blocking[]` is
408
+ the block's own list and can only ever hold `DSC-15`/`DSC-16`/`DSC-17`.
409
+ - **Default scope:** the eight run in `security.checks[]` with
410
+ `security.scope="advisory"`. A `FAIL` is capped to `WARN` (listed in
411
+ `security.warn[]`), `security.blocking=[]`, no security row appears in
412
+ `buckets.blocking`, the headline is unchanged, and `status`/exit stay the same.
413
+ - **`--scope security`:** the eight join `checks[]`. A `15`/`16`/`17` FAIL →
414
+ `security.blocking=[...]` plus `buckets.blocking=[...]`, headline `blocking N`,
415
+ `status=fail`, exit `1`. `DSC-21` findings are WARN-only and never block;
416
+ `18`/`19`/`20`/`22` are advisory. A mandatory check with no result makes
417
+ `security.status=incomplete`.
418
+ - **Partial:** 1.6.1 has no `security.partial[]`. A partly-run check is
419
+ `security.checks[].coverage === "partial"` (and top-level `coverage.partial` /
420
+ `buckets.partial`); `security.notRun[]` only carries `NA`/`ERROR`/full
421
+ `not-run`. Do not read the four arrays as complete coverage.
371
422
 
372
423
  <a id="limits"></a>
373
424
  ## What no static check can tell you
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-guide",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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",