@wenbin_wb/dsh-bridge 2.10.1 → 2.10.3

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.
@@ -21,7 +21,7 @@ import { randomUUID } from 'node:crypto'
21
21
  import { stat } from 'node:fs/promises'
22
22
  import { normalize } from 'node:path'
23
23
  import { resolveFilePath } from './message-split.js'
24
- import { splitForIM, textOfAssistantMessage, extractAndStripSendFileDirectives, extractFilePathsFromText } from './message-split.js'
24
+ import { splitForIM, textOfAssistantMessage, extractAndStripSendFileDirectives, extractFilePathsFromText, isPathAllowedForSend } from './message-split.js'
25
25
  import { routeCommand } from './commands.js'
26
26
  import { listSessions, listWorkspaces, validateWorkspacePath, renderSessions, sessionsInDisplayOrder, describeTurnEnd, helpText, fmtTime, fmtSessionId, sessionLabel } from './session-catalog.js'
27
27
 
@@ -665,9 +665,14 @@ export class ConversationBridge {
665
665
  const uniqueFilesToSend = []
666
666
  for (const f of rawFiles) {
667
667
  const resolved = resolveFilePath(f, cwd)
668
- if (resolved && !uniqueFilesToSend.includes(resolved)) {
669
- uniqueFilesToSend.push(resolved)
668
+ if (!resolved || uniqueFilesToSend.includes(resolved)) continue
669
+ // 发送白名单:仅允许会话 cwd(及其子目录)内、且不命中敏感路径的文件,
670
+ // 防止模型被提示注入后借 [SEND_FILE] 外发 .ssh/.credentials/.env 等任意本地文件。
671
+ if (!isPathAllowedForSend(resolved, cwd)) {
672
+ this.logger?.warn?.(`[dsh-bridge ${this.platform.id}] blocked SEND_FILE outside allowed workspace: ${resolved}`)
673
+ continue
670
674
  }
675
+ uniqueFilesToSend.push(resolved)
671
676
  }
672
677
  for (const resolved of uniqueFilesToSend) {
673
678
  try {
@@ -2,7 +2,7 @@
2
2
  // 自 conversation-bridge.js 拆出:按平台 maxMessageChars 分块、保留 fenced code block、
3
3
  // [SEND_FILE: ...] 显式指令提取与路径解析。
4
4
  import { statSync } from 'node:fs'
5
- import { isAbsolute, normalize, resolve } from 'node:path'
5
+ import { isAbsolute, normalize, relative, resolve } from 'node:path'
6
6
 
7
7
  const FENCE_RE = /^```([^\n`]*)\s*$/
8
8
 
@@ -149,6 +149,44 @@ export function resolveFilePath(rawPath, cwd = process.cwd()) {
149
149
  return null
150
150
  }
151
151
 
152
+ /**
153
+ * 判断解析后的文件路径是否允许经 [SEND_FILE] 发送给 IM。
154
+ * 安全约束(防止模型被诱导后外发任意本地文件):
155
+ * 1. 必须位于 allowedRoots 中的某个根目录(含子目录)内 —— 默认仅会话 cwd;
156
+ * 2. 路径任何一段不得命中敏感名单(.ssh/.gnupg/.aws/.git/.env/.credentials 等)。
157
+ * @param {string} resolvedPath 已解析的绝对路径
158
+ * @param {string|string[]} allowedRoots 允许的根目录(绝对路径);默认 process.cwd()
159
+ * @returns {boolean}
160
+ */
161
+ export function isPathAllowedForSend(resolvedPath, allowedRoots = process.cwd()) {
162
+ if (typeof resolvedPath !== 'string' || !resolvedPath) return false
163
+ const roots = Array.isArray(allowedRoots) ? allowedRoots : [allowedRoots]
164
+ if (roots.length === 0) return false
165
+
166
+ const normalized = resolve(resolvedPath)
167
+ const pathParts = normalized.split(/[\\/]/).filter(Boolean)
168
+ const SENSITIVE_PARTS = new Set([
169
+ '.ssh', '.gnupg', '.aws', '.azure', '.kube', '.git', '.svn', '.hg',
170
+ '.bash_history', '.zsh_history', '.profile', '.bash_profile', '.bashrc',
171
+ '.zshrc', '.netrc', '.env', '.npmrc', '.credentials', 'id_rsa', 'id_ed25519',
172
+ 'id_ecdsa', 'id_dsa', 'shadow', 'passwd',
173
+ ])
174
+ for (const part of pathParts) {
175
+ if (SENSITIVE_PARTS.has(part.toLowerCase())) return false
176
+ }
177
+
178
+ for (const root of roots) {
179
+ if (typeof root !== 'string' || !root) continue
180
+ const normRoot = resolve(root)
181
+ if (normalized === normRoot) return true
182
+ // 用 relative 判断是否位于根内:越界时 rel 为 '..' 或以 '../' 开头
183
+ // (Windows 跨盘则 rel 是绝对路径,isAbsolute 拦截)。跨平台正确处理 \ 与 / 差异。
184
+ const rel = relative(normRoot, normalized)
185
+ if (rel && !rel.startsWith('..') && !isAbsolute(rel)) return true
186
+ }
187
+ return false
188
+ }
189
+
152
190
  /**
153
191
  * 提取并过滤文本中的 [SEND_FILE: <path>] 显式发送指令
154
192
  * 由 AI 根据用户意图显式决定何时向用户发送文件附件,杜绝底层盲目扫描与误发。