keyshot-mcp 0.13.1 → 0.13.5

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/CHANGELOG.md CHANGED
@@ -4,6 +4,47 @@ All notable changes to KeyShot MCP are documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.13.5] - 2026-10-20
8
+
9
+ ### Installation and Diagnostics / 安装与诊断
10
+
11
+ - Isolate each bridge request in a private temporary subdirectory, clean it after execution, and return structured failures for local filesystem errors. Preserve completed diagnostic checks when startup throws; check temporary storage before launching KeyShot.
12
+ - 每次桥接请求使用独立临时子目录并在执行后清理;本地文件系统出错时返回结构化结果。启动抛异常时保留已完成的诊断,并在启动 KeyShot 前检查临时目录。
13
+
14
+ - Store bridge request/result files in the system temporary directory instead of the installed package, avoiding Windows path-length failures in nested installations.
15
+ - 将桥接请求与结果文件移到系统临时目录,避免 Windows 嵌套安装路径过长导致 KeyShot 无法写回结果。
16
+
17
+ ### Publication Privacy / 发布隐私
18
+
19
+ - Add a redacted public-content check to CI and publication, including credential
20
+ patterns, private paths, file names and PNG metadata. Reject unreviewed binary
21
+ assets; extend ignore rules for local environment, credential, scene and license files.
22
+ - 为 CI 与发布流程增加脱敏的公开内容检查,覆盖疑似凭据、私人路径、文件名及 PNG 元数据;
23
+ 拦截未经复核的二进制素材,补全本地环境、凭据、场景和许可证文件忽略规则。
24
+ - Bound preview file reads before allocation and while reading, reject special-file
25
+ output destinations, and check case variants and malformed PNG metadata containers.
26
+ - 预览读取在分配内存前和读取过程中均限制大小,拒绝特殊文件输出目标,并补查文件名
27
+ 大小写变体及损坏的 PNG 元数据容器。
28
+
29
+ ### Output Safety / 输出安全
30
+
31
+ - Apply default no-overwrite protection to single renders, queued jobs, imports,
32
+ scene edits and saves. Add optional `overwrite` to low-level writing tools without
33
+ renaming tools or removing inputs. Declared input files and linked aliases remain
34
+ protected even with overwrite enabled; scene and image outputs must be distinct.
35
+ - 将默认不覆盖保护统一到单张渲染、队列、导入、场景编辑与保存。底层写入工具增加可选
36
+ `overwrite`,不改工具名称或删除参数;即使允许覆盖,也不能替换本次声明的输入文件及其
37
+ 链接别名,场景与图片不能写入同一文件。
38
+ - Recheck every generated camera output inside the bridge, protect every queue input,
39
+ disambiguate selected-camera filenames and avoid deleting rejected preview outputs.
40
+ - bridge 在逐相机写入前重新检查路径,保护整个队列的输入场景,为清理后同名的指定相机
41
+ 生成不同文件名,并避免误删被拒绝的预览输出。
42
+
43
+ ### Documentation / 文档
44
+
45
+ - Clarify the roles of the MCP client's Agent, client configuration and model provider in privacy notices; distinguish client-managed transmission from the local KeyShot MCP service.
46
+ - 明确隐私提示中 MCP 客户端 Agent、客户端配置与模型服务的职责,区分客户端处理的数据传输与本机 KeyShot MCP 服务。
47
+
7
48
  ## [0.13.1] - 2026-10-15
8
49
 
9
50
  ### Release Safety / 发布安全
@@ -143,7 +184,8 @@ All notable changes to KeyShot MCP are documented in this file.
143
184
 
144
185
  - Documented that scene metadata and preview images may be sent by an MCP client to its configured model provider.
145
186
 
146
- [Unreleased]: https://github.com/truman-t3/keyshot-mcp/compare/v0.13.1...HEAD
187
+ [Unreleased]: https://github.com/truman-t3/keyshot-mcp/compare/v0.13.5...HEAD
188
+ [0.13.5]: https://github.com/truman-t3/keyshot-mcp/compare/v0.13.1...v0.13.5
147
189
  [0.13.1]: https://github.com/truman-t3/keyshot-mcp/compare/v0.13.0...v0.13.1
148
190
  [0.13.0]: https://github.com/truman-t3/keyshot-mcp/compare/v0.12.1...v0.13.0
149
191
  [0.12.1]: https://github.com/truman-t3/keyshot-mcp/compare/v0.12.0...v0.12.1
package/CONTRIBUTING.md CHANGED
@@ -22,6 +22,7 @@ Requirements: Node.js 20 or newer, pnpm 10, and Python 3.11 or newer.
22
22
  ```bash
23
23
  pnpm install --frozen-lockfile
24
24
  pnpm run check
25
+ pnpm run check:public-safety
25
26
  pnpm test
26
27
  python -m unittest discover -s tests -p "test_*.py"
27
28
  pnpm run format:check
@@ -38,6 +39,10 @@ reinspection, invalid-name rejection and an unsupported Wavefront MTL fixture.
38
39
  Optionally pass `--material-file "<native KeyShot MTL>"` to test a trusted local
39
40
  single-material file. The native file is read only, never copied into the repository.
40
41
  Tests use generated geometry and a new output subfolder, not user scenes.
42
+ Run `pnpm run smoke:keyshot:outputs` to check existing-file rejection, source hashes,
43
+ explicit replacement of a distinct copy and PNG output in a new test subfolder.
44
+ Use a fresh `KEYSHOT_OUTPUT_DIR` when repeating the full smoke test, which uses fixed names.
45
+ `pnpm docs:tools` also synchronizes the platform manifest's input schemas.
41
46
  CI on Linux validates the MCP and bridge logic; it does not verify KeyShot itself.
42
47
 
43
48
  ### Pull requests
@@ -54,6 +59,9 @@ Never include proprietary KeyShot scenes, license data, customer assets, credent
54
59
  private paths, or unpublished renders in Issues, Discussions, commits, or pull requests.
55
60
  Report vulnerabilities through GitHub's private security reporting form.
56
61
 
62
+ Use a GitHub no-reply commit email. Check filenames and image metadata as well as
63
+ visible content; new public images require review in `scripts/check-public-safety.mjs`.
64
+
57
65
  ## 中文
58
66
 
59
67
  欢迎设计师、开发者、技术美术和测试人员参与。比较有价值的贡献包括:
@@ -74,6 +82,7 @@ Report vulnerabilities through GitHub's private security reporting form.
74
82
  ```bash
75
83
  pnpm install --frozen-lockfile
76
84
  pnpm run check
85
+ pnpm run check:public-safety
77
86
  pnpm test
78
87
  python -m unittest discover -s tests -p "test_*.py"
79
88
  pnpm run format:check
@@ -89,6 +98,9 @@ KeyShot。
89
98
  运行 `pnpm run smoke:keyshot:materials` 可验证材质预设、保存后复查、无效名称及不支持的
90
99
  Wavefront MTL 拒绝;可选传入 `--material-file "<KeyShot 原生 MTL>"` 检查可信本地单材质文件。
91
100
  原生文件只读,不复制进仓库;测试仅使用生成模型和新输出子目录,不覆盖用户场景。
101
+ 运行 `pnpm run smoke:keyshot:outputs` 可验证重名拒绝、源文件哈希、明确覆盖另一份副本及
102
+ PNG 输出,文件保存在新测试子目录。完整 smoke 使用固定文件名,重复运行时请设置新的
103
+ `KEYSHOT_OUTPUT_DIR`。`pnpm docs:tools` 同时同步平台清单中的输入 Schema。
92
104
 
93
105
  ### Pull Request
94
106
 
@@ -100,3 +112,6 @@ Wavefront MTL 拒绝;可选传入 `--material-file "<KeyShot 原生 MTL>"` 检
100
112
 
101
113
  不要在 Issue、Discussion、提交或 PR 中包含专有 KeyShot 场景、许可证数据、客户资产、
102
114
  账号凭据、私人路径或未公开渲染图。安全漏洞请通过 GitHub 私密安全报告提交。
115
+
116
+ 提交使用 GitHub no-reply 邮箱;除可见内容外,也检查文件名和图片元数据。
117
+ 新增公开图片需要在 `scripts/check-public-safety.mjs` 中完成复核登记。
package/README.md CHANGED
@@ -44,9 +44,14 @@
44
44
  > computer as KeyShot. It does not read unsaved edits or control the open KeyShot window.
45
45
  > **处理已保存的场景,不是实时 GUI 控制。** 服务在本机后台运行 KeyShot,不能读取窗口中尚未保存的修改。
46
46
  >
47
- > File processing is local, but your client may send scene metadata and previews
48
- > to its model provider. Review [privacy and security](SECURITY.md) before confidential work.
49
- > 文件处理在本机完成,但客户端可能把场景信息和预览发送给模型服务;保密项目请先阅读[隐私与安全说明](SECURITY.md)。
47
+ > **Client Agent and privacy.** The Agent in your MCP client receives scene information
48
+ > and previews returned by tools. Depending on the client's implementation and settings,
49
+ > these results may be sent to its configured model provider. KeyShot MCP itself does
50
+ > not upload them. Review [privacy and security](SECURITY.md) before confidential work.
51
+ >
52
+ > **客户端 Agent 与隐私。** MCP 客户端中的 Agent 会接收工具返回的场景信息和预览;
53
+ > 这些内容可能根据客户端的实现与配置发送给其所选模型服务。KeyShot MCP 本身不主动上传这些内容。
54
+ > 保密项目请先阅读[隐私与安全说明](SECURITY.md)。
50
55
 
51
56
  ## From a Request to a Render / 一句话到出图
52
57
 
@@ -111,11 +116,12 @@ licensed KeyShot edition with headless scripting. **Windows 11 + KeyShot Studio
111
116
  2025 / KeyShot 14.1 is tested.** Other systems and KeyShot versions are not verified.
112
117
  KeyShot software, licenses and library assets are not included.
113
118
 
114
- <!-- release-version: 0.13.1 -->
119
+ <!-- release-version: 0.13.5 -->
115
120
 
116
- **Version 0.13.1** includes verified material assignments, clearer material-file
117
- errors and the updated designer workflow documentation. Installation examples
118
- below target this version. See the [release validation record and 1.0 checklist](docs/RELEASE-READINESS.md).
121
+ **Version 0.13.5.** This maintenance batch
122
+ strengthens output protection, bounded previews, publication privacy, Windows
123
+ installation paths and startup diagnostics. Installation examples below target
124
+ this version. See the [release validation record and 1.0 checklist](docs/RELEASE-READINESS.md).
119
125
  See [Changelog](CHANGELOG.md) and [upgrade notes](docs/UPGRADING.md).
120
126
 
121
127
  #### Ask Your Agent to Install
@@ -123,7 +129,7 @@ See [Changelog](CHANGELOG.md) and [upgrade notes](docs/UPGRADING.md).
123
129
  Send this to an Agent that can edit your client's MCP configuration:
124
130
 
125
131
  ```text
126
- Install KeyShot MCP 0.13.1 in my MCP client using npx -y keyshot-mcp@0.13.1.
132
+ Install KeyShot MCP 0.13.5 in my MCP client using npx -y keyshot-mcp@0.13.5.
127
133
  Find the full path of my local keyshot_headless.exe and set KEYSHOT_HEADLESS_EXE.
128
134
  Keep the default Documents/KeyShot MCP Outputs folder and external outputs disabled.
129
135
  Use my client's configuration format, restart or reload it, and run keyshot_status.
@@ -142,7 +148,7 @@ and replace the executable path with the actual path on your computer.
142
148
  "mcpServers": {
143
149
  "keyshot": {
144
150
  "command": "npx",
145
- "args": ["-y", "keyshot-mcp@0.13.1"],
151
+ "args": ["-y", "keyshot-mcp@0.13.5"],
146
152
  "env": {
147
153
  "KEYSHOT_HEADLESS_EXE": "C:/Program Files/KeyShot Studio/bin/keyshot_headless.exe"
148
154
  }
@@ -157,7 +163,7 @@ the executable, output access, bridge, presets and a minimal KeyShot startup.
157
163
  <details>
158
164
  <summary>Global npm installation or source build</summary>
159
165
 
160
- **Global:** run `npm install -g keyshot-mcp@0.13.1`, then use `keyshot-mcp` as the
166
+ **Global:** run `npm install -g keyshot-mcp@0.13.5`, then use `keyshot-mcp` as the
161
167
  client command with no `npx` arguments. Keep the same environment settings.
162
168
 
163
169
  **Source:**
@@ -306,15 +312,19 @@ See [material assignment and error guidance](docs/MATERIALS.md).
306
312
 
307
313
  Inputs can come from any trusted local path. Output traversal and symlink/junction
308
314
  escapes are rejected by default. Automatically named product outputs use `-2`,
309
- `-3`, etc. on collision; explicit paths are not silently renamed. Enable `overwrite`
310
- only where supported and when replacement is intentional. Prefer scene copies.
315
+ `-3`, etc. on collision; explicit paths are not silently renamed. Writing tools reject
316
+ existing files by default. Set `overwrite: true` only to replace a distinct output
317
+ intentionally; it never permits replacing this request's input scene, model, material
318
+ or environment, including linked aliases. Preview and saved-scene sync never overwrite.
319
+ Keep scene and image destinations separate. Prefer scene copies.
311
320
  KeyShot headless tasks run sequentially within each server instance to reduce
312
321
  license and file conflicts; this does not coordinate separate MCP server processes.
313
322
 
314
- Processing is local, **but your MCP client may send tool results, scene metadata
315
- and preview images to its selected model provider**. Review privacy settings
316
- before confidential work. No license bypass or arbitrary Python execution is
317
- provided. See [Security](SECURITY.md).
323
+ Processing is local. When an Agent in your MCP client uses these tools, **the client
324
+ may send tool results, scene metadata and preview images to its selected model provider**,
325
+ depending on the client's implementation and settings. KeyShot MCP itself does not
326
+ upload these results. Review privacy settings before confidential work. No license
327
+ bypass or arbitrary Python execution is provided. See [Security](SECURITY.md).
318
328
 
319
329
  ### Questions and Support
320
330
 
@@ -374,8 +384,9 @@ Community conduct is covered by [Code of Conduct](CODE_OF_CONDUCT.md).
374
384
  KeyShot**。已实测 **Windows 11 + KeyShot Studio 2025 / KeyShot 14.1**;其他系统和
375
385
  KeyShot 版本尚未实机验证。项目不包含 KeyShot、许可证或官方材质库。
376
386
 
377
- **本版为 `0.13.1`:** 包含材质实际赋值校验、更明确的材质文件错误提示及更新后的设计师工作流文档。
378
- 下方安装示例使用此版本。验证记录与正式版准备见[验收清单](docs/RELEASE-READINESS.md),
387
+ **本版为 `0.13.5`:** 集中加固输出保护、预览大小限制、发布隐私、
388
+ Windows 安装路径和启动诊断。下方安装示例使用此版本。
389
+ 验证记录与正式版准备见[验收清单](docs/RELEASE-READINESS.md),
379
390
  版本变更见[更新记录](CHANGELOG.md)和[升级说明](docs/UPGRADING.md)。
380
391
 
381
392
  #### 把安装交给 Agent
@@ -383,7 +394,7 @@ KeyShot 版本尚未实机验证。项目不包含 KeyShot、许可证或官方
383
394
  将下面这段话发给能够修改客户端 MCP 配置的 Agent:
384
395
 
385
396
  ```text
386
- 请用 npx -y keyshot-mcp@0.13.1 将 KeyShot MCP 安装到我的 MCP 客户端。
397
+ 请用 npx -y keyshot-mcp@0.13.5 将 KeyShot MCP 安装到我的 MCP 客户端。
387
398
  找到本机 keyshot_headless.exe 的完整路径,设置 KEYSHOT_HEADLESS_EXE。
388
399
  保留默认“文档/KeyShot MCP Outputs”输出目录,关闭外部输出。
389
400
  按这个客户端实际支持的格式配置,重启或重新加载后运行 keyshot_status。
@@ -402,7 +413,7 @@ KeyShot 版本尚未实机验证。项目不包含 KeyShot、许可证或官方
402
413
  "mcpServers": {
403
414
  "keyshot": {
404
415
  "command": "npx",
405
- "args": ["-y", "keyshot-mcp@0.13.1"],
416
+ "args": ["-y", "keyshot-mcp@0.13.5"],
406
417
  "env": {
407
418
  "KEYSHOT_HEADLESS_EXE": "C:/Program Files/KeyShot Studio/bin/keyshot_headless.exe"
408
419
  }
@@ -417,7 +428,7 @@ KeyShot 版本尚未实机验证。项目不包含 KeyShot、许可证或官方
417
428
  <details>
418
429
  <summary>全局 npm 安装或源码运行</summary>
419
430
 
420
- **全局安装:** 运行 `npm install -g keyshot-mcp@0.13.1`,客户端命令改成 `keyshot-mcp`,
431
+ **全局安装:** 运行 `npm install -g keyshot-mcp@0.13.5`,客户端命令改成 `keyshot-mcp`,
421
432
  不再填写 `npx` 参数;环境变量保持一致。
422
433
 
423
434
  **源码运行:**
@@ -549,12 +560,15 @@ pnpm run build
549
560
 
550
561
  输入可以来自任意可信本地路径。默认拒绝越界路径及软链接/目录联接逃逸。
551
562
  产品工具自动命名的输出重名时使用 `-2`、`-3` 等编号,显式路径不被静默改名。
552
- 仅在工具支持且确实要替换文件时开启 `overwrite`,平时使用场景副本。
563
+ 写入工具默认拒绝覆盖已有文件。只有确实要替换另一个输出文件时才设置
564
+ `overwrite: true`;即使开启,也不能覆盖本次调用的输入场景、模型、材质或环境文件,
565
+ 包括指向它们的链接。预览与保存同步始终不覆盖。场景和图片必须使用不同路径,平时使用场景副本。
553
566
  同一个服务实例内的 KeyShot 后台任务按顺序执行,以减少许可证和文件冲突;
554
567
  多个独立 MCP 服务进程之间不共享这一排队机制。
555
568
 
556
- 处理在本机完成,**但 MCP 客户端可能把工具结果、场景元数据和预览图发送给所选模型
557
- 服务**。保密项目请先核对隐私设置。项目不提供许可证绕过或任意 Python 执行。
569
+ 处理在本机完成。**MCP 客户端中的 Agent 使用工具时,返回的工具结果、场景元数据和
570
+ 预览图可能根据客户端的实现与配置发送给所选模型服务。** KeyShot MCP 本身不主动上传
571
+ 这些内容。保密项目请先核对隐私设置。项目不提供许可证绕过或任意 Python 执行。
558
572
  更多说明见 [SECURITY.md](SECURITY.md)。
559
573
 
560
574
  ### 常见问题与支持
package/SECURITY.md CHANGED
@@ -7,11 +7,12 @@ model, material, and environment files and can write rendered images or scene co
7
7
 
8
8
  ### Model-provider data flow
9
9
 
10
- MCP clients normally send tool inputs and results to the model service selected by
11
- the user. Scene metadata returned by inspection tools and images returned by
12
- `keyshot_preview_render` may therefore leave the local computer through that MCP
13
- client. Review the client and model provider's privacy settings before using private
14
- or customer work.
10
+ The Agent in an MCP client receives tool results, including scene metadata and
11
+ images returned by `keyshot_preview_render`. Depending on the client's implementation
12
+ and settings, those results may be sent to the user's selected model service.
13
+ The MCP client controls this data transmission; it is not an upload initiated by
14
+ KeyShot MCP. Review the client and model provider's privacy settings before using
15
+ private or customer work.
15
16
 
16
17
  KeyShot MCP itself does not upload files, run a remote service, or collect telemetry.
17
18
  It never requires license keys in prompts and does not expose arbitrary Python
@@ -28,6 +29,37 @@ execution.
28
29
  explicitly required and trusted.
29
30
  - Review a preview before approving edits or final renders, and preserve source scenes.
30
31
 
32
+ ### Output protection
33
+
34
+ Writing tools reject existing files unless `overwrite: true` is explicitly supported
35
+ and requested. Input files declared in the call (including material assignments) and
36
+ all scene inputs in a render queue remain protected even when overwrite is enabled.
37
+ Path comparisons resolve links and detect hard-linked input aliases. Image and scene
38
+ outputs must not share the same file. Preview and saved-scene sync never overwrite.
39
+
40
+ Paths are checked before headless startup and again in the bridge immediately before
41
+ each save or render, including camera-generated names. These checks are not a filesystem
42
+ sandbox or a cross-process lock: do not run unrelated writers against the same output
43
+ files or modify their links while a task runs. Use a separate output folder per process.
44
+
45
+ Existing low-level workflows that previously replaced a file implicitly now need a new
46
+ destination or explicit `overwrite: true`; overwriting a declared input is not supported.
47
+
48
+ ### Publication checks
49
+
50
+ Maintainers run `pnpm run check:public-safety` before a pull request or release.
51
+ CI rejects credential-like values, personal home paths, unreviewed image filenames,
52
+ scene/license files and suspicious PNG metadata in the tracked publication source.
53
+ Public example paths must be generic. New images require explicit review before being
54
+ added to the reviewed-image list; retain third-party credits and licenses.
55
+
56
+ The check is a rule-based guard, not proof that an asset is non-confidential. It does
57
+ not remove old commits, cached packages, uploaded release artifacts or public issue
58
+ attachments. Review Git history separately, use a GitHub no-reply commit email and
59
+ sanitize diagnostics before sharing: local file paths, scene names and KeyShot output
60
+ can identify a project even without a credential. If a real credential is exposed,
61
+ revoke it first; deleting a file from the latest commit is not sufficient.
62
+
31
63
  ### Reporting issues
32
64
 
33
65
  Report vulnerabilities through the repository's
@@ -44,9 +76,10 @@ KeyShot MCP 在本机运行 KeyShot headless。它可以读取本地场景、模
44
76
 
45
77
  ### 模型服务的数据流
46
78
 
47
- MCP 客户端通常会把工具输入和结果发送给用户选择的模型服务。因此,场景检查工具
48
- 返回的元数据,以及 `keyshot_preview_render` 返回的预览图片,可能通过 MCP 客户端
49
- 离开本机。处理私有项目或客户项目之前,请先确认 MCP 客户端和模型服务商的隐私设置。
79
+ MCP 客户端中的 Agent 会接收工具结果,包括场景检查返回的元数据和
80
+ `keyshot_preview_render` 返回的预览图片。这些内容可能根据客户端的实现与配置发送给
81
+ 用户选择的模型服务。数据传输由 MCP 客户端处理,不是 KeyShot MCP 主动上传。
82
+ 处理私有项目或客户项目之前,请先确认 MCP 客户端和模型服务商的隐私设置。
50
83
 
51
84
  KeyShot MCP 本身不会上传文件、运行远程服务或收集遥测数据。它不会要求用户在提示词
52
85
  中提供许可证密钥,也不开放任意 Python 执行。
@@ -60,6 +93,31 @@ KeyShot MCP 本身不会上传文件、运行远程服务或收集遥测数据
60
93
  - 除非明确需要并信任外部位置,否则保持 `KEYSHOT_ALLOW_EXTERNAL_OUTPUTS` 关闭。
61
94
  - 批准修改或最终渲染前先检查预览,并保留原始场景。
62
95
 
96
+ ### 输出保护
97
+
98
+ 写入工具默认拒绝覆盖已有文件,仅在工具支持且明确设置 `overwrite: true` 时允许替换。
99
+ 本次调用声明的输入文件(包括材质赋值中的文件)和渲染队列中所有任务的输入场景,
100
+ 即使开启覆盖也仍受保护。路径比较会解析链接,并识别输入文件的硬链接别名。
101
+ 场景与图片不能写入同一文件;预览与保存同步始终不覆盖。
102
+
103
+ 服务在启动后台进程前检查路径,bridge 在每次保存、渲染前再次检查,包括按相机名生成的路径。
104
+ 这些检查不是文件系统沙箱或跨进程锁:任务运行期间不要让其他程序写同一输出文件,
105
+ 也不要修改相关链接。多个进程应使用不同输出文件夹。
106
+
107
+ 以前依赖底层工具隐式替换文件的流程,需要改用新路径或显式 `overwrite: true`;
108
+ 覆盖本次声明的输入文件不受支持。
109
+
110
+ ### 发布前检查
111
+
112
+ 维护者在 PR 或发布前运行 `pnpm run check:public-safety`。CI 检查疑似凭据、个人用户目录、
113
+ 未经复核的图片文件名、场景或许可证文件及 PNG 元数据。公开示例只用通用占位路径。
114
+ 新图片经明确复核后才能加入已审核图片清单;保留第三方作者与许可说明。
115
+
116
+ 规则扫描不能证明素材一定不保密,也不会删除旧提交、缓存发布包、Release 附件或公开
117
+ Issue 附件。Git 历史需要单独审查;提交使用 GitHub no-reply 邮箱。分享诊断前先脱敏,
118
+ 本机路径、场景名和 KeyShot 输出即使不含密钥,也可能识别项目。真实凭据一旦公开,
119
+ 应先撤销,仅从最新提交删除文件是不够的。
120
+
63
121
  ### 报告安全问题
64
122
 
65
123
  请先通过仓库的
package/dist/config.js CHANGED
@@ -13,7 +13,7 @@ export function getConfig() {
13
13
  keyshotAllowExternalOutputs: parseBoolean(process.env.KEYSHOT_ALLOW_EXTERNAL_OUTPUTS),
14
14
  keyshotLicenseArgs: splitWindowsArgs(process.env.KEYSHOT_LICENSE_ARGS ?? ""),
15
15
  keyshotTimeoutMs: parsePositiveInt(process.env.KEYSHOT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS),
16
- tmpDir: path.join(projectRoot, "work", "tmp"),
16
+ tmpDir: path.join(os.tmpdir(), "keyshot-mcp"),
17
17
  bridgeScriptPath: path.join(projectRoot, "scripts", "keyshot_bridge.py"),
18
18
  materialPresetsPath: path.resolve(process.env.KEYSHOT_MATERIAL_PRESETS ??
19
19
  path.join(projectRoot, "presets", "materials.json")),
@@ -5,6 +5,7 @@ import { loadCameraPresets } from "./camera-presets.js";
5
5
  import { loadMaterialPresets } from "./presets.js";
6
6
  import { runKeyShotSerialized } from "./runner.js";
7
7
  import { VERSION } from "./version.js";
8
+ import { localFailure } from "./result.js";
8
9
  export async function runKeyShotDiagnostics(config, runStatus = runKeyShotSerialized) {
9
10
  const checks = [];
10
11
  const resolvedExecutable = await resolveExecutable(config.keyshotHeadlessExe);
@@ -22,12 +23,24 @@ export async function runKeyShotDiagnostics(config, runStatus = runKeyShotSerial
22
23
  });
23
24
  checks.push(await fileCheck("bridge-script", "KeyShot bridge script", config.bridgeScriptPath, "Reinstall keyshot-mcp so scripts/keyshot_bridge.py is included."));
24
25
  checks.push(await outputWriteCheck(config.keyshotOutputDir));
26
+ checks.push(await outputWriteCheck(config.tmpDir, "temporary-directory", "Temporary directory", "Check that your system temporary directory is writable, then restart the MCP client. 请检查系统临时目录写入权限,然后重启 MCP 客户端。"));
25
27
  checks.push(await presetCheck("camera-presets", "Camera presets", () => loadCameraPresets(config), config.cameraPresetsPath));
26
28
  checks.push(await presetCheck("material-presets", "Material presets", () => loadMaterialPresets(config), config.materialPresetsPath));
27
29
  let keyshotResult;
28
30
  if (resolvedExecutable &&
29
- checks.find((check) => check.id === "bridge-script")?.ok) {
30
- keyshotResult = await runStatus(config, { operation: "status" });
31
+ checks.every((check) => check.severity !== "error" || check.ok)) {
32
+ try {
33
+ keyshotResult = await runStatus(config, { operation: "status" });
34
+ }
35
+ catch (error) {
36
+ keyshotResult = localFailure(errorMessage(error), {
37
+ errorCode: "KEYSHOT_STARTUP_FAILED",
38
+ suggestions: [
39
+ "Check local folder permissions and KeyShot startup, then retry keyshot_status.",
40
+ "请检查本地目录权限及 KeyShot 能否启动,然后重试 keyshot_status。",
41
+ ],
42
+ });
43
+ }
31
44
  }
32
45
  else {
33
46
  keyshotResult = {
@@ -36,7 +49,7 @@ export async function runKeyShotDiagnostics(config, runStatus = runKeyShotSerial
36
49
  outputFiles: [],
37
50
  warnings: [],
38
51
  keyshotStdoutTail: "",
39
- error: "KeyShot startup was skipped because a required local file was not available.",
52
+ error: "KeyShot startup was skipped because a required local check failed.",
40
53
  errorCode: "DIAGNOSTIC_PREREQUISITE_FAILED",
41
54
  suggestions: [],
42
55
  };
@@ -88,6 +101,7 @@ export async function runKeyShotDiagnostics(config, runStatus = runKeyShotSerial
88
101
  outputDir: config.keyshotOutputDir,
89
102
  allowExternalOutputs: config.keyshotAllowExternalOutputs,
90
103
  timeoutMs: config.keyshotTimeoutMs,
104
+ temporaryDir: config.tmpDir,
91
105
  licenseArgsConfigured: config.keyshotLicenseArgs.length > 0,
92
106
  bridgeScriptPath: config.bridgeScriptPath,
93
107
  cameraPresetsPath: config.cameraPresetsPath,
@@ -150,29 +164,29 @@ async function fileCheck(id, label, filePath, suggestion) {
150
164
  suggestion: ok ? undefined : suggestion,
151
165
  };
152
166
  }
153
- async function outputWriteCheck(outputDir) {
167
+ async function outputWriteCheck(outputDir, id = "output-directory", label = "Output directory", suggestion = "Set KEYSHOT_OUTPUT_DIR to a local folder where the current user can create files.") {
154
168
  const probe = path.join(outputDir, `.keyshot-mcp-write-test-${randomUUID()}`);
155
169
  try {
156
170
  await fs.mkdir(outputDir, { recursive: true });
157
171
  await fs.writeFile(probe, "ok", "utf8");
158
172
  await fs.rm(probe, { force: true });
159
173
  return {
160
- id: "output-directory",
161
- label: "Output directory",
174
+ id,
175
+ label,
162
176
  ok: true,
163
177
  severity: "error",
164
- message: `Output directory is writable: ${outputDir}`,
178
+ message: `${label} is writable: ${outputDir}`,
165
179
  };
166
180
  }
167
181
  catch (error) {
168
182
  await fs.rm(probe, { force: true }).catch(() => undefined);
169
183
  return {
170
- id: "output-directory",
171
- label: "Output directory",
184
+ id,
185
+ label,
172
186
  ok: false,
173
187
  severity: "error",
174
- message: `Output directory is not writable: ${outputDir} (${errorMessage(error)})`,
175
- suggestion: "Set KEYSHOT_OUTPUT_DIR to a local folder where the current user can create files.",
188
+ message: `${label} is not writable: ${outputDir} (${errorMessage(error)})`,
189
+ suggestion,
176
190
  };
177
191
  }
178
192
  }
@@ -1,6 +1,79 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  const OUTPUT_FIELDS = ["outputPath", "outputDir", "outputScenePath"];
4
+ const INPUT_FIELDS = new Set([
5
+ "scenePath",
6
+ "sourcePath",
7
+ "modelPath",
8
+ "baseScenePath",
9
+ "materialPath",
10
+ "environmentPath",
11
+ ]);
12
+ export function inputFilePaths(value) {
13
+ if (Array.isArray(value))
14
+ return value.flatMap(inputFilePaths);
15
+ if (!value || typeof value !== "object")
16
+ return [];
17
+ return Object.entries(value).flatMap(([key, entry]) => {
18
+ if (INPUT_FIELDS.has(key) && typeof entry === "string")
19
+ return [entry];
20
+ if (key === "_protectedInputPaths" && Array.isArray(entry))
21
+ return entry.filter((item) => typeof item === "string");
22
+ return inputFilePaths(entry);
23
+ });
24
+ }
25
+ export async function assertOutputFilesSafe(request) {
26
+ const inputs = inputFilePaths(request);
27
+ const outputs = [request.outputScenePath, request.outputPath].filter((value) => typeof value === "string");
28
+ for (let index = 0; index < outputs.length; index += 1) {
29
+ const output = outputs[index];
30
+ for (const input of inputs) {
31
+ if (await sameFile(output, input))
32
+ throw new Error(`Output must not replace an input file: ${output}`);
33
+ }
34
+ for (const previous of outputs.slice(0, index)) {
35
+ if (await sameFile(output, previous))
36
+ throw new Error(`Output paths must refer to different files: ${output}`);
37
+ }
38
+ try {
39
+ const stat = await fs.lstat(output);
40
+ let target = stat;
41
+ if (stat.isSymbolicLink()) {
42
+ try {
43
+ target = await fs.stat(output);
44
+ }
45
+ catch {
46
+ throw new Error(`Output file path must be a regular file: ${output}`);
47
+ }
48
+ }
49
+ if (target.isDirectory())
50
+ throw new Error(`Output file path is a directory: ${output}`);
51
+ if (!target.isFile())
52
+ throw new Error(`Output file path must be a regular file: ${output}`);
53
+ if (request.overwrite !== true)
54
+ throw new Error(`Output already exists and overwrite is false: ${output}`);
55
+ }
56
+ catch (error) {
57
+ if (error.code !== "ENOENT")
58
+ throw error;
59
+ }
60
+ }
61
+ }
62
+ async function sameFile(first, second) {
63
+ if (normalizeCase(await realpathWithMissingTail(first)) ===
64
+ normalizeCase(await realpathWithMissingTail(second)))
65
+ return true;
66
+ try {
67
+ const a = await fs.stat(first, { bigint: true });
68
+ const b = await fs.stat(second, { bigint: true });
69
+ return a.ino !== 0n && a.dev === b.dev && a.ino === b.ino;
70
+ }
71
+ catch (error) {
72
+ if (error.code === "ENOENT")
73
+ return false;
74
+ throw error;
75
+ }
76
+ }
4
77
  export async function normalizeOutputPaths(config, request) {
5
78
  await fs.mkdir(config.keyshotOutputDir, { recursive: true });
6
79
  const normalized = { ...request };