# dsh-plugin-inline-image

[![CI](https://github.com/usavv1547-cyber/dsh-plugin-inline-image/actions/workflows/ci.yml/badge.svg)](https://github.com/usavv1547-cyber/dsh-plugin-inline-image/actions/workflows/ci.yml)
[![npm version](https://img.shields.io/npm/v/dsh-plugin-inline-image)](https://www.npmjs.com/package/dsh-plugin-inline-image)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

DSH（DeepSeek Harness）Web 插件：在 Web 服务器上开放一条**受严格约束的只读图片路由**
`/dsh-image/<alias>/<相对路径>`，让 agent 可以把工作区里的图片**直接内嵌显示在对话中**，
无需点击文件链接，也无需临时起本地 HTTP 服务器。

## 为什么需要它

DSH Web UI 的 markdown 渲染器会把 `http(s)` 图片链接渲染成 `<img>`，但：
- 服务器没有为工作区文件提供任何静态路由（未命中的路径全部回落到 SPA）；
- 渲染器的 URL 消毒器只放行 `http:` / `https:` / `mailto:`，`data:` 内嵌被拒绝。

因此 agent 生成的图表（matplotlib PNG 等）之前只能以"可点击文件"形式给出。装上本插件后，
agent 在消息里写：

```markdown
![分布图](/dsh-image/sixsigma/distributions.png)
```

图片就会直接显示在对话里。

## 安装

> ⚠️ 安装后需要**重启 `dsh web`** 才会加载插件，重启会短暂中断当前 Web 会话。

```bash
# 1) 安装到 web profile（等价于在 ~/.dsh/profiles/web 里 pnpm add）
dsh plugin --profile web add dsh-plugin-inline-image

# 2) 把插件加入 bundle 清单：编辑 ~/.dsh/profiles/web/package.json
#    "dsh": { "profile": { "bundles": [ ..., "dsh-plugin-inline-image" ] } }

# 3) 配置插件（见下），然后重启 dsh web
```

> 本地开发时可用 `dsh plugin --profile web add file:/本地路径/dsh-plugin-inline-image` 代替第 1 步。

### 配置（~/.dsh/profiles/web/cordis.patch.yml）

在 `cordis.patch.yml` 里追加：

```yaml
- insert:
    - id: inline-image
      name: 'dsh-plugin-inline-image'
      config:
        # alias -> 允许对外提供图片的绝对目录
        roots:
          sixsigma: /Users/guoguo/6sigma
        # 默认 false：.svg 不开放（可携带脚本，XSS 面）；确需时改为 true
        allowSvg: false
```

不配置 `roots` 时，插件会自动回退到 harness 已注册的全部 workspace
（按 workspace id 作为 alias），方便直接用现有工作区。

### 使用

配置了 alias `sixsigma` 指向 `/Users/guoguo/6sigma` 后：

| 消息内容 | 效果 |
|---------|------|
| `![](/dsh-image/sixsigma/fig.png)` | 内嵌显示 `/Users/guoguo/6sigma/fig.png` |
| `![](/dsh-image/sixsigma/reports/chart.webp)` | 内嵌显示子目录图片 |

给 agent 的提示词可以加一句：*"要内嵌展示图片时，用相对链接
`/dsh-image/<alias>/<相对路径>`，如 `![](/dsh-image/sixsigma/fig.png)`。"*

## 安全设计（纵深防御）

1. **只允许 GET / HEAD**，其余方法返回 405；
2. **逐段校验**：每个路径段先 URL 解码，段不得为空、不得为 `.` / `..`、
   不得在解码后包含 `/`、`\`、NUL（`%2e%2e`、`..%2f` 等编码穿越均被拒）；
3. **词法包含检查**：`resolve + normalize` 后必须仍在 root 之下，否则 403；
4. **真实路径检查**：`realpath()` 解析符号链接后再次校验必须仍在 root 之下，
   防止 root 内的符号链接逃逸到任意文件（403）；
5. **扩展名白名单**：仅常见栅格图格式（png/jpg/jpeg/webp/gif/avif/bmp/ico）；
   SVG 默认 415（可选 `allowSvg` 打开）；Content-Type 一律由白名单推导；
6. 服务器进程外无任何暴露（路由挂在现有 `dsh web` 进程上）。

## 测试

```bash
cd dsh-plugin-inline-image
npm install
node --test          # 15 项：正常读取 / HEAD / 405 / 各类穿越 / 符号链接逃逸 / MIME / SVG / 404 / 插件接线
```

## 与官方路线的关系

DSH 消息协议已为"助手输出图片"预留了 `ImageBlock`
（`packages/llm` 中类型注释：*valid in user or assistant content … assistant-side
rendering is forward compatibility*）。本插件是让这一能力**当下可用**的轻量落地；
官方后续若实现完整的 assistant 附件通道，本插件可作为过渡方案。
功能请求已提交：https://github.com/deepseek-ai/deepseek-harness/discussions/2995

## 参与贡献

欢迎提交 issue 和 PR！插件小而聚焦：单文件实现 + 一套覆盖正常路径与各类
逃逸尝试的测试。路径处理是本插件的**安全核心契约**，改动路径/路由/扩展名
白名单的 PR 必须附带对应攻击面测试。

- 开发与测试指南、发布流程：[CONTRIBUTING.md](CONTRIBUTING.md)
- CI：push/PR 自动在 Node 18/20/22 上运行全部测试
- 发布：打 `v<version>` tag 即自动 `npm publish` + 生成 GitHub Release

## License

MIT
