@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.
- package/CHANGELOG.md +39 -0
- package/README.en.md +2 -0
- package/README.md +2 -0
- package/client/client.js +103 -1
- package/client/index.js +83 -0
- package/lib/bridge-rpc-constants.js +1 -0
- package/lib/bridge-rpc.js +10 -0
- package/lib/feishu/node.js +10 -4
- package/lib/index.js +126 -14
- package/lib/platform/conversation-bridge.js +8 -3
- package/lib/platform/message-split.js +39 -1
- package/lib/qq/node.js +539 -532
- package/lib/session-strip.js +57 -0
- package/lib/telegram/node.js +0 -27
- package/lib/tunnel-client.mjs +419 -426
- package/lib/wechat/gateway.js +17 -4
- package/package.json +2 -2
|
@@ -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
|
|
669
|
-
|
|
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 根据用户意图显式决定何时向用户发送文件附件,杜绝底层盲目扫描与误发。
|