dsh-standards 1.5.0 → 1.6.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/40-checker/fixtures/security/sec15-clean/index.mjs +3 -0
- package/40-checker/fixtures/security/sec15-clean/package.json +19 -0
- package/40-checker/fixtures/security/sec15-missing-ref/index.mjs +3 -0
- package/40-checker/fixtures/security/sec15-missing-ref/package.json +22 -0
- package/40-checker/fixtures/security/sec15-node/index.mjs +3 -0
- package/40-checker/fixtures/security/sec15-node/package.json +22 -0
- package/40-checker/fixtures/security/sec15-node/scripts/build.mjs +1 -0
- package/40-checker/fixtures/security/sec15-pipe/index.mjs +3 -0
- package/40-checker/fixtures/security/sec15-pipe/package.json +22 -0
- package/40-checker/fixtures/security/sec16-client/index.mjs +3 -0
- package/40-checker/fixtures/security/sec16-client/lib/client.js +3 -0
- package/40-checker/fixtures/security/sec16-client/package.json +21 -0
- package/40-checker/fixtures/security/sec16-declared/index.mjs +3 -0
- package/40-checker/fixtures/security/sec16-declared/package.json +19 -0
- package/40-checker/fixtures/security/sec16-eval/index.mjs +3 -0
- package/40-checker/fixtures/security/sec16-eval/package.json +19 -0
- package/40-checker/fixtures/security/sec16-require/index.mjs +4 -0
- package/40-checker/fixtures/security/sec16-require/package.json +19 -0
- package/40-checker/fixtures/security/sec17-env/index.mjs +5 -0
- package/40-checker/fixtures/security/sec17-env/package.json +19 -0
- package/40-checker/fixtures/security/sec17-seam/index.mjs +3 -0
- package/40-checker/fixtures/security/sec17-seam/package.json +19 -0
- package/40-checker/fixtures/security/sec17-ssh/index.mjs +6 -0
- package/40-checker/fixtures/security/sec17-ssh/package.json +19 -0
- package/40-checker/fixtures/security/sec18-bad/index.mjs +3 -0
- package/40-checker/fixtures/security/sec18-bad/package.json +21 -0
- package/40-checker/fixtures/security/sec18-good/index.mjs +3 -0
- package/40-checker/fixtures/security/sec18-good/package.json +26 -0
- package/40-checker/fixtures/security/sec18-undeclared/index.mjs +3 -0
- package/40-checker/fixtures/security/sec18-undeclared/package.json +26 -0
- package/40-checker/fixtures/security/sec19-blob/index.mjs +2 -0
- package/40-checker/fixtures/security/sec19-blob/package.json +19 -0
- package/40-checker/fixtures/security/sec19-clean/index.mjs +2 -0
- package/40-checker/fixtures/security/sec19-clean/package.json +19 -0
- package/40-checker/fixtures/security/sec19-wasm/blob.wasm +0 -0
- package/40-checker/fixtures/security/sec19-wasm/index.mjs +3 -0
- package/40-checker/fixtures/security/sec19-wasm/package.json +19 -0
- package/40-checker/fixtures/security/sec20-unwrapped/index.mjs +4 -0
- package/40-checker/fixtures/security/sec20-unwrapped/package.json +23 -0
- package/40-checker/fixtures/security/sec20-wrapped/index.mjs +3 -0
- package/40-checker/fixtures/security/sec20-wrapped/package.json +23 -0
- package/40-checker/fixtures/security/sec21-known/index.mjs +3 -0
- package/40-checker/fixtures/security/sec21-known/package.json +23 -0
- package/40-checker/fixtures/security/sec21-typosquat/index.mjs +3 -0
- package/40-checker/fixtures/security/sec21-typosquat/package.json +22 -0
- package/40-checker/fixtures/security/sec22-attested/index.mjs +3 -0
- package/40-checker/fixtures/security/sec22-attested/package.json +22 -0
- package/40-checker/fixtures/security/sec22-none/index.mjs +3 -0
- package/40-checker/fixtures/security/sec22-none/package.json +19 -0
- package/40-checker/src/cli.mjs +8 -3
- package/40-checker/src/declarations.mjs +10 -10
- package/40-checker/src/index.mjs +2 -1
- package/40-checker/src/report.mjs +68 -2
- package/40-checker/src/security.mjs +1065 -0
- package/40-checker/src/util.mjs +53 -0
- package/CHANGELOG.md +14 -0
- package/docs/SIGNING.md +137 -0
- package/docs/coverage.md +39 -6
- package/package.json +1 -1
- package/spec/coverage-contract.md +33 -11
- package/spec/dsc-clauses.json +410 -9
- package/spec/remediation.json +196 -0
- package/spec/schemas/conformance.schema.json +75 -2
package/40-checker/src/util.mjs
CHANGED
|
@@ -123,6 +123,59 @@ export function listSourceFiles(root, options = {}) {
|
|
|
123
123
|
return scanSourceFiles(root, options).paths
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Inventory every file in a package (not only source extensions) without
|
|
128
|
+
* following symlinks. Used by the security checks for binary/obfuscation and
|
|
129
|
+
* script-reference analysis. Same skip directories as the source walker so
|
|
130
|
+
* node_modules/tests/docs do not dominate the sample.
|
|
131
|
+
* @param {string} root
|
|
132
|
+
* @param {{maxFiles?: number, maxBytes?: number}} [options]
|
|
133
|
+
*/
|
|
134
|
+
export function inventoryFiles(root, options = {}) {
|
|
135
|
+
const maxFiles = options.maxFiles ?? 1000
|
|
136
|
+
const maxBytes = options.maxBytes ?? 20_000_000
|
|
137
|
+
const files = []
|
|
138
|
+
let bytes = 0
|
|
139
|
+
let truncated = false
|
|
140
|
+
const walk = (directory) => {
|
|
141
|
+
if (files.length >= maxFiles || bytes >= maxBytes) {
|
|
142
|
+
truncated = true
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
let entries
|
|
146
|
+
try { entries = fs.readdirSync(directory, { withFileTypes: true }) } catch { return }
|
|
147
|
+
entries.sort((a, b) => a.name.localeCompare(b.name))
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
if (files.length >= maxFiles || bytes >= maxBytes) {
|
|
150
|
+
truncated = true
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
const absolute = path.join(directory, entry.name)
|
|
154
|
+
if (entry.isDirectory()) {
|
|
155
|
+
if (SKIP_DIRECTORIES.has(entry.name)) continue
|
|
156
|
+
if (entry.name.startsWith('.')) continue
|
|
157
|
+
walk(absolute)
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
if (!entry.isFile()) continue
|
|
161
|
+
let size = 0
|
|
162
|
+
try { size = fs.statSync(absolute).size } catch { continue }
|
|
163
|
+
if (bytes + size > maxBytes) {
|
|
164
|
+
truncated = true
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
bytes += size
|
|
168
|
+
files.push({
|
|
169
|
+
file: absolute,
|
|
170
|
+
relative: posixRelative(root, absolute),
|
|
171
|
+
size,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
walk(root)
|
|
176
|
+
return { files, stats: { files: files.length, bytes, maxFiles, maxBytes, truncated } }
|
|
177
|
+
}
|
|
178
|
+
|
|
126
179
|
/** Read all source files once, returning {file, relative, text} records. */
|
|
127
180
|
export function readSources(root, options) {
|
|
128
181
|
return scanSourceFiles(root, options).paths.map(file => ({
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 1.6.0 — 2026-11-07
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- 安全层 DSC-15…22:生命周期脚本、动态执行、敏感文件、外联端点、混淆/二进制、提示词注入线索、供应链、来源/完整性。
|
|
6
|
+
- `--scope security`:22 项检查,DSC-15/16/17/21 为强制;默认 14 项判定不变,security 块内 FAIL 封顶 WARN。
|
|
7
|
+
- 威胁模型与宿主边界(`34` §十):同进程全权限、capabilities 不被宿主读取、sandbox 仅子进程文件写、approval 仅 tool ask、`!!js` 可执行、client 可调 Remote。
|
|
8
|
+
- `SECURITY.md`(漏洞报告)、`spec/dsc-clauses.json` 22 父/113 条、`spec/remediation.json` C-15…22、coverage 口径 22。
|
|
9
|
+
|
|
10
|
+
### Verified
|
|
11
|
+
|
|
12
|
+
- `npm test` 269/269;恶意 fixture(curl|sh、eval、凭据读取、typosquat)在 security scope 下 FAIL;10 个真实包默认 entry 10/10 exit 0。
|
|
13
|
+
- 静态安全 PASS 不代表无恶意;检查器不能证明安全。
|
|
14
|
+
|
|
1
15
|
## 1.5.0 — 2026-11-07
|
|
2
16
|
|
|
3
17
|
### Added
|
package/docs/SIGNING.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Evidence signing (Ed25519)
|
|
2
|
+
|
|
3
|
+
`dsh-flow-evidence/2` documents can carry a detached Ed25519 signature. The
|
|
4
|
+
implementation uses only `node:crypto` (no dependencies) and lives in
|
|
5
|
+
`40-checker/src/signature.mjs`; `40-checker/scripts/sign-evidence.mjs` is the
|
|
6
|
+
CLI. Signing is optional: it does not change `validateFlowEvidence()`, does not
|
|
7
|
+
change the chain hash, and never upgrades a claim level.
|
|
8
|
+
|
|
9
|
+
## What is signed
|
|
10
|
+
|
|
11
|
+
The payload is canonical JSON (recursively sorted object keys, array order
|
|
12
|
+
preserved) of every field **except `signature`**. This includes `chain`, so a
|
|
13
|
+
signature also binds the declared `chain.evidenceHash`. The chain hash itself
|
|
14
|
+
is computed over the body only (it excludes `chain` and `signature`). A signed
|
|
15
|
+
document keeps its existing `chain.evidenceHash`; `signature` is outside the
|
|
16
|
+
hash, so signing and re-signing do not invalidate the chain.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
signature: {
|
|
20
|
+
alg: "ed25519",
|
|
21
|
+
keyId: "<first 16 hex of sha256(SPKI public key PEM)>",
|
|
22
|
+
publicKey: "<SPKI PEM>",
|
|
23
|
+
sig: "<base64 detached signature>",
|
|
24
|
+
at: "<ISO-8601 timestamp>"
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`keyId` is the first 16 hex characters of sha256 over the exact SPKI PEM string
|
|
29
|
+
embedded as `publicKey` (line endings included), so a verifier can recompute it
|
|
30
|
+
from the signature record. `publicKey` is embedded for transport convenience;
|
|
31
|
+
it is **self-asserted**.
|
|
32
|
+
|
|
33
|
+
## Key management
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
# Generate once, outside the repository working tree.
|
|
37
|
+
node 40-checker/scripts/sign-evidence.mjs keygen --out ~/.dsh-evidence-keys
|
|
38
|
+
# ed25519.pem (PKCS#8 private) and ed25519.pub.pem (SPKI public), both mode 0600.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- Never commit `ed25519.pem`, put it in logs, or paste it into an issue. Back it
|
|
42
|
+
up in a secret manager; losing it means old signatures can no longer be
|
|
43
|
+
produced (they remain verifiable).
|
|
44
|
+
- `keygen` refuses to overwrite an existing key unless `--force` is passed.
|
|
45
|
+
- Distribute `ed25519.pub.pem` and its `keyId` through a separate, trusted
|
|
46
|
+
channel (release notes, repository file, pinned fingerprint). A public key
|
|
47
|
+
fetched from the same JSON it verifies proves nothing about identity.
|
|
48
|
+
- Rotate by generating a new pair; signatures keep their old `keyId`. Verifiers
|
|
49
|
+
should accept a set of pinned keyIds and record which one matched.
|
|
50
|
+
- The `at` field is signer-defined. It is evidence metadata, not a trusted
|
|
51
|
+
timestamp; a signer can backdate or replay. Add policy (expiry, nonce,
|
|
52
|
+
transparency log) outside this tool if that matters.
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
# Sign after the collector has written and finalized the evidence.
|
|
58
|
+
node 40-checker/scripts/sign-evidence.mjs sign flow-evidence.json \
|
|
59
|
+
--key ~/.dsh-evidence-keys/ed25519.pem \
|
|
60
|
+
--out flow-evidence.signed.json
|
|
61
|
+
|
|
62
|
+
# Verify. Prints exactly one of: valid | invalid | missing.
|
|
63
|
+
node 40-checker/scripts/sign-evidence.mjs verify flow-evidence.signed.json
|
|
64
|
+
node 40-checker/scripts/sign-evidence.mjs verify flow-evidence.signed.json \
|
|
65
|
+
--pub ~/.dsh-evidence-keys/ed25519.pub.pem
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Exit codes: `0` valid, `1` invalid or missing, `2` usage/input error.
|
|
69
|
+
`--json` prints the detailed result (`status`, `keyId`, `keyIdMatch`,
|
|
70
|
+
`publicKeySource`, `reason`). `missing` means the document has no signature (or
|
|
71
|
+
`signature: null`), or there is no public key to check it (no embedded
|
|
72
|
+
`publicKey` and no readable `--pub`). `invalid` covers malformed records, key
|
|
73
|
+
mismatch, and a payload that does not verify. When `--pub` is supplied, it is
|
|
74
|
+
the trust anchor: the embedded key is only used to cross-check `keyId`.
|
|
75
|
+
|
|
76
|
+
The same helpers are re-exported from `40-checker/src/flow-evidence.mjs`:
|
|
77
|
+
`attachEvidenceSignature()`, `verifyEvidenceSignature()`,
|
|
78
|
+
`evidenceSigningPayload()`, `keyIdFromPublicKey()`.
|
|
79
|
+
|
|
80
|
+
## CI usage
|
|
81
|
+
|
|
82
|
+
Keep the private key in a CI secret and materialize it as a 0600 file only for
|
|
83
|
+
the signing step; delete it immediately afterwards. Never print it.
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
- name: Sign FLOW evidence
|
|
87
|
+
env:
|
|
88
|
+
EVIDENCE_PRIVATE_KEY: ${{ secrets.EVIDENCE_PRIVATE_KEY }}
|
|
89
|
+
run: |
|
|
90
|
+
umask 077
|
|
91
|
+
printf '%s' "$EVIDENCE_PRIVATE_KEY" > "$RUNNER_TEMP/evidence-key.pem"
|
|
92
|
+
node 40-checker/scripts/sign-evidence.mjs sign flow-evidence.json \
|
|
93
|
+
--key "$RUNNER_TEMP/evidence-key.pem" \
|
|
94
|
+
--out flow-evidence.signed.json
|
|
95
|
+
rm -f "$RUNNER_TEMP/evidence-key.pem"
|
|
96
|
+
|
|
97
|
+
- name: Verify with the pinned public key
|
|
98
|
+
run: |
|
|
99
|
+
node 40-checker/scripts/sign-evidence.mjs verify flow-evidence.signed.json \
|
|
100
|
+
--pub .github/evidence-pub.pem
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
For PRs from forks, do not expose the signing secret to code from the fork.
|
|
104
|
+
Sign on a trusted workflow (e.g. `workflow_run` after review, or a maintainer
|
|
105
|
+
run); a signature produced by untrusted code is only as trustworthy as that
|
|
106
|
+
code.
|
|
107
|
+
|
|
108
|
+
## Trust boundary
|
|
109
|
+
|
|
110
|
+
**A valid self-signature is not trust.** It proves that whoever held the
|
|
111
|
+
private key signed these exact bytes. It does **not** prove:
|
|
112
|
+
|
|
113
|
+
- who that holder is (no identity, no CA, no account binding);
|
|
114
|
+
- that the evidence was produced honestly, or that commands ran where claimed;
|
|
115
|
+
- that the subject package is safe, correct, or officially endorsed;
|
|
116
|
+
- freshness: `at` can be backdated and a valid old document can be replayed;
|
|
117
|
+
- that the signer is allowed to speak for a project or marketplace.
|
|
118
|
+
|
|
119
|
+
Consequences:
|
|
120
|
+
|
|
121
|
+
- A verifier must pin the public key or `keyId` out-of-band. Treat the embedded
|
|
122
|
+
`publicKey` as a hint, not as an identity.
|
|
123
|
+
- Compromise of the private key compromises every signature made with it;
|
|
124
|
+
revocation and rotation are policy decisions this tool does not implement.
|
|
125
|
+
- Signature + chain hash give integrity and origin-of-holding, not truth. The
|
|
126
|
+
structural rules of `validateFlowEvidence()` and `level-audit` are unchanged;
|
|
127
|
+
signing cannot turn `self-attested`/`ci-attested` into `sandbox-attested`.
|
|
128
|
+
- A marketplace or adoption decision still needs its own policy: who is
|
|
129
|
+
allowed to sign, which keyIds are accepted, what evidence is required, how
|
|
130
|
+
long it is valid, and how disputes/revocations are handled. This repository
|
|
131
|
+
ships a mechanism, not a trust root and not a certification.
|
|
132
|
+
|
|
133
|
+
For the sandbox side of the evidence, see `40-checker/src/sandbox-run.mjs`:
|
|
134
|
+
`setpriv --no-new-privs --bounding-set=-all`, extra `nofile`/`fsize` rlimits,
|
|
135
|
+
and a read-only bind of the package copy are attempted and recorded. They are
|
|
136
|
+
containment, not seccomp/cgroup/OCI isolation, and they do not make a
|
|
137
|
+
self-signed document trustworthy.
|
package/docs/coverage.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# 覆盖说明(
|
|
1
|
+
# 覆盖说明(22 项口径)— `dsh-standards check` 默认查什么
|
|
2
2
|
|
|
3
3
|
`dsh-standards check <package-dir>` 是作者入口。**默认 `--entry-claim static --no-exec`:不执行插件代码**。本文只描述这套默认,不把 `not-run` / `NA` 写成 PASS。
|
|
4
4
|
|
|
@@ -13,13 +13,15 @@ dsh-standards check <package-dir> \
|
|
|
13
13
|
|
|
14
14
|
| 口径 | 数量 | 说明 |
|
|
15
15
|
|---|---|---|
|
|
16
|
-
|
|
|
16
|
+
| 完整口径 | **22** | `DSC-01` … `DSC-22`;`--scope security` 时 `checks[]` 带全 22 项 |
|
|
17
|
+
| 默认 `checks[]` | **14** | `DSC-01` … `DSC-14`;默认 DS-C 判定只看这 14 项 |
|
|
17
18
|
| 默认实际评估 | **6** | `DSC-01`、`DSC-02`、`DSC-10`、`DSC-11`、`DSC-12`、`DSC-13` |
|
|
18
19
|
| 默认未检查 | **8** | `DSC-03`(NA)+ `DSC-04`–`DSC-09`(not-run)+ `DSC-14`(NA) |
|
|
20
|
+
| 默认 security 块 | **8** | `DSC-15`–`DSC-22` 在 `security` 块里跑 advisory;FAIL 封顶 WARN,不改变 DS-C 结论 |
|
|
19
21
|
|
|
20
22
|
`not checked` 桶把 NA / not-run / ERROR 都列出来;**没检查不等于通过**。`DSC-10` 是 advisory,只提供信号,不改变结论。
|
|
21
23
|
|
|
22
|
-
##
|
|
24
|
+
## 22 项逐条
|
|
23
25
|
|
|
24
26
|
| ID | 层 | 默认状态 | 默认为什么 | 怎么开启 |
|
|
25
27
|
|---|---|---|---|---|
|
|
@@ -37,6 +39,33 @@ dsh-standards check <package-dir> \
|
|
|
37
39
|
| `DSC-12` | L0 | **检查** | 凭据 backing file、`listRecords`、seam、commitments/disclosure 形状 | 默认即查 |
|
|
38
40
|
| `DSC-13` | L0 | **检查** | `dsh.labels` / `upstream` / 禁用语标签 | 默认即查 |
|
|
39
41
|
| `DSC-14` | L1 | **NA** | 默认 `entry-claim=static`,不声明运行时证据 | `--scope entry --entry-claim runtime --runtime <...>`(bundle 加 `--host`) |
|
|
42
|
+
| `DSC-15` | L0 | **advisory(security 块)** | 默认 scope 下 FAIL 封顶 WARN | `--scope security`;生命周期脚本,可 FAIL |
|
|
43
|
+
| `DSC-16` | L0 | **advisory(security 块)** | 默认 scope 下 FAIL 封顶 WARN | `--scope security`;动态执行,可 FAIL |
|
|
44
|
+
| `DSC-17` | L0 | **advisory(security 块)** | 默认 scope 下 FAIL 封顶 WARN | `--scope security`;敏感文件,可 FAIL |
|
|
45
|
+
| `DSC-18` | L0 | **advisory(security 块)** | 端点线索,永不 FAIL | `--scope security`;外联端点 |
|
|
46
|
+
| `DSC-19` | L0 | **advisory(security 块)** | 只登记不判恶 | `--scope security`;混淆/二进制 |
|
|
47
|
+
| `DSC-20` | L0 | **advisory(security 块)** | 语义不可判、误报高 | `--scope security`;提示词注入 |
|
|
48
|
+
| `DSC-21` | L0 | **advisory WARN(security 块)** | 名字线索;默认 scope 不 FAIL | `--scope security`;供应链,强制项但只 WARN |
|
|
49
|
+
| `DSC-22` | L0 | **advisory(security 块)** | 只有字段自证 | `--scope security`;来源/完整性,v1 只读字段 |
|
|
50
|
+
|
|
51
|
+
## security scope(C-15~C-22)
|
|
52
|
+
|
|
53
|
+
`--scope security` 是**独立、opt-in** 的安全范围。它把 8 项加入 `checks[]`(14 → 22),但**不改变** `DS-C` 徽章、也不并入 DS-C 的 11/12 条强制。它只降低风险,**不证明无恶意**:
|
|
54
|
+
|
|
55
|
+
| 项 | security scope 判定 | 触发 |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `DSC-15` 生命周期脚本 | **blocking**(可 FAIL) | 安装期 `curl\|sh`、`base64 -d`、`node -e`、`sh -c`、`powershell -enc`、引用缺失脚本、写 `$HOME`/`/etc` |
|
|
58
|
+
| `DSC-16` 动态执行 | **blocking**(可 FAIL) | `eval`/`new Function(非空)`/`vm`/`module._compile`/`process.binding` → FAIL;裸 `child_process` → WARN |
|
|
59
|
+
| `DSC-17` 敏感文件 | **blocking**(可 FAIL) | 直接读 `~/.ssh`、`$DSH_HOME` credentials、`id_rsa`、`.aws`、`.npmrc`、`.netrc` 等;`.env` 只 WARN |
|
|
60
|
+
| `DSC-18` 外联端点 | **advisory**(永不 FAIL) | 非 https、userinfo、公网裸 IP、webhook/paste/ngrok/onion、未披露端点 |
|
|
61
|
+
| `DSC-19` 混淆/二进制 | **advisory** | 二进制 sha256、超长单行、大块 base64、高熵;只登记不判恶 |
|
|
62
|
+
| `DSC-20` 提示词注入 | **advisory** | 外部文本进 systemPrompt、指令样字面量、无 untrusted 包裹;语义不可判 |
|
|
63
|
+
| `DSC-21` 供应链 | **blocking(强制项)/ WARN(发现封顶)** | scope 同形字/近似 scope、近似名、元数据缺失;**永不 FAIL**,但未跑会使 security `incomplete` |
|
|
64
|
+
| `DSC-22` 来源/完整性 | **advisory**(永不 FAIL) | 只读 `dist.integrity` / `dsh.attestation` / `dsh.signature` 字段是否存在;不验签、不比对 digest;无输入即 `not-run` |
|
|
65
|
+
|
|
66
|
+
**默认不改变判定**:不传 `--scope security` 时,这 8 项不进 `checks[]`,只在 `security` 块里以 `scope=advisory` 运行;任何 FAIL 封顶 WARN,`DS-C` 的 11/12 条强制集合与 exit code 不变。security 的 FAIL 不能撤销 `DS-C` PASS,`DS-C` PASS 也不能证明 security 通过。
|
|
67
|
+
|
|
68
|
+
**边界**:C-15/C-16/C-17 可以 FAIL;C-21 是强制项但发现只到 WARN;C-18/C-19/C-20/C-22 是 advisory;其余安全面(实际网络流量、审批绕过、同进程逃逸、client DOM、`!!js` 配置、Remote 调用方身份)**没有机械判据**。任何 PASS 都只是"本次覆盖内未见该类线索",不是"安全/已审计"。
|
|
40
69
|
|
|
41
70
|
## 执行 opt-in
|
|
42
71
|
|
|
@@ -44,7 +73,8 @@ dsh-standards check <package-dir> \
|
|
|
44
73
|
|
|
45
74
|
- `--runtime <path>`:Cordis 运行时的 `lib/index.js`,开启 L1–L3;
|
|
46
75
|
- `--host <file>`:app-boot 入口,开启 bundle 的 G0–G3 冒烟;
|
|
47
|
-
- `--entry-claim runtime`:entry 门声明运行时证据(通常还要配 `--runtime` / `--host
|
|
76
|
+
- `--entry-claim runtime`:entry 门声明运行时证据(通常还要配 `--runtime` / `--host`);
|
|
77
|
+
- `--scope security`:只跑 C-15~C-22 的静态/元数据检查,**仍不执行插件代码**(C-15 只读脚本文本,不跑 `npm install`)。
|
|
48
78
|
|
|
49
79
|
想保留运行时声明但只看静态结果,显式加 `--no-exec`。`--runtime-version` 只是 anchor,**不是**执行开关。
|
|
50
80
|
|
|
@@ -68,8 +98,11 @@ dsh-standards check <package-dir> \
|
|
|
68
98
|
## 复核命令
|
|
69
99
|
|
|
70
100
|
```sh
|
|
71
|
-
# 默认:6/14 评估,8 项 not-run/NA
|
|
72
|
-
dsh-standards check <package-dir> --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s);console.log(r.coverage.checked+"/"+r.coverage.total);console.log(r.anchor);console.log(r.
|
|
101
|
+
# 默认:6/14 评估,8 项 not-run/NA;security 块是 advisory
|
|
102
|
+
dsh-standards check <package-dir> --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s);console.log(r.coverage.checked+"/"+r.coverage.total);console.log(r.anchor);console.log(r.security)})'
|
|
103
|
+
|
|
104
|
+
# 默认 + security scope:checks[] 变 22 项(不执行插件)
|
|
105
|
+
dsh-standards check <package-dir> --scope security --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s);console.log(r.coverage.checked+"/"+r.coverage.total);console.log(r.security)})'
|
|
73
106
|
|
|
74
107
|
# 显式声明宿主版本,仍不执行插件
|
|
75
108
|
dsh-standards check <package-dir> --host-version 0.2.2-rc.1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-standards",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "DSH plugin standards and conformance checks: ENTRY/FLOW checks plus BOOST guidance. Non-official; no certification or ranking.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "40-checker/src/index.mjs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# coverage 契约(`dsh-conformance/1`)
|
|
2
2
|
|
|
3
|
-
> 适用版本:dsh-standards **1.
|
|
3
|
+
> 适用版本:dsh-standards **1.6.0**(计划版本;1.5.0 及更早为 14 项)。`40-checker/src/report.mjs` 的 `buildReport()` 负责计数;`security` 块见 §6。
|
|
4
4
|
> 机器可校验形状见 [`schemas/conformance.schema.json`](schemas/conformance.schema.json);本文只统一字段含义。
|
|
5
5
|
> 结论:`coverage` 是**加性计数**,不参与 verdict;`PASS/WARN/FAIL` 与 `status/level` 由 `conclusion` 和对应 profile 的强制项决定。
|
|
6
6
|
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
| 字段 | 定义 |
|
|
19
19
|
|---|---|
|
|
20
|
-
| `total` | `checks[].length
|
|
21
|
-
| `mandatory` | 当前 `scope`/`profile`/`staticOnly`/`entryClaim` 选出的强制检查 ID 数,不是
|
|
20
|
+
| `total` | `checks[].length`:默认 `ds-c` 为 14(`DSC-01`…`DSC-14`);`--scope security` 为 22(再加 `DSC-15`…`DSC-22`)。 |
|
|
21
|
+
| `mandatory` | 当前 `scope`/`profile`/`staticOnly`/`entryClaim` 选出的强制检查 ID 数,不是 22;默认 package 11、listing 12、`--scope security` 4(`DSC-15/16/17/21`)。 |
|
|
22
22
|
| `checked` | 结果属于 `PASS` / `WARN` / `FAIL` 的检查数,即**实际评估过**的项。 |
|
|
23
23
|
| `pass` | 结果 `PASS` 的数量。 |
|
|
24
24
|
| `warn` | 结果 `WARN` 的数量。 |
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
- `checked = pass + warn + fail`(`advisory <= warn`)。
|
|
43
43
|
- `error <= notRun`;`notRun` 统计的是“没拿到完整结论”的项,`NA` 不算没跑。
|
|
44
|
-
-
|
|
44
|
+
- 默认 `ds-c` 完整跑(14 项、无缺失)与 `--scope security` 完整跑(22 项、无缺失)都满足 `checked + na + notRun = total`;两份报告的 `total` 不同,不能混算通过率。
|
|
45
45
|
- **例外**:若某项 `coverage: "partial"` 但结果仍是 `WARN`/`FAIL`,它会同时出现在 `checked` 与 `notRun` 中——已评估但不完整,不能当成 `full`。有缺失强制项时,`notRun` 会含不在 `checks[]` 里的 ID,此时上述加法可大于 `total`。
|
|
46
46
|
- `metrics = { checked, total, declaredFields }`:`checked`/`total` 与 `coverage` 同源;`declaredFields` 是 capabilities/labels/commitments/disclosure/engines.dsh 五个可选声明面中实际声明的个数(0–5),只作上下文。
|
|
47
47
|
|
|
@@ -78,23 +78,45 @@
|
|
|
78
78
|
- `checked`:真正产出 detail 的前提;`notChecked`:没产出的前提。
|
|
79
79
|
- `checked.length + notChecked.length = required`;`failed` 是 `checked` 中结果为 `FAIL` 的子集。
|
|
80
80
|
- `pass` 仅在 `failed` 为空**且** `notChecked` 为空时为 `true`。没给 `--install-spec`/`--market-index` 时 C-3.x 未查,`pass` 必为 `false`。
|
|
81
|
-
- `entryBlocking` 不替代 `coverage`;它回答“阻断前提齐不齐”,`coverage`
|
|
81
|
+
- `entryBlocking` 不替代 `coverage`;它回答“阻断前提齐不齐”,`coverage` 回答“`checks[]` 各是什么状态”(默认 14 项,security 22 项)。
|
|
82
82
|
|
|
83
|
-
## 5.
|
|
83
|
+
## 5. 示例(`--scope security` 示意,22 项;数字非实测)
|
|
84
84
|
|
|
85
85
|
```json
|
|
86
86
|
{
|
|
87
|
-
"total":
|
|
88
|
-
"pass":
|
|
89
|
-
"na": 1, "notRun": 7, "error": 7, "entryBlocking": null
|
|
87
|
+
"total": 22, "mandatory": 4, "checked": 14,
|
|
88
|
+
"pass": 12, "warn": 1, "advisory": 1, "fail": 1,
|
|
89
|
+
"na": 1, "notRun": 7, "error": 7, "entryBlocking": null,
|
|
90
|
+
"security": {
|
|
91
|
+
"scope": "security", "status": "fail",
|
|
92
|
+
"blocking": ["DSC-17"], "warn": [], "advisory": ["DSC-19"], "notRun": []
|
|
93
|
+
}
|
|
90
94
|
}
|
|
91
95
|
```
|
|
92
96
|
|
|
93
|
-
读法:
|
|
97
|
+
读法:12 PASS + 1 WARN + 1 FAIL = 14 项实际评估;1 项显式 NA;7 项 ERROR/未跑;`checked + na + notRun = 22`;`error (7) <= notRun (7)`。`conclusion.status` 由 `--scope security` 的 4 个强制项(`DSC-15/16/17/21`)决定;`security.status=fail` 只是 security 块的独立结论,不把 `DS-C` 徽章改写成 fail。
|
|
94
98
|
|
|
95
|
-
## 6.
|
|
99
|
+
## 6. security scope(C-15~C-22)
|
|
100
|
+
|
|
101
|
+
`--scope security` 是独立 opt-in scope,不并入 `ds-c` / `entry` / `mvp`。8 个 security 检查**始终**在报告的 `security` 块里运行;是否进入 `checks[]`、是否可 FAIL,取决于 scope。
|
|
102
|
+
|
|
103
|
+
| 项 | 语义 |
|
|
104
|
+
|---|---|
|
|
105
|
+
| 计入 `total` | **默认否**:`checks[]` 仍是 14,`security` 块单独存在、不计入 `coverage`。**security 是**:`checks[]` 22,`coverage.total=22`、`coverage.mandatory=4`。 |
|
|
106
|
+
| 默认 `security.scope` | `"advisory"`。8 项照常评估,但任何 `FAIL` 封顶为 `WARN`,不改 `conclusion`/exit;`security.status` 可为 `pass`/`warn`。 |
|
|
107
|
+
| `security.status=pass` | **security scope 下**:无 FAIL、无强制项结果不在 `PASS`/`WARN`/`FAIL`、无任何 WARN(含 advisory)。默认 advisory 模式只可能 `pass`/`warn`,且 `pass` 不代表覆盖完整。advisory 的 `not-run`(如 C-22 无元数据)不挡 `pass`,但必须留在 `security.notRun[]`;C-21 的传递依赖元数据缺失只记 check 级 `coverage: partial`,不等同 PASS。 |
|
|
108
|
+
| `security.status=fail` | **仅 security scope**:`DSC-15`/`DSC-16`/`DSC-17` 任一 FAIL。`DSC-21` 永不 FAIL;默认模式 FAIL 封顶 WARN,不会出现 fail。 |
|
|
109
|
+
| `security.status=warn` | 无 FAIL,但有 `DSC-21` 或 15/16/17 的 WARN,或 18/19/20/22 的 advisory WARN。 |
|
|
110
|
+
| `security.status=incomplete` | **security scope 下** `DSC-15`/`DSC-16`/`DSC-17`/`DSC-21` 任一结果不在 `PASS`/`WARN`/`FAIL`(即 `NA`/`ERROR`/`not-run`)。 |
|
|
111
|
+
| 与 DS-C 关系 | **独立**:security FAIL 不撤销 DS-C PASS;DS-C PASS 不证明 security 通过;反之亦然。 |
|
|
112
|
+
| 徽章 | **不得**发 security 徽章;市场只透传 `security.status`、`security.checks[]` 与 `security.notRun[]`,不按安全分排序/筛选。 |
|
|
113
|
+
|
|
114
|
+
严重度以 `spec/dsc-clauses.json` 的 `mandatory`/`advisory` 与 31 §2 C-15~C-22 为准:`DSC-15/16/17` 为 blocking(可 FAIL);`DSC-21` 是强制项但发现封顶 WARN(永不 FAIL,未跑才 `incomplete`);`DSC-18/19/20/22` 为 advisory(`advisory:true`,永不改 verdict)。`security` 块只汇总这 8 项,不与顶层 `coverage` 换算成第二套通过率。
|
|
115
|
+
|
|
116
|
+
## 7. 禁止事项
|
|
96
117
|
|
|
97
118
|
- 不得把 `na`、`notRun`、`error` 或 `partial` 写成 PASS。
|
|
98
119
|
- 不得用 `coverage.pass / total` 当通过率或等级;`total` 含未跑项,且 `mandatory` 随 profile 变。
|
|
99
120
|
- 不得把 `advisory` 计入 `fail` 或据其改变 `exitCode`。
|
|
100
121
|
- 检查级 `coverage` 与报告级 `coverage` 同名不同义;引用时必须写全路径(`checks[].coverage` vs `coverage`)。
|
|
122
|
+
- 不得把 `security.status` 写成安全认证/审计结论;security 的 `pass` 只覆盖这 8 项的静态/元数据证据。
|