theRMM714/git-for-dsh ↗★ 0

git-for-dsh

Allowlisted git execution for DeepSeek Harness: a model-facing git tool that runs outside the file sandbox, with a settings page where the user ticks which git subcommands are permitted and, per path, which reads and writes are allowed, prompted or refused 适合需要授权 AI 在沙箱外执行特定安全 Git 命令的开发者。

パッケージ
git-for-dsh
互換性
未検証
Harness ピア範囲
*
Cordis ピア範囲
*
バージョン
0.0.7
ライセンス
MIT
最終更新
2026/09/28

インストール

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:theRMM714/git-for-dsh

ドキュメント

README 全文を読む ↗

免责声明:这是个人测试用的项目,使用这个项目出现任何问题,作者不负任何责任,如果同意再进行使用。

一个 DeepSeek Harness 插件:让 AI 能在文件沙箱之外执行 git 命令,同时由用户自己勾选「允许哪些 git 子命令」。

插件由两半组成,装一次即可:

  • Host 半(src/index.js)注册模型可见的工具 git_exec,并实现四道闸门:用户的允许清单、参数闸门、配置写入闸门,以及执行环境硬化。
  • Client 半(src/client.js)在「设置 → Git 工具」里渲染勾选页,用户在这里决定放行哪些子命令。

使用 git_exec

参数必填含义
argv是程序名之后的 git 参数,第一个元素是子命令,例如 ["log", "--oneline", "-n", "20"]
paths否本次命令涉及的文件或目录,插件拼成 git … --
description是一句话说明这条命令做什么,显示在界面上
workdir否工作目录;默认会话工作区,相对路径按会话工作区解析
timeoutMs否超时毫秒数;本地操作默认 120000,触及远端的操作默认 300000
justification否需要审批时提供的一句话理由

返回 exitCode、signal、timedOut、operation、command、stdout、stderr。

文件路径放在 paths 里,不要塞进 argv:插件会拼成 git … -- ,因此空格、通配符、以 - 开头的文件名都不需要转义。

工具描述与系统提示段都从当前允许清单实时生成,模型任何时刻都能看到自己能用哪些操作。

3. 配置不能变成程序

只读操作会读取仓库配置,而配置里可以指定要执行的程序,这足以绕过整个允许清单:

git config --local core.fsmonitor  "sh -c '…'"                     → git status 执行了它
git config --local diff.evil.command "sh -c '…'" + .gitattributes  → git diff 执行了它

status 和 diff 都是默认放行的只读操作,因此「只放行看状态」实际上等于给了代码执行能力。三处封堵:

  • config 只保留读取形式:最多一个操作数,且不接受 --unset / --add / --replace-all / --edit / -f 等写入或选文件旗标。git config user.name X 会被拒绝。
  • 指定程序的配置键被钉死:core.fsmonitor、core.gitProxy 连同 core.pager、core.hooksPath、credential.helper 一起,通过环境变量注入(优先级高于任何配置文件)。ssh 也在其中,但方式不同:GIT_SSH_COMMAND 被钉成 -o BatchMode=yes -o StrictHostKeyChecking=accept-new,环境通道同样压过一切配置文件,所以仓库无法指定程序来当 ssh,而 SSH 传输仍然可用。
  • diff 类子命令强制带 --no-ext-diff --no-textconv:diff..command 是通配键,无法逐个钉死,因此在命令上关闭外部 diff 与 textconv。调用方传 --ext-diff / --textconv 会被拒绝,无法把它打开。

scripts/verify-driver-hardening.mjs 用真实 git 与一个恶意仓库验证这两条路径已关闭,并且带控制组(未加固时两个标记都必须出现),否则「没看到标记」可能只是没武装。

4. 仓库配置审计

前三项针对的是「键能被钉死」的情况。git 原生通配(filter..clean、url..insteadOf、alias.、merge..driver)没有对应的单个环境键可以覆盖,钉不死就只能拒绝。

所以每次调用前先跑一次 git config --local --includes --list --name-only -z,命中危险键就按配置的裁定处理:

裁定(设置页文案)行为
refuse-repo(一票拒绝,默认)该仓库里出现任何危险键,本工具在该仓库拒绝执行任何命令
refuse-affected(只拒受影响)只拒「会被该键影响」的子命令(例如 alias.* 劫持不了 git 内建命令,不影响 status)
neutralize(尽量中和)已被钉死的键照常工作,只拒无法钉死的通配键
off(关闭审计)关闭审计:不检查仓库配置,因此也不为此多起一个 git 进程。危险键可以借此执行程序(core.fsmonitor、diff.*.command),只应在完全信任仓库时使用

这个命令有两条硬性要求:

  • 必须带 --includes。git config --local --list 看不到由 [include] 引入的键,而它们照样生效:一个被 include 进来的 core.fsmonitor 能执行却对 --local --list 不可见。不带 --includes 的审计会恰好漏掉「会执行的那些」。
  • --name-only 必须与 --list 同用。只写 --name-only 时 git 以 exit 129(no action specified)失败;而「读不到配置」按设计不算拒绝,于是闸门会完全空转。该命令在代码里只有一处定义,并由 scripts/verify-config-audit.mjs 用真实 git 复查它确实能跑、能看到 include 进来的键、三种裁定都正确(off 档不检查配置,因此不在此脚本范围内)。

审计也未必是「对抗 AI 的边界」—— 仓库配置就在工作区内,模型可以直接写它。它的价值在于不管配置怎么来的,运行前都会被检查:敌对仓库(clone 来、不是自己写的)尤其如此。

性能:审计结果按仓库配置状态缓存

一次 git_exec 会起两个 git 进程:子命令本身,以及执行前的仓库配置审计。在 WSL 上进程启动是毫秒级开销,所以审计结果会缓存 —— 但缓存键覆盖了判定所依赖的每一个输入:

  • 运行目录;
  • 危险键策略;
  • 仓库自身配置的状态戳(mtime + size,含 config.worktree)。

配置一改,戳就变,缓存自动失效;缓存里存的是解析出的键名,而判定每次都按当前子命令重新计算,所以不存在「缓存住的结论」。

两个边界是写明的:

  1. [include] 引入的文件:它们的路径不额外问 git 就看不到(而问 git 正是缓存要省掉的调用),所以对被 include 的文件的修改由 30 秒 TTL 兜住,而不是立刻失效;
  2. worktree(.git 是文件而非目录):没有可监视的配置,于是完全不缓存(宁可每次都跑,也不缓存一个无法验证的东西)。