🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

拖拽附件预览 anzhaohao/DragView

DeepSeek Harness
anzhaohao71 天前有提交已提供中文文档扫描:含敏感能力

拖入文件生成附件卡片,安全预览不暴露路径

28.6/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/9/16 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add anzhaohao/DragView
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么[目录]—
dsh 原生插件 · other
暂无时间戳·判据 →
装得上吗[实装验证]✓
本站已真实安装成功
非静态推断
L4 · 真实安装等级说明 →
数据截至 2026/09/25·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]—
更新放缓:最近一次提交在 71 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 124 处,展示前 59 条)
  • 读取本地文件
    src/index.js:120
    const rootInfo = await lstat(resolve(rootInput));
  • 读取本地文件
    src/index.js:122
    const root = await realpath(rootInput);
  • 读取本地文件
    src/index.js:123
    if (!(await stat(root)).isDirectory()) throw new Error("workspace is not a directory");
  • 读取本地文件
    src/index.js:129
    const info = await lstat(next);
  • 写入本地文件
    src/index.js:134
    await mkdir(next);
  • 读取本地文件
    src/index.js:136
    current = await realpath(next);
  • 读取本地文件
    src/index.js:142
    const rootInfo = await lstat(resolve(rootInput));
  • 执行外部命令来自配置/变量
    src/index.js:348
    const child = spawn(spec.command, [...spec.args], spec.options);
  • 执行外部命令来自配置/变量
    src/index.js:504
    execFile(command, [...args], {
  • 写入本地文件
    src/index.js:861
    await mkdir2(next);
  • 发起网络请求
    src/client.js:171
    const response = await fetch(RESOLVE_ROUTE, {
  • 发起网络请求
    src/client.js:227
    const response = await fetch(OPEN_ROUTE, {
  • 发起网络请求
    src/client.js:319
    const response = await fetch(`${TEXT_PREVIEW_ROUTE}?id=${encodeURIComponent(item.id)}&sessionId=${encodeURIComponent(item.sessionId)}`, { signal: cont
  • 发起网络请求
    src/client.js:342
    const probe = await fetch(src, { method: "HEAD", signal: controller.signal });
  • 发起网络请求
    src/client.js:801
    fetch("/file-drop/settings").then((response) => response.json()).then((result) => {
  • 发起网络请求
    src/client.js:814
    const response = await fetch("/file-drop/settings", {
  • 发起网络请求
    src/client/index.ts:69
    const response = await fetch(CONFIG_ROUTE)
  • 发起网络请求
    src/client/index.ts:104
    const response = await fetch(COPY_ROUTE, {
  • 发起网络请求
    src/client/index.ts:369
    void fetch(REVOKE_ROUTE, {
  • 发起网络请求
    src/client/locator.ts:10
    const response = await fetch(RESOLVE_ROUTE, {
  • 发起网络请求
    src/client/preview-fixture.js:66
    const response = await fetch(OPEN_ROUTE, {
  • 发起网络请求
    src/client/preview-fixture.js:158
    const response = await fetch(`${TEXT_PREVIEW_ROUTE}?id=${encodeURIComponent(item.id)}&sessionId=${encodeURIComponent(item.sessionId)}`, { signal: cont
  • 发起网络请求
    src/client/preview-fixture.js:181
    const probe = await fetch(src, { method: "HEAD", signal: controller.signal });
  • 发起网络请求
    src/client/preview.ts:29
    const response = await fetch(OPEN_ROUTE, {
  • 发起网络请求
    src/client/preview.ts:121
    const response = await fetch(`${TEXT_PREVIEW_ROUTE}?id=${encodeURIComponent(item.id)}&sessionId=${encodeURIComponent(item.sessionId)}`, { signal: cont
  • 发起网络请求
    src/client/preview.ts:141
    const probe = await fetch(src, { method: 'HEAD', signal: controller.signal })
  • 读取本地文件
    src/file-access.js:113
    const rootInfo = await lstat(resolve(rootInput));
  • 读取本地文件
    src/file-access.js:115
    const root = await realpath(rootInput);
  • 读取本地文件
    src/file-access.js:116
    if (!(await stat(root)).isDirectory()) throw new Error("workspace is not a directory");
  • 读取本地文件
    src/file-access.js:122
    const info = await lstat(next);
  • 写入本地文件
    src/file-access.js:127
    await mkdir(next);
  • 读取本地文件
    src/file-access.js:129
    current = await realpath(next);
  • 读取本地文件
    src/file-access.js:135
    const rootInfo = await lstat(resolve(rootInput));
  • 执行外部命令来自配置/变量
    src/file-access.js:353
    const child = spawn(spec.command, [...spec.args], spec.options);
  • 读取本地文件
    src/file-access.ts:142
    const rootInfo = await lstat(resolve(rootInput))
  • 读取本地文件
    src/file-access.ts:144
    const root = await realpath(rootInput)
  • 读取本地文件
    src/file-access.ts:145
    if (!(await stat(root)).isDirectory()) throw new Error('workspace is not a directory')
  • 读取本地文件
    src/file-access.ts:151
    const info = await lstat(next)
  • 写入本地文件
    src/file-access.ts:156
    await mkdir(next)
  • 读取本地文件
    src/file-access.ts:158
    current = await realpath(next)
  • 读取本地文件
    src/file-access.ts:165
    const rootInfo = await lstat(resolve(rootInput))
  • 执行外部命令来自配置/变量
    src/file-access.ts:397
    const child = spawn(spec.command, [...spec.args], spec.options)
  • 读取本地文件
    src/fingerprint.js:53
    const size = (await stat(path)).size;
  • 读取本地文件
    src/fingerprint.ts:44
    const size = (await stat(path)).size
  • 读取本地文件
    src/locator.ts:33
    const info = await stat(path)
  • 读取本地文件
    src/locator.ts:44
    try { entries = await readdir(directory, { withFileTypes: true }) } catch { return }
  • 读取本地文件
    src/locator.ts:60
    const info = await stat(path)
  • 读取本地文件
    src/locator.ts:83
    try { entries = await readdir(root, { withFileTypes: true }) } catch { continue }
  • 读取本地文件
    src/locator.ts:93
    try { grandchildren = await readdir(directory, { withFileTypes: true }) } catch { continue }
  • 读取本地文件
    src/platform-search.ts:31
    try { await access(command, constants.X_OK); return true } catch { return false }
  • 执行外部命令来自配置/变量
    src/platform-search.ts:47
    execFile(command, [...args], {
  • 读取本地文件
    src/platform-search.ts:159
    for (const entry of await readdir(parent, { withFileTypes: true })) {
  • 读取本地文件
    src/side-chat-bridge.js:93
    const rootInfo = await lstat(resolve(rootInput));
  • 读取本地文件
    src/side-chat-bridge.js:95
    const root = await realpath(rootInput);
  • 读取本地文件
    src/side-chat-bridge.js:96
    if (!(await stat(root)).isDirectory()) throw new Error("workspace is not a directory");
  • 读取本地文件
    src/side-chat-bridge.js:102
    const info = await lstat(next);
  • 写入本地文件
    src/side-chat-bridge.js:107
    await mkdir(next);
  • 读取本地文件
    src/side-chat-bridge.js:109
    current = await realpath(next);
  • 读取本地文件
    src/side-chat-bridge.js:115
    const rootInfo = await lstat(resolve(rootInput));
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 拖入文件生成附件卡片
  • 应用内安全预览
  • 不暴露文件路径

为 DSH 提供拖拽式文件附件与安全的应用内预览。

README

由 DeepSeek 最新模型翻译生成
DragView — dsh-dragview DragView 是 DSH 的拖拽文件与预览插件:把本地文件拖进聊天界面,按配置复制进工作区或只解析真实路径,在输入框中渲染成 Codex 风格附件卡片,并安全预览或用系统默认应用打开——不把路径显示给用户。 GitHub 仓库名为 DragView,npm 与 DSH 包名为 dsh-dragview;品牌与 drag 搜索词同时保留,便于在 GitHub 和 DSH 插件市场检索。 两种模式(设置里切换,重启生效) | 模式 | 行为 | 适合场景 | | resolve(默认) | 拖入后解析文件的真实绝对路径,不复制、不动原文件 | 引用工作区已有文件;不破坏文件与邻居依赖的关系 | | copy | 松手即把文件字节复制进 //(默认 .drops),返回副本路径 | 拖入工作区外的任意文件,让模型能稳定读取 | 图片(image/)不经过本插件,保留平台原生附件通道(缩略图、预览、历史渲染)。 交互流程 1. 从资源管理器拖文件到页面任意位置 → 全屏遮罩提示(按模式显示「复制」或「引用」文案)。 2. 松手:非图片文件进入解析/复制管线;图片走原生通道(混合拖拽时图片自动重派发给原生)。
展开完整文档(README 剩余 1 节)
DragView — dsh-dragview

DragView 是 DSH 的拖拽文件与预览插件:把本地文件拖进聊天界面,按配置复制进工作区或只解析真实路径,在输入框中渲染成 Codex 风格附件卡片,并安全预览或用系统默认应用打开——不把路径显示给用户。

GitHub 仓库名为 DragView,npm 与 DSH 包名为 dsh-dragview;品牌与 drag 搜索词同时保留,便于在 GitHub 和 DSH 插件市场检索。

两种模式(设置里切换,重启生效)

| 模式 | 行为 | 适合场景 |
| resolve(默认) | 拖入后解析文件的真实绝对路径,不复制、不动原文件 | 引用工作区已有文件;不破坏文件与邻居依赖的关系 |
| copy | 松手即把文件字节复制进 //(默认 .drops),返回副本路径 | 拖入工作区外的任意文件,让模型能稳定读取 |

图片(image/)不经过本插件,保留平台原生附件通道(缩略图、预览、历史渲染)。

交互流程

1. 从资源管理器拖文件到页面任意位置 → 全屏遮罩提示(按模式显示「复制」或「引用」文案)。
2. 松手:非图片文件进入解析/复制管线;图片走原生通道(混合拖拽时图片自动重派发给原生)。
3. 输入框附件栏出现约 280×64 px 的 Codex 风格附件卡片:左侧类型方块,中间文件名与「类型 · 大小」双行信息,右侧圆形移除按钮。卡片和原生图片共同换行,side-chat 引用独占下方一行。
4. 点击卡片或按 Enter/Space:PDF、文本/代码、视频和音频在 DSH 内预览;Office、压缩包与未知文件使用系统默认应用打开。移除按钮不会触发上述操作。
5. 发送时,消息内容在用户文本前追加每行一个 @"" 的文件引用(纯文本模型可读),但绝对路径不会进入附件 DOM、标题或浏览器日志。
6. 发送成功才清空卡片并撤销临时 token;失败保留,便于重试。

路径解析策略(resolve 模式)

浏览器出于安全不会把真实路径交给网页,插件按顺序定位:

1. 当前会话的注册工作区 → 其他注册工作区(workspace 根由 host 权威解析,客户端不能指定任意路径)。
2. 系统目录:桌面 / 文档 / 下载。
3. 浅扫描:深度 1–3 层(每根目录最多展开 4096 个子目录)。
4. 系统索引:Windows 检测到 Everything CLI(es.exe)才启用,否则用 PowerShell 递归兜底;macOS Spotlight;Linux plocate/locate。
5. 有界递归:深度 12、最多 2 万目录项、最多 100 候选。

候选按名称+大小去重,按 |mtime − lastModified| 排序;即使只有一个 metadata 候选也必须经过内容证明。小于等于 3 × 64 KB 时 sample 已覆盖全文件;更大文件的 sample 匹配后必须再做全量指纹。候选绝对路径只保存在 host 的短期 resolution 中,浏览器只持有不透明 resolutionId/choiceId;仍分不清时选择器只显示安全编号。失败会弹出提示,不静默。

文件能力与安全边界

- host 为每个解析/复制成功的文件建立短期、不透明的 fileId;预览、系统打开与撤销接口只接受该 ID。
- 每次访问都重新核对 realpath、文件类型、大小、mtime、设备号与 inode;过期、撤销、变化或非法 token 均拒绝。
- copy 只能写入当前 session 在 workspace registry 中权威绑定的工作区。dropDir 禁止绝对路径、..、符号链接/junction 与前缀碰撞逃逸。
- side-chat host 先把 Markdown 安全保存到可信 exportRoot,并根据 child session 的可信记录推导 parent session,再调用 dshDragFileHost。drag-file host 验证来源仍位于该 exportRoot:resolve 直接为原导出文件签发 token,不创建工作区副本;只有 copy 才受限读取最多 4 MiB,并安全复制到 parent session 权威工作区的 .dsh-side-chat-exports 后注册。浏览器事件只接收已签发的 opaque token 与显示/发送元数据,不接收路径或 Markdown 来换取权限;未安装 drag-file 时,side-chat 仍以自身保存的 savedPath 创建 DSH 原生 reference chip。
- 系统打开使用参数数组与 shell: false;不拼接 shell 命令。
- PDF/音视频与文本预览都从经过 token、session、workspace 和文件身份复核后打开的 FileHandle 读取;PDF/音视频响应为 inline、no-store、nosniff,支持单段 Range(206/416)。文本文件最大 10 MB,最多读取并显示前 1 MB;无论原扩展名/MIME 是 HTML、XML、代码还是 Markdown,文本预览响应均强制 text/plain 并附加 sandbox CSP,避免主动内容执行。
- 系统打开同样先取得并复核 FileHandle,关闭句柄后再把已验证路径交给 Explorer/操作系统;这一交接点仍有无法完全消除的极窄 TOCTOU 残余,不能宣称 system-open 与路径替换完全无竞态。
- 写入流程逐层拒绝 symlink/junction,并在 wx 创建后重新 realpath 验证目标仍位于 canonical workspace。受 Node 跨平台 API 限制,同权限恶意进程在验证与创建之间替换目录的极窄竞态不能被描述为完全消除,详见交接文档。

安装(开发机手动同步)

npm run build # 产出 host/client bundle 与安全、指纹、rail 测试产物
npm run check
npm test # token/路径约束/Range/MIME/open argv/side 内容桥回归

把包同步到 web profile: ~/.hanako/plugin-data/dsh-hanako/dsh-home/profiles/web/node_modules/dsh-dragview/ ├── src/ # 构建产物 ├── package.json └── cordis.patch.yml

并在 profiles/web/cordis.patch.yml 追加:

- insert:
- id: drag-file
name: dsh-dragview

客户端刷新页面即生效;host 端改动需重启 dsh web。

发布到 npm 后可直接 dsh plugin add dsh-dragview(dsh 字段 + dsh.bundle.patch 已声明)。

注:npm 上的 dsh-drag-file 是其他维护者的既有包,不是 DragView;请使用完整包名 dsh-dragview。GitHub 仓库改名后通常会为旧 URL 保留跳转,但 npm 包名不支持别名。

兼容性标识

为避免旧配置和 side-chat 集成失效,改名后仍保留以下稳定接口:

- Cordis entry 与设置分区 ID:drag-file;
- side-chat 事件:dsh-drag-file:add-pill;
- host capability:dshDragFileHost;
- host 路由前缀:/file-drop;
- 已有 dsh-drag-file- DOM/CSS/data/header 标识。

这些是有意保留的协议名,不表示仍需安装旧 npm 包。

发布与安全

- 版本变化见 CHANGELOG.md。
- 私密漏洞报告与支持范围见 SECURITY.md。
- 维护者发布、部署和 DSH 市场提交流程见 docs/RELEASING.md。

设置

DSH 设置里新增 drag-file 分区:

- mode:resolve(默认)| copy
- dropDir:复制目标相对工作区的文件夹名,默认 .drops

维护

- src/* 是源码(TypeScript);src/.js / src/client.js 是构建产物,改动源码后必须 npm run build。
- host 路由:/file-drop/config、resolve、copy、preview、text-preview、open、revoke;side-chat 导出使用 dshDragFileHost 的 host-to-host capability,不开放浏览器注册路由(见 src/index.ts)。
- 客户端:拖拽监听、MIME 分流、胶囊队列、sendSession 补丁(见 src/client/index.ts)。

图标策略

常用类型(PDF/DOC/XLS/PPT/压缩/视频/音频/代码/文本)用 Bootstrap Icons 的内嵌 SVG 图标;其余类型直接把扩展名渲染成图标(小圆角方块 + 扩展名文字),不需要为任何新扩展名寻找图标资产。见 src/client/icons.ts。

致谢

- 文件类型图标 ← Bootstrap Icons(MIT)
- 路径解析/定位器、平台搜索、指纹、拖拽遮罩、选择器 ← omdsh-dev/dsh-drag-and-drop(BSD-3-Clause)
- sendSession 补丁模式、复制路由、胶囊栏 DOM 注入、混合拖拽图片重派发 ← loudMore/dsh-drop-to-path(MIT)
- 构建管线与插件骨架 ← anzhaohao/dsh-side-chat-plus-plus(MIT)

详见 NOTICE。
预览分享卡片长什么样 ↓anzhaohao/DragView
分享 · DeepSeek Harness Hub
anzhaohao/DragView

拖入文件生成附件卡片,安全预览不暴露路径

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部