dsh-standards 1.0.3 → 1.2.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.
Files changed (99) hide show
  1. package/40-checker/README.md +15 -0
  2. package/40-checker/bin/dsh-flow-collect.mjs +3 -0
  3. package/40-checker/bin/dsh-levels.mjs +13 -2
  4. package/40-checker/fixtures/bundle-patch-only/cordis.patch.yml +3 -0
  5. package/40-checker/fixtures/bundle-patch-only/package.json +12 -0
  6. package/40-checker/fixtures/c12-command-arg/index.mjs +7 -0
  7. package/40-checker/fixtures/c12-command-arg/package.json +10 -0
  8. package/40-checker/fixtures/c12-comment/index.mjs +3 -0
  9. package/40-checker/fixtures/c12-comment/package.json +10 -0
  10. package/40-checker/fixtures/c12-compare/index.mjs +3 -0
  11. package/40-checker/fixtures/c12-compare/package.json +10 -0
  12. package/40-checker/fixtures/c12-doc-string/index.mjs +13 -0
  13. package/40-checker/fixtures/c12-doc-string/package.json +10 -0
  14. package/40-checker/fixtures/c12-grant/index.mjs +3 -0
  15. package/40-checker/fixtures/c12-grant/package.json +10 -0
  16. package/40-checker/fixtures/c12-string/index.mjs +3 -0
  17. package/40-checker/fixtures/c12-string/package.json +10 -0
  18. package/40-checker/fixtures/declaration-file/index.d.ts +1 -0
  19. package/40-checker/fixtures/declaration-file/index.mjs +9 -0
  20. package/40-checker/fixtures/declaration-file/package.json +10 -0
  21. package/40-checker/fixtures/import-local-side-effect/index.mjs +11 -0
  22. package/40-checker/fixtures/import-local-side-effect/package.json +21 -0
  23. package/40-checker/fixtures/import-local-side-effect/side-effectful.mjs +5 -0
  24. package/40-checker/fixtures/import-open-readonly/index.mjs +14 -0
  25. package/40-checker/fixtures/import-open-readonly/package.json +19 -0
  26. package/40-checker/fixtures/import-package-only/index.mjs +11 -0
  27. package/40-checker/fixtures/import-package-only/package.json +21 -0
  28. package/40-checker/fixtures/import-parse-error/index.mjs +5 -0
  29. package/40-checker/fixtures/import-parse-error/package.json +10 -0
  30. package/40-checker/fixtures/import-side-effect/index.mjs +14 -0
  31. package/40-checker/fixtures/import-side-effect/package.json +19 -0
  32. package/40-checker/fixtures/import-side-effect-nonentry/index.mjs +9 -0
  33. package/40-checker/fixtures/import-side-effect-nonentry/package.json +19 -0
  34. package/40-checker/fixtures/import-side-effect-nonentry/selfcheck.mjs +8 -0
  35. package/40-checker/fixtures/import-void-main/index.mjs +16 -0
  36. package/40-checker/fixtures/import-void-main/package.json +19 -0
  37. package/40-checker/fixtures/no-source/package.json +19 -0
  38. package/40-checker/fixtures/parse-error-nonentry/index.mjs +2 -0
  39. package/40-checker/fixtures/parse-error-nonentry/package.json +10 -0
  40. package/40-checker/fixtures/parse-error-nonentry/references/broken.js +1 -0
  41. package/40-checker/scripts/bundle-smoke.mjs +3 -0
  42. package/40-checker/scripts/entry18-ci.mjs +4 -3
  43. package/40-checker/scripts/entry18-web.mjs +4 -1
  44. package/40-checker/scripts/flow-collect.mjs +494 -0
  45. package/40-checker/scripts/flow-sample-ci.mjs +4 -4
  46. package/40-checker/scripts/host-farm.mjs +2 -1
  47. package/40-checker/scripts/journey-config-observe.mjs +5 -3
  48. package/40-checker/scripts/journey-runtime.mjs +208 -83
  49. package/40-checker/scripts/level-audit.mjs +91 -19
  50. package/40-checker/scripts/m2-runner.mjs +4 -3
  51. package/40-checker/scripts/runtime-child.mjs +4 -0
  52. package/40-checker/src/cli.mjs +11 -3
  53. package/40-checker/src/declarations.mjs +514 -19
  54. package/40-checker/src/flow-evidence.mjs +431 -0
  55. package/40-checker/src/index.mjs +3 -0
  56. package/40-checker/src/manifest.mjs +5 -0
  57. package/40-checker/src/remediation.mjs +132 -0
  58. package/40-checker/src/report.mjs +19 -2
  59. package/40-checker/src/sandbox-env.mjs +78 -0
  60. package/40-checker/test/checks.test.mjs +243 -1
  61. package/40-checker/test/flow-evidence.test.mjs +295 -0
  62. package/40-checker/test/remediation.test.mjs +300 -0
  63. package/40-checker/vendor/babel/README.md +10 -0
  64. package/40-checker/vendor/babel/index.cjs +3 -0
  65. package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/LICENSE +22 -0
  66. package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/README.md +19 -0
  67. package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/lib/index.d.ts +41 -0
  68. package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/lib/index.js +286 -0
  69. package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/package.json +31 -0
  70. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/LICENSE +22 -0
  71. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/README.md +19 -0
  72. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/identifier-browser.js +62 -0
  73. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/identifier.js +60 -0
  74. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/index.d.ts +28 -0
  75. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/index.js +27 -0
  76. package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/package.json +45 -0
  77. package/40-checker/vendor/babel/node_modules/@babel/parser/CHANGELOG.md +1073 -0
  78. package/40-checker/vendor/babel/node_modules/@babel/parser/LICENSE +19 -0
  79. package/40-checker/vendor/babel/node_modules/@babel/parser/README.md +19 -0
  80. package/40-checker/vendor/babel/node_modules/@babel/parser/bin/babel-parser.js +14 -0
  81. package/40-checker/vendor/babel/node_modules/@babel/parser/lib/index.d.ts +1047 -0
  82. package/40-checker/vendor/babel/node_modules/@babel/parser/lib/index.js +14612 -0
  83. package/40-checker/vendor/babel/node_modules/@babel/parser/package.json +57 -0
  84. package/40-checker/vendor/babel/node_modules/@babel/parser/typings/babel-parser.d.ts +636 -0
  85. package/40-checker/vendor/babel/node_modules/@babel/types/LICENSE +22 -0
  86. package/40-checker/vendor/babel/node_modules/@babel/types/README.md +19 -0
  87. package/40-checker/vendor/babel/node_modules/@babel/types/lib/index-legacy.d.ts +2765 -0
  88. package/40-checker/vendor/babel/node_modules/@babel/types/lib/index.d.ts +3668 -0
  89. package/40-checker/vendor/babel/node_modules/@babel/types/lib/index.js +10758 -0
  90. package/40-checker/vendor/babel/node_modules/@babel/types/lib/index.js.flow +2618 -0
  91. package/40-checker/vendor/babel/node_modules/@babel/types/package.json +38 -0
  92. package/CHANGELOG.md +66 -0
  93. package/bin/dsh-flow-collect.mjs +4 -0
  94. package/package.json +2 -1
  95. package/spec/README.md +2 -0
  96. package/spec/flow/README.md +23 -2
  97. package/spec/flow-evidence.schema.json +217 -0
  98. package/spec/levels.json +2 -0
  99. package/spec/remediation.json +478 -0
@@ -19,6 +19,8 @@ node bin/dsh-plugin-verify.mjs /path/to/plugin --format json --out report.json
19
19
  node bin/dsh-plugin-verify.mjs /path/to/plugin --format sarif --out report.sarif
20
20
  ```
21
21
 
22
+ `buildReport()` 与 `--format json` 默认带**加性** `advice[]`(来自 `spec/remediation.json` 的 68 条规则;FAIL/WARN detail 优先,无 detail 的运行时 FAIL 用 `check.clause` 兜底)。`--no-advice` 可关闭;它不改变任何 check 的结果或 exit code。
23
+
22
24
  ## 2. 检查项
23
25
 
24
26
  | ID | 条款 | 层 | 说明 |
@@ -124,6 +126,19 @@ node scripts/level-audit.mjs ./my-bundle \
124
126
  - ENTRY 自动;FLOW 只读 `--flow-evidence`(J1–J8),BOOST 只读 `--boost-evidence`(LEV-1–8)。
125
127
  - 没有证据就写 `not-run` / `not-measured`,工具不会替作者升级。
126
128
 
129
+ ### FLOW 证据链 `dsh-flow-collect`
130
+
131
+ ```bash
132
+ node bin/dsh-flow-collect.mjs --package ./my-plugin --out flow-evidence.json
133
+ # 等价:node bin/dsh-levels.mjs flow collect --package ./my-plugin --out flow-evidence.json
134
+ ```
135
+
136
+ - 只接受 `dsh-flow-evidence/2`(`spec/flow-evidence.schema.json`)。`level-audit --flow-evidence` 对 v1(`dsh-flow-journey/1`、`dsh-flow-runtime/1`)和裸 `{Jx:{status}}` 标 `deprecated`,不计 pass。
137
+ - 每个 J 必须至少 1 条 `checks[]` 且全部 `exitCode === 0`、断言成立、至少一条非 `static` 观测检查,才计 pass;`static` preflight 不是 runtime,`warn`/`blocked`/`na` 都不计 8/8。
138
+ - collector 把包复制到临时目录再跑,前后 `treeSha256` 必须一致;子进程 env 走白名单(不含 token/key/secret);不授予 `allowBuilds`,不自动 `allow-version`。
139
+ - 前置缺失(无 dsh、无 `--previous`、宿主兼容门禁拒绝)记 `blocked` + reason,出口码 0;真失败出口码 1,并打印五要素提示(子检查 / 命令 / exitCode / stderr 末 20 行 / 日志绝对路径 + 条款锚)。
140
+ - 本地证据只是 `self-attested`:结构自洽 + 绑定此包 + 未过期,**不证明真跑过**,也不是沙箱、认证或安全审计。
141
+
127
142
  ## 4. 运行 L1–L3
128
143
 
129
144
  需要一个**已构建**的 `@deepseek-ai/cordis` 入口。检查器不修改宿主,也不从源码构建宿主:
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // FLOW collector entry point; the implementation lives in scripts/.
3
+ import '../scripts/flow-collect.mjs'
@@ -2,6 +2,7 @@
2
2
  /**
3
3
  * dsh-levels — one entry point for the three-level audit.
4
4
  * It forwards to level-audit.mjs so there is a single documented command.
5
+ * `dsh-levels flow collect ...` forwards to the FLOW evidence collector.
5
6
  */
6
7
  import path from 'node:path'
7
8
  import { spawnSync } from 'node:child_process'
@@ -9,7 +10,17 @@ import { fileURLToPath } from 'node:url'
9
10
 
10
11
  const HERE = path.dirname(fileURLToPath(import.meta.url))
11
12
  const AUDIT = path.resolve(HERE, '..', 'scripts', 'level-audit.mjs')
13
+ const FLOW_COLLECT = path.resolve(HERE, 'dsh-flow-collect.mjs')
12
14
  const args = process.argv.slice(2)
13
- if (args[0] === 'audit') args.shift()
14
- const run = spawnSync(process.execPath, [AUDIT, ...args], { stdio: 'inherit' })
15
+ let target = AUDIT
16
+ if (args[0] === 'flow' && args[1] === 'collect') {
17
+ args.splice(0, 2)
18
+ target = FLOW_COLLECT
19
+ } else if (args[0] === 'flow') {
20
+ console.error('usage: dsh-levels flow collect --package <dir> [--out <file>] [--static] [--previous <dir>]')
21
+ process.exit(2)
22
+ } else if (args[0] === 'audit') {
23
+ args.shift()
24
+ }
25
+ const run = spawnSync(process.execPath, [target, ...args], { stdio: 'inherit' })
15
26
  process.exitCode = run.status ?? 1
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: fixture-bundle-server
3
+ name: './server.mjs'
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@dsh-fixtures/bundle-patch-only",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
6
+ "dsh": {
7
+ "manifestVersion": 1,
8
+ "bundle": { "patch": "./cordis.patch.yml" },
9
+ "capabilities": [],
10
+ "labels": []
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ import { spawnSync } from 'node:child_process'
2
+
3
+ export const name = 'c12-command-arg'
4
+ export function apply() {}
5
+
6
+ // C-12.4: --add-dir is a real command-line argument here, not documentation.
7
+ spawnSync('dsh', ['agent', '--add-dir', './docs'], { stdio: 'inherit' })
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/c12-command-arg",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": [], "labels": [] }
10
+ }
@@ -0,0 +1,3 @@
1
+ export const name = 'c12-comment'
2
+ export function apply() {}
3
+ // danger-full-access belongs in a comment only
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/c12-comment",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": [], "labels": [] }
10
+ }
@@ -0,0 +1,3 @@
1
+ export const name = 'c12-compare'
2
+ export function apply() {}
3
+ if (mode === 'danger-full-access') { /* comparison, not a grant */ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/c12-compare",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": [], "labels": [] }
10
+ }
@@ -0,0 +1,13 @@
1
+ export const name = 'c12-doc-string'
2
+ export function apply() {}
3
+
4
+ // Documentation, not a grant: the literals below are labels and help text.
5
+ const documentation = {
6
+ desc: 'danger-full-access',
7
+ description: '--add-dir',
8
+ help: 'danger-full-access',
9
+ }
10
+
11
+ console.log('danger-full-access')
12
+ console.info('--add-dir')
13
+ console.warn(`mode: ${'danger-full-access'}`)
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/c12-doc-string",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": [], "labels": [] }
10
+ }
@@ -0,0 +1,3 @@
1
+ export const name = 'c12-grant'
2
+ export function apply() {}
3
+ svc.set(session, 'danger-full-access')
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/c12-grant",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": [], "labels": [] }
10
+ }
@@ -0,0 +1,3 @@
1
+ export const name = 'c12-string'
2
+ export function apply() {}
3
+ console.log('use danger-full-access for this case')
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/c12-string",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": [], "labels": [] }
10
+ }
@@ -0,0 +1 @@
1
+ export declare function broken(: string
@@ -0,0 +1,9 @@
1
+ export const name = 'fixture-declaration-file'
2
+ export const inject = ['sessions']
3
+ export const provide = ['declaration-file']
4
+
5
+ export function apply(ctx) {
6
+ const sessions = ctx.sessions
7
+ ctx.provide('declaration-file', { hasSessions: Boolean(sessions) })
8
+ ctx.effect(() => () => {}, 'fixture.declaration-file.effect')
9
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/declaration-file",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": ["sessions:read"], "labels": [] }
10
+ }
@@ -0,0 +1,11 @@
1
+ import './side-effectful.mjs'
2
+
3
+ export const name = 'fixture-import-local-side-effect'
4
+ export const inject = ['sessions']
5
+ export const provide = ['import-local-side-effect']
6
+
7
+ export function apply(ctx) {
8
+ const sessions = ctx.sessions
9
+ ctx.provide('import-local-side-effect', { hasSessions: Boolean(sessions) })
10
+ ctx.effect(() => () => {}, 'fixture.import-local-side-effect.effect')
11
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@dsh-fixtures/import-local-side-effect",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "engines": {
11
+ "dsh": ">=0.2.2-alpha.2 <0.3.0"
12
+ },
13
+ "dsh": {
14
+ "manifestVersion": 1,
15
+ "kind": "plugin",
16
+ "capabilities": [
17
+ "sessions:read"
18
+ ],
19
+ "labels": []
20
+ }
21
+ }
@@ -0,0 +1,5 @@
1
+ import { writeFileSync } from 'node:fs'
2
+
3
+ // C-10.3: the entry imports this module with a relative specifier, so its
4
+ // module-scope write executes on import of the entry.
5
+ writeFileSync('/tmp/dsh-fixture-import-local-side-effect.txt', 'x')
@@ -0,0 +1,14 @@
1
+ import { openSync } from 'node:fs'
2
+
3
+ export const name = 'fixture-import-open-readonly'
4
+ export const inject = ['sessions']
5
+ export const provide = ['import-open-readonly']
6
+
7
+ export function apply(ctx) {
8
+ const sessions = ctx.sessions
9
+ ctx.provide('import-open-readonly', { hasSessions: Boolean(sessions) })
10
+ ctx.effect(() => () => {}, 'fixture.import-open-readonly.effect')
11
+ }
12
+
13
+ // C-10.4 is about fs *writes*; a read-only open is a different side effect.
14
+ openSync(new URL(import.meta.url), 'r')
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@dsh-fixtures/import-open-readonly",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "engines": {
11
+ "dsh": ">=0.2.2-alpha.2 <0.3.0"
12
+ },
13
+ "dsh": {
14
+ "manifestVersion": 1,
15
+ "kind": "plugin",
16
+ "capabilities": ["sessions:read"],
17
+ "labels": []
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ import path from 'node:path'
2
+
3
+ export const name = 'fixture-import-package-only'
4
+ export const inject = ['sessions']
5
+ export const provide = ['import-package-only']
6
+
7
+ export function apply(ctx) {
8
+ const sessions = ctx.sessions
9
+ ctx.provide('import-package-only', { basename: path.basename('.'), hasSessions: Boolean(sessions) })
10
+ ctx.effect(() => () => {}, 'fixture.import-package-only.effect')
11
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@dsh-fixtures/import-package-only",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "engines": {
11
+ "dsh": ">=0.2.2-alpha.2 <0.3.0"
12
+ },
13
+ "dsh": {
14
+ "manifestVersion": 1,
15
+ "kind": "plugin",
16
+ "capabilities": [
17
+ "sessions:read"
18
+ ],
19
+ "labels": []
20
+ }
21
+ }
@@ -0,0 +1,5 @@
1
+ export const name = 'fixture-import-parse-error'
2
+ export function apply() {
3
+ // Deliberately invalid syntax: the AST check must report not-run, not PASS.
4
+ }
5
+ const broken = {
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/import-parse-error",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": [], "labels": [] }
10
+ }
@@ -0,0 +1,14 @@
1
+ import { writeFileSync } from 'node:fs'
2
+
3
+ export const name = 'fixture-import-side-effect'
4
+ export const inject = ['sessions']
5
+ export const provide = ['import-side-effect']
6
+
7
+ export function apply(ctx) {
8
+ const sessions = ctx.sessions
9
+ ctx.provide('import-side-effect', { hasSessions: Boolean(sessions) })
10
+ ctx.effect(() => () => {}, 'fixture.import-side-effect.effect')
11
+ }
12
+
13
+ // DSC-10.4: a module-scope fs mutation, executed on import.
14
+ writeFileSync('/tmp/dsh-fixture-import-side-effect.txt', 'x')
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@dsh-fixtures/import-side-effect",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "engines": {
11
+ "dsh": ">=0.2.2-alpha.2 <0.3.0"
12
+ },
13
+ "dsh": {
14
+ "manifestVersion": 1,
15
+ "kind": "plugin",
16
+ "capabilities": ["sessions:read"],
17
+ "labels": []
18
+ }
19
+ }
@@ -0,0 +1,9 @@
1
+ export const name = 'fixture-import-side-effect-nonentry'
2
+ export const inject = ['sessions']
3
+ export const provide = ['import-side-effect-nonentry']
4
+
5
+ export function apply(ctx) {
6
+ const sessions = ctx.sessions
7
+ ctx.provide('import-side-effect-nonentry', { hasSessions: Boolean(sessions) })
8
+ ctx.effect(() => () => {}, 'fixture.import-side-effect-nonentry.effect')
9
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@dsh-fixtures/import-side-effect-nonentry",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "engines": {
11
+ "dsh": ">=0.2.2-alpha.2 <0.3.0"
12
+ },
13
+ "dsh": {
14
+ "manifestVersion": 1,
15
+ "kind": "plugin",
16
+ "capabilities": ["sessions:read"],
17
+ "labels": []
18
+ }
19
+ }
@@ -0,0 +1,8 @@
1
+ // Preflight helper shipped with the package, never imported by the entry module.
2
+ // A module-scope fs mutation here is not an entry import-time side effect (C-10.3).
3
+ import { mkdirSync } from 'node:fs'
4
+
5
+ mkdirSync('/tmp/dsh-fixture-selfcheck', { recursive: true })
6
+
7
+ // Same class of module-scope work, but still outside the entry module.
8
+ globalThis.__dshFixtureSelfcheck = true
@@ -0,0 +1,16 @@
1
+ export const name = 'fixture-import-void-main'
2
+ export const inject = ['sessions']
3
+ export const provide = ['import-void-main']
4
+
5
+ export function apply(ctx) {
6
+ const sessions = ctx.sessions
7
+ ctx.provide('import-void-main', { hasSessions: Boolean(sessions) })
8
+ ctx.effect(() => () => {}, 'fixture.import-void-main.effect')
9
+ }
10
+
11
+ function main() {
12
+ return undefined
13
+ }
14
+
15
+ // C-10.2: an immediate module-scope call, expressed as `void main()`.
16
+ void main()
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@dsh-fixtures/import-void-main",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "engines": {
11
+ "dsh": ">=0.2.2-alpha.2 <0.3.0"
12
+ },
13
+ "dsh": {
14
+ "manifestVersion": 1,
15
+ "kind": "plugin",
16
+ "capabilities": ["sessions:read"],
17
+ "labels": []
18
+ }
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@dsh-fixtures/no-source",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": {
8
+ ".": "./index.mjs"
9
+ },
10
+ "engines": {
11
+ "dsh": ">=0.2.2-alpha.2 <0.3.0"
12
+ },
13
+ "dsh": {
14
+ "manifestVersion": 1,
15
+ "kind": "plugin",
16
+ "capabilities": ["sessions:read"],
17
+ "labels": []
18
+ }
19
+ }
@@ -0,0 +1,2 @@
1
+ export const name = 'fixture-parse-error-nonentry'
2
+ export function apply() {}
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@dsh-fixtures/parse-error-nonentry",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./index.mjs",
7
+ "exports": { ".": "./index.mjs" },
8
+ "engines": { "dsh": ">=0.2.2-alpha.2 <0.3.0" },
9
+ "dsh": { "manifestVersion": 1, "kind": "plugin", "capabilities": [], "labels": [] }
10
+ }
@@ -0,0 +1 @@
1
+ export const broken = {
@@ -14,6 +14,7 @@ import fs from 'node:fs'
14
14
  import os from 'node:os'
15
15
  import path from 'node:path'
16
16
  import { pathToFileURL } from 'node:url'
17
+ import { scrubProcessEnv } from '../src/sandbox-env.mjs'
17
18
 
18
19
  function parse(argv) {
19
20
  const options = { package: null, host: null, base: null, web: null, bare: null, json: false, timeout: 30_000 }
@@ -42,6 +43,8 @@ function withTimeout(promise, ms, label) {
42
43
  }
43
44
 
44
45
  async function main() {
46
+ // Boot happens in this process: drop secrets before any plugin import.
47
+ scrubProcessEnv()
45
48
  const options = parse(process.argv.slice(2))
46
49
  const pkg = path.resolve(options.package)
47
50
  const manifest = JSON.parse(fs.readFileSync(path.join(pkg, 'package.json'), 'utf8'))
@@ -58,6 +58,7 @@ import fs from 'node:fs'
58
58
  import os from 'node:os'
59
59
  import path from 'node:path'
60
60
  import { spawnSync } from 'node:child_process'
61
+ import { sanitizedEnv } from '../src/sandbox-env.mjs'
61
62
  import { fileURLToPath } from 'node:url'
62
63
 
63
64
  const HERE = path.dirname(fileURLToPath(import.meta.url))
@@ -153,16 +154,16 @@ function run(command, args, { cwd, timeout, env } = {}) {
153
154
  encoding: 'utf8',
154
155
  maxBuffer: 64 * 1024 * 1024,
155
156
  killSignal: 'SIGKILL',
156
- env: {
157
- ...process.env,
157
+ env: sanitizedEnv({
158
158
  NO_COLOR: '1',
159
159
  npm_config_update_notifier: 'false',
160
160
  // Bound registry waits so one unresponsive metadata request cannot wedge
161
161
  // a shard past its job timeout.
162
162
  npm_config_fetch_timeout: '60000',
163
163
  npm_config_fetch_retries: '2',
164
+ ...(process.env.DSH_HOME ? { DSH_HOME: process.env.DSH_HOME } : {}),
164
165
  ...env,
165
- },
166
+ }),
166
167
  })
167
168
  return {
168
169
  command,
@@ -17,6 +17,7 @@ import fs from 'node:fs'
17
17
  import os from 'node:os'
18
18
  import path from 'node:path'
19
19
  import { spawnSync } from 'node:child_process'
20
+ import { sanitizedEnv, scrubProcessEnv } from '../src/sandbox-env.mjs'
20
21
  import { fileURLToPath } from 'node:url'
21
22
 
22
23
  const HERE = path.dirname(fileURLToPath(import.meta.url))
@@ -87,7 +88,7 @@ function run(command, args, { cwd, timeout, env } = {}) {
87
88
  timeout,
88
89
  encoding: 'utf8',
89
90
  maxBuffer: 64 * 1024 * 1024,
90
- env: { ...process.env, NO_COLOR: '1', ...env },
91
+ env: sanitizedEnv({ NO_COLOR: '1', ...(process.env.DSH_HOME ? { DSH_HOME: process.env.DSH_HOME } : {}), ...env }),
91
92
  })
92
93
  return {
93
94
  status: result.status,
@@ -159,6 +160,8 @@ function targetRows(pkgDir, manifest, loadOverlayPatches, composeEntries) {
159
160
 
160
161
  async function main() {
161
162
  const options = parseArgs(process.argv.slice(2))
163
+ // This harness boots the sample in-process; drop secrets before any import.
164
+ scrubProcessEnv()
162
165
  const startedAt = new Date().toISOString()
163
166
  const work = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-entry18-web-'))
164
167
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-entry18-home-'))