pdd-skills 3.0.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 (261) hide show
  1. package/README.md +1478 -0
  2. package/bin/pdd.js +354 -0
  3. package/config/bpmn-rules.yaml +166 -0
  4. package/config/checkstyle.xml +105 -0
  5. package/config/eslint.config.js +48 -0
  6. package/config/pmd.xml +91 -0
  7. package/config/prd-rules.yaml +113 -0
  8. package/config/ruff.toml +45 -0
  9. package/config/sqlfluff.cfg +82 -0
  10. package/hooks/hook-executor.js +332 -0
  11. package/index.js +43 -0
  12. package/lib/api-routes.js +750 -0
  13. package/lib/api-server.js +408 -0
  14. package/lib/cache/cache-config.js +209 -0
  15. package/lib/cache/system-cache.js +852 -0
  16. package/lib/config-manager.js +373 -0
  17. package/lib/generate.js +528 -0
  18. package/lib/grpc/grpc-routes.js +1134 -0
  19. package/lib/grpc/grpc-server.js +912 -0
  20. package/lib/grpc/proto-definitions.js +1033 -0
  21. package/lib/init.js +172 -0
  22. package/lib/iteration/auto-fixer.js +1025 -0
  23. package/lib/iteration/auto-reviewer.js +923 -0
  24. package/lib/iteration/controller.js +577 -0
  25. package/lib/list.js +130 -0
  26. package/lib/mcp-server.js +548 -0
  27. package/lib/openclaw/api-integration.js +535 -0
  28. package/lib/openclaw/cli-integration.js +567 -0
  29. package/lib/openclaw/data-sync.js +845 -0
  30. package/lib/openclaw/openclaw-adapter.js +783 -0
  31. package/lib/plugin/example-plugins/code-stats/index.js +332 -0
  32. package/lib/plugin/example-plugins/code-stats/plugin.json +1 -0
  33. package/lib/plugin/example-plugins/custom-linter/index.js +472 -0
  34. package/lib/plugin/example-plugins/custom-linter/plugin.json +1 -0
  35. package/lib/plugin/example-plugins/hello-world/index.js +86 -0
  36. package/lib/plugin/example-plugins/hello-world/plugin.json +1 -0
  37. package/lib/plugin/plugin-manager.js +655 -0
  38. package/lib/plugin/plugin-sdk.js +565 -0
  39. package/lib/plugin/sandbox.js +627 -0
  40. package/lib/quality/rules/maintainability.js +418 -0
  41. package/lib/quality/rules/performance.js +498 -0
  42. package/lib/quality/rules/readability.js +441 -0
  43. package/lib/quality/rules/robustness.js +504 -0
  44. package/lib/quality/rules/security.js +444 -0
  45. package/lib/quality/scorer.js +576 -0
  46. package/lib/report.js +669 -0
  47. package/lib/sdk-base.js +301 -0
  48. package/lib/sdk-js.js +446 -0
  49. package/lib/sdk-python/README.md +546 -0
  50. package/lib/sdk-python/examples/basic_usage.py +450 -0
  51. package/lib/sdk-python/pdd_sdk/__init__.py +180 -0
  52. package/lib/sdk-python/pdd_sdk/client.py +1170 -0
  53. package/lib/sdk-python/pdd_sdk/events.py +423 -0
  54. package/lib/sdk-python/pdd_sdk/exceptions.py +158 -0
  55. package/lib/sdk-python/pdd_sdk/models.py +518 -0
  56. package/lib/sdk-python/pdd_sdk/utils.py +759 -0
  57. package/lib/token/budget-alert.js +367 -0
  58. package/lib/token/budget-manager.js +485 -0
  59. package/lib/update.js +54 -0
  60. package/lib/utils/logger.js +88 -0
  61. package/lib/verify.js +741 -0
  62. package/lib/version.js +52 -0
  63. package/lib/vm/README.md +102 -0
  64. package/lib/vm/dashboard/api-routes.js +669 -0
  65. package/lib/vm/dashboard/server.js +391 -0
  66. package/lib/vm/dashboard/sse.js +358 -0
  67. package/lib/vm/dashboard/static/css/dashboard.css +1378 -0
  68. package/lib/vm/dashboard/static/index.html +118 -0
  69. package/lib/vm/dashboard/static/js/app.js +949 -0
  70. package/lib/vm/dashboard/static/js/charts.js +913 -0
  71. package/lib/vm/dashboard/static/js/kanban-view.js +1053 -0
  72. package/lib/vm/dashboard/static/js/pipeline-view.js +463 -0
  73. package/lib/vm/dashboard/static/js/quality-view.js +598 -0
  74. package/lib/vm/dashboard/static/js/system-view.js +1021 -0
  75. package/lib/vm/data-provider.js +1191 -0
  76. package/lib/vm/event-bus.js +402 -0
  77. package/lib/vm/hooks/extract-hook.js +307 -0
  78. package/lib/vm/hooks/generate-hook.js +374 -0
  79. package/lib/vm/hooks/hook-interface.js +458 -0
  80. package/lib/vm/hooks/report-hook.js +331 -0
  81. package/lib/vm/hooks/verify-hook.js +454 -0
  82. package/lib/vm/models.js +1003 -0
  83. package/lib/vm/reconciler.js +855 -0
  84. package/lib/vm/scanner.js +988 -0
  85. package/lib/vm/state-schema.js +955 -0
  86. package/lib/vm/state-store.js +733 -0
  87. package/lib/vm/tui/components/card.js +339 -0
  88. package/lib/vm/tui/components/progress-bar.js +368 -0
  89. package/lib/vm/tui/components/sparkline.js +327 -0
  90. package/lib/vm/tui/components/status-light.js +294 -0
  91. package/lib/vm/tui/components/table.js +370 -0
  92. package/lib/vm/tui/input.js +335 -0
  93. package/lib/vm/tui/renderer.js +548 -0
  94. package/lib/vm/tui/screens/kanban-screen.js +397 -0
  95. package/lib/vm/tui/screens/overview-screen.js +357 -0
  96. package/lib/vm/tui/screens/quality-screen.js +336 -0
  97. package/lib/vm/tui/screens/system-screen.js +379 -0
  98. package/lib/vm/tui/tui.js +805 -0
  99. package/package.json +1 -0
  100. package/scripts/cso-analyzer.js +198 -0
  101. package/scripts/eval-runner.js +359 -0
  102. package/scripts/i18n-checker.js +109 -0
  103. package/scripts/linter/activiti-linter.js +272 -0
  104. package/scripts/linter/prd-linter.js +162 -0
  105. package/scripts/linter/report-generator.js +207 -0
  106. package/scripts/linter/run-linters.js +285 -0
  107. package/scripts/linter/sql-linter.js +166 -0
  108. package/scripts/token-analyzer.js +162 -0
  109. package/scripts/vm-test.js +180 -0
  110. package/skills/core/official-doc-writer/LICENSE +21 -0
  111. package/skills/core/official-doc-writer/README.md +232 -0
  112. package/skills/core/official-doc-writer/SKILL.md +475 -0
  113. package/skills/core/official-doc-writer/_meta.json +1 -0
  114. package/skills/core/official-doc-writer/document_generator.py +580 -0
  115. package/skills/core/official-doc-writer/evals/default-evals.json +1 -0
  116. package/skills/core/official-doc-writer/examples.md +150 -0
  117. package/skills/core/official-doc-writer/fonts/FONTS_LIST.md +45 -0
  118. package/skills/core/official-doc-writer/fonts/README.md +141 -0
  119. package/skills/core/official-doc-writer/fonts/SIMFANG.TTF +0 -0
  120. package/skills/core/official-doc-writer/fonts/SIMHEI.TTF +0 -0
  121. package/skills/core/official-doc-writer/fonts/SIMKAI.TTF +0 -0
  122. package/skills/core/official-doc-writer/fonts/SIMSUN.TTC +0 -0
  123. package/skills/core/official-doc-writer/fonts//346/226/271/346/255/243/345/260/217/346/240/207/345/256/213GBK.TTF +0 -0
  124. package/skills/core/official-doc-writer/references/GBT_9704-2012_/345/205/232/346/224/277/346/234/272/345/205/263/345/205/254/346/226/207/346/240/274/345/274/217.md +422 -0
  125. package/skills/core/official-doc-writer/scripts/__pycache__/generate_official_doc.cpython-313.pyc +0 -0
  126. package/skills/core/official-doc-writer/scripts/dialog_manager.py +564 -0
  127. package/skills/core/official-doc-writer/scripts/generate_official_doc.py +252 -0
  128. package/skills/core/official-doc-writer/scripts/install_fonts.py +390 -0
  129. package/skills/core/official-doc-writer/scripts/smart_prompts.py +363 -0
  130. package/skills/core/pdd-ba/SKILL.md +305 -0
  131. package/skills/core/pdd-ba/_meta.json +1 -0
  132. package/skills/core/pdd-ba/evals/default-evals.json +1 -0
  133. package/skills/core/pdd-code-reviewer/SKILL.md +378 -0
  134. package/skills/core/pdd-code-reviewer/_meta.json +1 -0
  135. package/skills/core/pdd-code-reviewer/evals/default-evals.json +1 -0
  136. package/skills/core/pdd-doc-change/SKILL.md +350 -0
  137. package/skills/core/pdd-doc-change/_meta.json +1 -0
  138. package/skills/core/pdd-doc-change/evals/default-evals.json +1 -0
  139. package/skills/core/pdd-doc-gardener/SKILL.md +248 -0
  140. package/skills/core/pdd-doc-gardener/_meta.json +1 -0
  141. package/skills/core/pdd-doc-gardener/evals/default-evals.json +1 -0
  142. package/skills/core/pdd-entropy-reduction/SKILL.md +360 -0
  143. package/skills/core/pdd-entropy-reduction/_meta.json +1 -0
  144. package/skills/core/pdd-entropy-reduction/evals/default-evals.json +1 -0
  145. package/skills/core/pdd-entropy-reduction/references/entropy-report-template.md +287 -0
  146. package/skills/core/pdd-entropy-reduction/references/golden-principles.md +573 -0
  147. package/skills/core/pdd-entropy-reduction/scripts/entropy_scan.py +712 -0
  148. package/skills/core/pdd-extract-features/SKILL.md +320 -0
  149. package/skills/core/pdd-extract-features/_meta.json +1 -0
  150. package/skills/core/pdd-extract-features/evals/default-evals.json +1 -0
  151. package/skills/core/pdd-generate-spec/SKILL.md +418 -0
  152. package/skills/core/pdd-generate-spec/_meta.json +1 -0
  153. package/skills/core/pdd-generate-spec/evals/default-evals.json +1 -0
  154. package/skills/core/pdd-implement-feature/SKILL.md +332 -0
  155. package/skills/core/pdd-implement-feature/_meta.json +1 -0
  156. package/skills/core/pdd-implement-feature/evals/default-evals.json +1 -0
  157. package/skills/core/pdd-main/SKILL.md +540 -0
  158. package/skills/core/pdd-main/_meta.json +1 -0
  159. package/skills/core/pdd-main/evals/default-evals.json +1 -0
  160. package/skills/core/pdd-main/evals/evals.json +215 -0
  161. package/skills/core/pdd-verify-feature/SKILL.md +474 -0
  162. package/skills/core/pdd-verify-feature/_meta.json +1 -0
  163. package/skills/core/pdd-verify-feature/evals/default-evals.json +1 -0
  164. package/skills/core/pdd-vm/evals/default-evals.json +1 -0
  165. package/skills/core/traffic-accident-assessor/LICENSE +29 -0
  166. package/skills/core/traffic-accident-assessor/SKILL.md +439 -0
  167. package/skills/core/traffic-accident-assessor/evals/evals.json +1 -0
  168. package/skills/core/traffic-accident-assessor/references/accident-types.md +369 -0
  169. package/skills/core/traffic-accident-assessor/references/liability-rules.md +287 -0
  170. package/skills/core/traffic-accident-assessor/references/traffic-laws.md +226 -0
  171. package/skills/core/traffic-accident-assessor/references//351/253/230/345/260/224/345/244/253/350/257/264/346/230/216/344/271/246.pdf +32576 -106
  172. package/skills/core/traffic-accident-assessor/scripts/generate_official_statement.py +588 -0
  173. package/skills/core/traffic-accident-assessor/scripts/generate_report.py +495 -0
  174. package/skills/core/traffic-accident-assessor/scripts/generate_statement.py +528 -0
  175. package/skills/core/traffic-accident-assessor.zip +0 -0
  176. package/skills/entropy/expert-arch-enforcer/SKILL.md +292 -0
  177. package/skills/entropy/expert-arch-enforcer/_meta.json +1 -0
  178. package/skills/entropy/expert-arch-enforcer/evals/default-evals.json +1 -0
  179. package/skills/entropy/expert-auto-refactor/SKILL.md +327 -0
  180. package/skills/entropy/expert-auto-refactor/_meta.json +1 -0
  181. package/skills/entropy/expert-auto-refactor/evals/default-evals.json +1 -0
  182. package/skills/entropy/expert-code-quality/SKILL.md +468 -0
  183. package/skills/entropy/expert-code-quality/_meta.json +1 -0
  184. package/skills/entropy/expert-code-quality/evals/default-evals.json +1 -0
  185. package/skills/entropy/expert-code-quality/evals/evals.json +109 -0
  186. package/skills/entropy/expert-code-quality/references/code-smells.md +605 -0
  187. package/skills/entropy/expert-code-quality/references/design-patterns.md +1111 -0
  188. package/skills/entropy/expert-code-quality/references/refactoring-catalog.md +1281 -0
  189. package/skills/entropy/expert-code-quality/references/solid-principles.md +524 -0
  190. package/skills/entropy/expert-entropy-auditor/SKILL.md +276 -0
  191. package/skills/entropy/expert-entropy-auditor/_meta.json +1 -0
  192. package/skills/entropy/expert-entropy-auditor/evals/default-evals.json +1 -0
  193. package/skills/expert/expert-activiti/SKILL.md +497 -0
  194. package/skills/expert/expert-activiti/_meta.json +1 -0
  195. package/skills/expert/expert-mysql/SKILL.md +832 -0
  196. package/skills/expert/expert-mysql/_meta.json +1 -0
  197. package/skills/expert/expert-performance/SKILL.md +379 -0
  198. package/skills/expert/expert-performance/_meta.json +1 -0
  199. package/skills/expert/expert-performance/evals/default-evals.json +1 -0
  200. package/skills/expert/expert-ruoyi/SKILL.md +472 -0
  201. package/skills/expert/expert-ruoyi/_meta.json +1 -0
  202. package/skills/expert/expert-security/SKILL.md +1341 -0
  203. package/skills/expert/expert-security/_meta.json +1 -0
  204. package/skills/expert/expert-security/evals/default-evals.json +1 -0
  205. package/skills/expert/software-architect/SKILL.md +350 -0
  206. package/skills/expert/software-architect/_meta.json +1 -0
  207. package/skills/expert/software-engineer/SKILL.md +437 -0
  208. package/skills/expert/software-engineer/_meta.json +1 -0
  209. package/skills/expert/software-engineer/architecture.md +130 -0
  210. package/skills/expert/software-engineer/patterns.md +151 -0
  211. package/skills/expert/software-engineer/testing.md +135 -0
  212. package/skills/expert/system-architect/SKILL.md +628 -0
  213. package/skills/expert/system-architect/_meta.json +1 -0
  214. package/skills/expert/system-architect/assets/templates/ARCHITECTURE.md +25 -0
  215. package/skills/expert/system-architect/assets/templates/README.md +44 -0
  216. package/skills/expert/system-architect/references/js-ts-standards.md +18 -0
  217. package/skills/expert/system-architect/references/python-standards.md +19 -0
  218. package/skills/expert/system-architect/references/scaffolding.md +61 -0
  219. package/skills/expert/system-architect/references/security-checklist.md +21 -0
  220. package/skills/openspec/openspec-apply-change/SKILL.md +156 -0
  221. package/skills/openspec/openspec-apply-change/_meta.json +1 -0
  222. package/skills/openspec/openspec-archive-change/SKILL.md +114 -0
  223. package/skills/openspec/openspec-archive-change/_meta.json +1 -0
  224. package/skills/openspec/openspec-bulk-archive-change/SKILL.md +246 -0
  225. package/skills/openspec/openspec-bulk-archive-change/_meta.json +1 -0
  226. package/skills/openspec/openspec-continue-change/SKILL.md +118 -0
  227. package/skills/openspec/openspec-continue-change/_meta.json +1 -0
  228. package/skills/openspec/openspec-explore/SKILL.md +288 -0
  229. package/skills/openspec/openspec-explore/_meta.json +1 -0
  230. package/skills/openspec/openspec-ff-change/SKILL.md +101 -0
  231. package/skills/openspec/openspec-ff-change/_meta.json +1 -0
  232. package/skills/openspec/openspec-new-change/SKILL.md +74 -0
  233. package/skills/openspec/openspec-new-change/_meta.json +1 -0
  234. package/skills/openspec/openspec-onboard/SKILL.md +554 -0
  235. package/skills/openspec/openspec-onboard/_meta.json +1 -0
  236. package/skills/openspec/openspec-sync-specs/SKILL.md +138 -0
  237. package/skills/openspec/openspec-sync-specs/_meta.json +1 -0
  238. package/skills/openspec/openspec-verify-change/SKILL.md +168 -0
  239. package/skills/openspec/openspec-verify-change/_meta.json +1 -0
  240. package/skills/pr/pdd-multi-review/SKILL.md +534 -0
  241. package/skills/pr/pdd-multi-review/_meta.json +1 -0
  242. package/skills/pr/pdd-pr-batch/SKILL.md +303 -0
  243. package/skills/pr/pdd-pr-batch/_meta.json +1 -0
  244. package/skills/pr/pdd-pr-create/SKILL.md +344 -0
  245. package/skills/pr/pdd-pr-create/_meta.json +1 -0
  246. package/skills/pr/pdd-pr-merge/SKILL.md +286 -0
  247. package/skills/pr/pdd-pr-merge/_meta.json +1 -0
  248. package/skills/pr/pdd-pr-review/SKILL.md +217 -0
  249. package/skills/pr/pdd-pr-review/_meta.json +1 -0
  250. package/skills/pr/pdd-task-manager/SKILL.md +636 -0
  251. package/skills/pr/pdd-task-manager/_meta.json +1 -0
  252. package/skills/pr/pdd-template-engine/SKILL.md +306 -0
  253. package/skills/pr/pdd-template-engine/_meta.json +1 -0
  254. package/templates/behavior-shaping/iron-law-template.md +87 -0
  255. package/templates/behavior-shaping/rationalization-template.md +62 -0
  256. package/templates/behavior-shaping/red-flags-template.md +70 -0
  257. package/templates/bilingual-template.md +139 -0
  258. package/templates/config/default.yaml +47 -0
  259. package/templates/project/default/README.md +31 -0
  260. package/templates/project/frontend/README.md +46 -0
  261. package/templates/project/java/README.md +48 -0
@@ -0,0 +1,627 @@
1
+ /**
2
+ * PDD Plugin Sandbox - 插件安全沙箱
3
+ *
4
+ * 为每个插件提供隔离的执行环境,限制文件系统、网络、
5
+ * CPU/内存等资源访问,防止恶意或失控插件影响宿主系统。
6
+ *
7
+ * 核心安全策略:
8
+ * - 文件系统白名单:仅允许访问指定目录
9
+ * - 网络域名白名单:仅允许请求指定域名
10
+ * - 资源限制:最大内存和执行超时时间
11
+ * - 全局变量代理:受控访问 process/env 等
12
+ * - API 审计日志:记录所有敏感操作
13
+ *
14
+ * @module sandbox
15
+ * @author PDD Team
16
+ * @version 1.0.0
17
+ * @license MIT
18
+ */
19
+
20
+ import { readFile, writeFile, stat, readdir, access } from 'node:fs/promises';
21
+ import { join, resolve, isAbsolute, relative } from 'node:path';
22
+
23
+ // ==================== SandboxPolicy 策略配置 ====================
24
+
25
+ /**
26
+ * 沙箱策略配置类
27
+ * 定义插件执行时的各项安全约束
28
+ *
29
+ * @class SandboxPolicy
30
+ *
31
+ * @example
32
+ * const policy = new SandboxPolicy({
33
+ * filesystem: { allowRead: true, allowWrite: false, rootDir: '/safe/dir' },
34
+ * network: { allowDomains: ['api.example.com'] },
35
+ * execution: { maxMemoryMB: 256, timeoutMs: 5000 }
36
+ * });
37
+ */
38
+ export class SandboxPolicy {
39
+ /**
40
+ * 创建沙箱策略实例
41
+ * @param {Object} options - 策略选项
42
+ * @param {Object} [options.filesystem={}] - 文件系统策略
43
+ * @param {boolean} [options.filesystem.allowRead=true] - 是否允许读取文件
44
+ * @param {boolean} [options.filesystem.allowWrite=false] - 是否允许写入文件
45
+ * @param {string} [options.filesystem.rootDir=''] - 文件系统根目录(空表示不限制)
46
+ * @param {string[]} [options.filesystem.allowExtensions=[]] - 允许的文件扩展名(空表示不限制)
47
+ * @param {Object} [options.network={}] - 网络访问策略
48
+ * @param {string[]} [options.network.allowDomains=[]] - 允许访问的域名白名单(空表示禁止所有网络)
49
+ * @param {number} [options.network.maxRequests=100] - 最大请求数量(0 表示无限制)
50
+ * @param {Object} [options.execution={}] - 执行资源策略
51
+ * @param {number} [options.execution.maxMemoryMB=512] - 最大内存使用(MB)
52
+ * @param {number} [options.execution.timeoutMs=30000] - 单次操作超时时间(毫秒)
53
+ * @param {number} [options.execution.maxCpuTime=0] - 最大 CPU 时间(毫秒,0 表示不限制)
54
+ * @param {Object} [options.environment={}] - 环境变量策略
55
+ * @param {string[]} [options.environment.allowedEnvVars=[]] - 允许读取的环境变量名列表
56
+ * @param {boolean} [options.auditEnabled=true] - 是否启用审计日志
57
+ */
58
+ constructor(options = {}) {
59
+ const fs = options.filesystem || {};
60
+ const net = options.network || {};
61
+ const exec = options.execution || {};
62
+ const env = options.environment || {};
63
+
64
+ /** @type {Object} 文件系统策略 */
65
+ this.filesystem = {
66
+ allowRead: fs.allowRead !== false,
67
+ allowWrite: !!fs.allowWrite,
68
+ rootDir: fs.rootDir ? resolve(fs.rootDir) : '',
69
+ allowExtensions: fs.allowExtensions || [],
70
+ };
71
+
72
+ /** @type {Object} 网络访问策略 */
73
+ this.network = {
74
+ allowDomains: net.allowDomains || [],
75
+ maxRequests: net.maxRequests ?? 100,
76
+ };
77
+
78
+ /** @type {Object} 执行资源策略 */
79
+ this.execution = {
80
+ maxMemoryMB: exec.maxMemoryMB || 512,
81
+ timeoutMs: exec.timeoutMs ?? 30000,
82
+ maxCpuTime: exec.maxCpuTime || 0,
83
+ };
84
+
85
+ /** @type {Object} 环境变量策略 */
86
+ this.environment = {
87
+ allowedEnvVars: env.allowedEnvVars || [],
88
+ };
89
+
90
+ /** @type {boolean} 是否启用审计 */
91
+ this.auditEnabled = options.auditEnabled !== false;
92
+ }
93
+
94
+ /**
95
+ * 将策略序列化为可存储的 JSON 对象
96
+ * @returns {Object}
97
+ */
98
+ toJSON() {
99
+ return {
100
+ filesystem: { ...this.filesystem },
101
+ network: { ...this.network },
102
+ execution: { ...this.execution },
103
+ environment: { ...this.environment },
104
+ auditEnabled: this.auditEnabled,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * 从 JSON 对象恢复策略实例
110
+ * @param {Object} data - 序列化的策略数据
111
+ * @returns {SandboxPolicy}
112
+ */
113
+ static fromJSON(data) {
114
+ return new SandboxPolicy(data);
115
+ }
116
+ }
117
+
118
+ // ==================== AuditLog 审计日志 ====================
119
+
120
+ /**
121
+ * 沙箱审计日志条目
122
+ * @typedef {Object} AuditEntry
123
+ * @property {number} timestamp - 时间戳
124
+ * @property {string} action - 操作类型 (fs.read / fs.write / net.request / env.access)
125
+ * @property {string} target - 操作目标(路径或域名)
126
+ * @property {'allowed'|'denied'} result - 结果
127
+ * @property {string} [reason] - 拒绝原因
128
+ */
129
+
130
+ /**
131
+ * 审计日志收集器
132
+ * @class AuditLogger
133
+ */
134
+ class AuditLogger {
135
+ constructor(pluginName) {
136
+ /** @type {string} 关联的插件名称 */
137
+ this.pluginName = pluginName;
138
+ /** @type {AuditEntry[]} 日志条目 */
139
+ this.entries = [];
140
+ /** @type {number} 最大保留条数 */
141
+ this.maxEntries = 10000;
142
+ }
143
+
144
+ /**
145
+ * 记录一条审计日志
146
+ * @param {string} action - 操作类型
147
+ * @param {string} target - 操作目标
148
+ * @param {'allowed'|'denied'} result - 结果
149
+ * @param {string} [reason] - 原因
150
+ */
151
+ log(action, target, result, reason) {
152
+ const entry = {
153
+ timestamp: Date.now(),
154
+ action,
155
+ target,
156
+ result,
157
+ reason: reason || undefined,
158
+ };
159
+
160
+ this.entries.push(entry);
161
+
162
+ // 防止内存泄漏:超出上限时移除旧条目
163
+ if (this.entries.length > this.maxEntries) {
164
+ this.entries = this.entries.slice(-Math.floor(this.maxEntries / 2));
165
+ }
166
+ }
167
+
168
+ /**
169
+ * 获取所有日志
170
+ * @returns {AuditEntry[]}
171
+ */
172
+ getLogs() {
173
+ return [...this.entries];
174
+ }
175
+
176
+ /**
177
+ * 获取被拒绝的操作数量
178
+ * @returns {number}
179
+ */
180
+ getDeniedCount() {
181
+ return this.entries.filter(e => e.result === 'denied').length;
182
+ }
183
+
184
+ /**
185
+ * 清空日志
186
+ */
187
+ clear() {
188
+ this.entries = [];
189
+ }
190
+ }
191
+
192
+ // ==================== Sandbox 主类 ====================
193
+
194
+ /**
195
+ * 插件安全沙箱
196
+ *
197
+ * 为插件提供受限的运行环境,通过 Proxy 代理对危险 API 的访问。
198
+ * 所有文件系统、网络和环境变量的访问都经过策略检查。
199
+ *
200
+ * 注意:Node.js 的 Worker Threads 或 VM 可提供更强的隔离能力,
201
+ * 本实现侧重于 API 层面的访问控制和审计追踪。
202
+ *
203
+ * @class Sandbox
204
+ *
205
+ * @example
206
+ * import { createSandbox, SandboxPolicy } from './sandbox.js';
207
+ *
208
+ * const policy = new SandboxPolicy({
209
+ * filesystem: { rootDir: '/tmp/plugins/my-plugin' },
210
+ * network: { allowDomains: [] }, // 禁止网络
211
+ * });
212
+ *
213
+ * const sandbox = createSandbox('my-plugin', policy);
214
+ * await sandbox.fs.readFile('/tmp/plugins/my-plugin/config.json');
215
+ */
216
+ export class Sandbox {
217
+ /**
218
+ * 创建沙箱实例
219
+ * @param {string} pluginName - 插件名称(用于审计标识)
220
+ * @param {SandboxPolicy} policy - 安全策略
221
+ */
222
+ constructor(pluginName, policy) {
223
+ if (!policy || !(policy instanceof SandboxPolicy)) {
224
+ throw new Error('Sandbox 需要一个有效的 SandboxPolicy 实例');
225
+ }
226
+
227
+ /** @type {string} 插件名称 */
228
+ this.pluginName = pluginName;
229
+
230
+ /** @type {SandboxPolicy} 安全策略 */
231
+ this.policy = policy;
232
+
233
+ /** @type {AuditLogger} 审计日志器 */
234
+ this._audit = new AuditLogger(pluginName);
235
+
236
+ /** @type {number} 网络请求计数器 */
237
+ this._requestCount = 0;
238
+
239
+ // 初始化受控 API
240
+ this.fs = this._createFsProxy();
241
+ this.net = this._createNetProxy();
242
+ this.env = this._createEnvProxy();
243
+
244
+ // 内存监控定时器
245
+ this._memoryMonitor = null;
246
+ this._startMemoryMonitoring();
247
+ }
248
+
249
+ // ==================== 文件系统代理 ====================
250
+
251
+ /**
252
+ * 创建文件系统访问代理
253
+ * 拦截所有文件操作并检查路径是否在白名单内
254
+ * @returns {Proxy} 受控的文件系统对象
255
+ * @private
256
+ */
257
+ _createFsProxy() {
258
+ const self = this;
259
+ const policy = this.policy.filesystem;
260
+
261
+ /**
262
+ * 检查路径是否被允许访问
263
+ * @param {string} filePath - 待检查的路径
264
+ * @param {boolean} isWrite - 是否为写操作
265
+ * @returns {{ allowed: boolean, resolvedPath: string, reason?: string }}
266
+ */
267
+ const checkPath = (filePath, isWrite = false) => {
268
+ let resolvedPath;
269
+
270
+ try {
271
+ resolvedPath = resolve(filePath);
272
+ } catch {
273
+ return { allowed: false, resolvedPath: filePath, reason: '无效的文件路径' };
274
+ }
275
+
276
+ // 写权限检查
277
+ if (isWrite && !policy.allowWrite) {
278
+ return { allowed: false, resolvedPath, reason: '策略禁止文件写入' };
279
+ }
280
+
281
+ // 读权限检查
282
+ if (!isWrite && !policy.allowRead) {
283
+ return { allowed: false, resolvedPath, reason: '策略禁止文件读取' };
284
+ }
285
+
286
+ // 根目录限制
287
+ if (policy.rootDir) {
288
+ const rel = relative(policy.rootDir, resolvedPath);
289
+ if (rel.startsWith('..') || rel === filePath) {
290
+ return {
291
+ allowed: false,
292
+ resolvedPath,
293
+ reason: `路径 "${filePath}" 超出允许的根目录 ${policy.rootDir}`,
294
+ };
295
+ }
296
+ }
297
+
298
+ // 扩展名检查
299
+ if (policy.allowExtensions.length > 0) {
300
+ const ext = extname(resolvedPath).toLowerCase();
301
+ if (ext && !policy.allowExtensions.includes(ext)) {
302
+ return {
303
+ allowed: false,
304
+ resolvedPath,
305
+ reason: `文件扩展名 "${ext}" 不在允许列表中`,
306
+ };
307
+ }
308
+ }
309
+
310
+ return { allowed: true, resolvedPath };
311
+ };
312
+
313
+ return new Proxy(
314
+ {},
315
+ {
316
+ get(_, methodName) {
317
+ return async (...args) => {
318
+ const pathArg = args[0];
319
+
320
+ // 根据方法名判断是读还是写
321
+ const writeMethods = ['writeFile', 'appendFile', 'mkdir', 'rm', 'unlink'];
322
+ const isWrite = writeMethods.includes(methodName);
323
+
324
+ // 路径检查
325
+ if (typeof pathArg === 'string') {
326
+ const check = checkPath(pathArg, isWrite);
327
+
328
+ self._audit.log(`fs.${methodName}`, pathArg, check.allowed, check.reason);
329
+
330
+ if (!check.allowed) {
331
+ throw new Error(
332
+ `[Sandbox:${self.pluginName}] 文件访问被拒绝: ${check.reason}`
333
+ );
334
+ }
335
+
336
+ // 替换为解析后的绝对路径
337
+ args[0] = check.resolvedPath;
338
+ }
339
+
340
+ // 应用超时包装
341
+ return self._withTimeout(() => {
342
+ switch (methodName) {
343
+ case 'readFile':
344
+ return readFile(...args);
345
+ case 'writeFile':
346
+ return writeFile(...args);
347
+ case 'stat':
348
+ return stat(...args);
349
+ case 'readdir':
350
+ return readdir(...args);
351
+ case 'access':
352
+ return access(...args);
353
+ default:
354
+ throw new Error(
355
+ `[Sandbox] 不支持的文件系统方法: ${methodName}`
356
+ );
357
+ }
358
+ });
359
+ };
360
+ },
361
+ }
362
+ );
363
+ }
364
+
365
+ // ==================== 网络访问代理 ====================
366
+
367
+ /**
368
+ * 创建网络访问代理
369
+ * 限制可访问的域名和请求数量
370
+ * @returns {Proxy} 受控的网络对象
371
+ * @private
372
+ */
373
+ _createNetProxy() {
374
+ const self = this;
375
+ const policy = this.policy.network;
376
+
377
+ return new Proxy(
378
+ {},
379
+ {
380
+ get(_, methodName) {
381
+ return async (urlOrOptions, ...restArgs) => {
382
+ // 解析目标 URL
383
+ let targetUrl;
384
+ if (typeof urlOrOptions === 'string') {
385
+ targetUrl = urlOrOptions;
386
+ } else if (urlOrOptions?.hostname || urlOrOptions?.host) {
387
+ targetUrl = urlOrOptions.hostname || urlOrOptions.host;
388
+ } else {
389
+ throw new Error('[Sandbox] 无法确定网络请求的目标地址');
390
+ }
391
+
392
+ // 从 URL 中提取域名
393
+ let hostname = targetUrl;
394
+ try {
395
+ if (targetUrl.startsWith('http')) {
396
+ hostname = new URL(targetUrl).hostname;
397
+ }
398
+ } catch {
399
+ // 使用原始值
400
+ }
401
+
402
+ // 域名白名单检查
403
+ const isAllowed =
404
+ policy.allowDomains.length === 0
405
+ ? false // 空白名单 = 禁止所有
406
+ : policy.allowDomains.some(domain =>
407
+ hostname === domain || hostname.endsWith(`.${domain}`)
408
+ );
409
+
410
+ self._audit.log(`net.${methodName}`, hostname, isAllowed,
411
+ isAllowed ? undefined : `域名 "${hostname}" 不在白名单中`
412
+ );
413
+
414
+ if (!isAllowed) {
415
+ throw new Error(
416
+ `[Sandbox:${self.pluginName}] 网络访问被拒绝: 域名 "${hostname}" 未授权`
417
+ );
418
+ }
419
+
420
+ // 请求数量限制
421
+ if (policy.maxRequests > 0) {
422
+ self._requestCount++;
423
+ if (self._requestCount > policy.maxRequests) {
424
+ throw new Error(
425
+ `[Sandbox:${self.pluginName}] 网络请求数超限 (${policy.maxRequests})`
426
+ );
427
+ }
428
+ }
429
+
430
+ // 实际的网络请求需要调用方提供 fetch 实现
431
+ // 这里返回一个标记对象,由 PluginManager 注入真实 fetch
432
+ return self._withTimeout(async () => {
433
+ // 如果有注入的 fetch,使用它;否则抛出提示
434
+ if (self._fetchImpl) {
435
+ return self._fetchImpl(urlOrOptions, ...restArgs);
436
+ }
437
+ throw new Error(
438
+ '[Sandbox] 未注入 fetch 实现,无法完成网络请求。' +
439
+ '请在 PluginManager 中通过 sandbox.setFetch(fetchFn) 设置。'
440
+ );
441
+ });
442
+ };
443
+ },
444
+ }
445
+ );
446
+ }
447
+
448
+ // ==================== 环境变量代理 ====================
449
+
450
+ /**
451
+ * 创建环境变量访问代理
452
+ * 仅允许读取白名单中的环境变量
453
+ * @returns {Proxy} 受控的环境变量对象
454
+ * @private
455
+ */
456
+ _createEnvProxy() {
457
+ const self = this;
458
+ const allowedVars = this.policy.environment.allowedEnvVars;
459
+
460
+ return new Proxy(process.env, {
461
+ get(target, prop) {
462
+ if (typeof prop !== 'string') return undefined;
463
+
464
+ const isAllowed =
465
+ allowedVars.length === 0
466
+ ? false // 空白名单 = 禁止访问
467
+ : allowedVars.includes(prop);
468
+
469
+ self._audit.log('env.access', prop, isAllowed,
470
+ isAllowed ? undefined : `环境变量 "${prop}" 未在允许列表中`
471
+ );
472
+
473
+ if (!isAllowed) {
474
+ throw new Error(
475
+ `[Sandbox:${self.pluginName}] 环境变量访问被拒绝: "${prop}" 未授权`
476
+ );
477
+ }
478
+
479
+ return Reflect.get(target, prop);
480
+ },
481
+
482
+ set() {
483
+ throw new Error(
484
+ `[Sandbox:${self.pluginName}] 环境变量修改被拒绝`
485
+ );
486
+ },
487
+
488
+ ownKeys() {
489
+ // 只列出允许的环境变量键
490
+ return allowedVars.filter(key => key in process.env);
491
+ },
492
+
493
+ hasProperty(target, prop) {
494
+ return allowedVars.includes(prop) && prop in target;
495
+ },
496
+ });
497
+ }
498
+
499
+ // ==================== 超时与资源控制 ====================
500
+
501
+ /**
502
+ * 为异步函数添加超时控制
503
+ * @param {Function} fn - 要执行的异步函数
504
+ * @returns {Promise<*>} 函数结果
505
+ * @private
506
+ */
507
+ async _withTimeout(fn) {
508
+ const timeoutMs = this.policy.execution.timeoutMs;
509
+
510
+ if (timeoutMs <= 0) return fn();
511
+
512
+ return Promise.race([
513
+ fn(),
514
+ new Promise((_, reject) =>
515
+ setTimeout(() => reject(new Error(
516
+ `[Sandbox:${this.pluginName}] 操作超时 (${timeoutMs}ms)`
517
+ )), timeoutMs)
518
+ ),
519
+ ]);
520
+ }
521
+
522
+ /**
523
+ * 启动内存监控
524
+ * 定期检查当前进程内存使用情况
525
+ * @private
526
+ */
527
+ _startMemoryMonitoring() {
528
+ const intervalMs = 5000; // 每 5 秒检查一次
529
+ const maxBytes = this.policy.execution.maxMemoryMB * 1024 * 1024;
530
+
531
+ if (maxBytes <= 0) return; // 0 表示不限制
532
+
533
+ this._memoryMonitor = setInterval(() => {
534
+ const usage = process.memoryUsage();
535
+ if (usage.heapUsed > maxBytes) {
536
+ console.error(
537
+ `[Sandbox:${this.pluginName}] ` +
538
+ `内存超限: ${(usage.heapUsed / 1024 / 1024).toFixed(1)}MB / ` +
539
+ `${this.policy.execution.maxMemoryMB}MB`
540
+ );
541
+ // 注意:这里只做警告,不强制杀掉进程(因为 Node.js 单进程模型)
542
+ // 生产环境中建议配合 Worker Threads 实现真正的资源隔离
543
+ }
544
+ }, intervalMs).unref(); // unref 阻止定时器保持进程活跃
545
+ }
546
+
547
+ // ==================== 公共 API ====================
548
+
549
+ /**
550
+ * 设置网络请求实现(通常由 PluginManager 在初始化时注入)
551
+ * @param {Function} fetchFn - fetch 函数实现
552
+ */
553
+ setFetch(fetchFn) {
554
+ this._fetchImpl = fetchFn;
555
+ }
556
+
557
+ /**
558
+ * 获取审计日志
559
+ * @returns {AuditEntry[]}
560
+ */
561
+ getAuditLog() {
562
+ return this._audit.getLogs();
563
+ }
564
+
565
+ /**
566
+ * 被拒绝的操作计数
567
+ * @returns {number}
568
+ */
569
+ getDeniedCount() {
570
+ return this._audit.getDeniedCount();
571
+ }
572
+
573
+ /**
574
+ * 获取当前网络请求计数
575
+ * @returns {number}
576
+ */
577
+ getRequestCount() {
578
+ return this._requestCount;
579
+ }
580
+
581
+ /**
582
+ * 销毁沙箱,释放监控资源
583
+ */
584
+ destroy() {
585
+ if (this._memoryMonitor) {
586
+ clearInterval(this._memoryMonitor);
587
+ this._memoryMonitor = null;
588
+ }
589
+ this._audit.clear();
590
+ }
591
+ }
592
+
593
+ // ==================== 工厂函数 ====================
594
+
595
+ /**
596
+ * 创建沙箱实例的工厂函数
597
+ *
598
+ * @param {string} pluginName - 插件名称
599
+ * @param {SandboxPolicy} [policy] - 安全策略(不传则使用默认严格策略)
600
+ * @returns {Sandbox} 沙箱实例
601
+ *
602
+ * @example
603
+ * // 默认策略(最严格:禁止一切外部访问)
604
+ * const sandbox = createSandbox('my-plugin');
605
+ *
606
+ * // 自定义宽松策略
607
+ * const looseSandbox = createSandbox('my-plugin', new SandboxPolicy({
608
+ * filesystem: { allowRead: true, allowWrite: true, rootDir: './data' },
609
+ * network: { allowDomains: ['api.example.com'], maxRequests: 50 },
610
+ * execution: { maxMemoryMB: 1024, timeoutMs: 60000 },
611
+ * }));
612
+ */
613
+ export function createSandbox(pluginName, policy) {
614
+ // 默认策略:最严格的限制
615
+ const defaultPolicy = policy || new SandboxPolicy({
616
+ filesystem: { allowRead: true, allowWrite: false },
617
+ network: { allowDomains: [] }, // 禁止所有网络
618
+ execution: { maxMemoryMB: 256, timeoutMs: 10000 },
619
+ environment: { allowedEnvVars: [] }, // 禁止访问环境变量
620
+ });
621
+
622
+ return new Sandbox(pluginName, defaultPolicy);
623
+ }
624
+
625
+ // ==================== 导出 ====================
626
+
627
+ export default Sandbox;