ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Hermes WebUI Workspace Git 控制:默认只读、可安全开启的浏览器端 Git 操作设计

Hermes WebUI Workspace Git 控制:默认只读、可安全开启的浏览器端 Git 操作设计 Hermes WebUI Workspace Git 控制默认只读、可安全开启的浏览器端 Git 操作设计【免费下载链接】hermes-webuiHermes WebUI: The best way to use Hermes Agent from the web or from your phone!项目地址: https://gitcode.com/GitHub_Trending/he/hermes-webuiWorkspace Git 控制Workspace Git controls是 Hermes WebUI 中让浏览器直接检视并操作“当前会话工作区”Git 仓库的一套受控机制默认只能读status、分支、diff、fetch、生成提交信息建议所有会改动仓库/索引/工作树的操作被整体封锁直到 WebUI 进程以HERMES_WEBUI_WORKSPACE_GIT_DESTRUCTIVE1启动。读完本文你能完整掌握该功能的信任模型、默认/受限能力边界、路径与作用域隔离、并发协调、Hook 与远端凭据行为以及源码级的安全加固细节从而在生产部署中正确决定是否开启变更能力。一、总体设计浏览器只看会话工作区服务端负责解析与隔离根据 官方文档浏览器不会发送任意仓库路径Git 请求只携带session_id和必要时工作区相对文件路径。服务端负责解析会话工作区、把每个路径与该工作区做越界校验然后才构造 Git pathspec。这一设计的核心实现在 api/workspace_git.py 中其模块注释直接点明了边界Git helpers for the workspace panel. The browser only sends session ids and workspace-relative paths. This module resolves the active workspace server-side, scopes paths before they become Git pathspecs, and keeps all Git subprocess calls shell-free and bounded. 服务端解析工作区上下文的过程见resolve_git_context()api/workspace_git.py#L457-L467先对 workspace 做expanduser().resolve()再用git rev-parse --show-toplevel定位仓库根最后把 workspace 相对仓库根的目录前缀workspace_prefix记录下来之后所有 status/diff 的 pathspec 都限定在这个前缀内_workspace_pathspec()workspace 恰好是仓库根时用.。对于具体文件路径_repo_rel()api/workspace_git.py#L474-L488用safe_resolve_ws()解析并两次做relative_to校验既不能逃出工作区也不能逃出仓库根任何..或符号链接逃逸都会抛出path_outside_workspace错误。二、默认能力只读检视 fetch 提交信息建议未设置破坏性开关时WebUI 可以做以下事情与文档 What works by default 章节一致显示仓库状态status列出分支本地与远端显示文件 diffstaged / unstaged / 未跟踪文件从已配置远端 fetch生成提交信息建议commit-message suggestions对应的只读/默认端点注册在 api/routes.py#L14191-L14198端点说明GET /api/git/status?session_id...仓库状态、分支、ahead/behind、变更文件清单GET /api/git/branches?session_id...本地/远端分支及 upstream 信息GET /api/git/diff?session_id...path...kindunstaged\|staged单文件 diff这些能力值得注意的边界源码里都有明确佐证fetch 只更新远端跟踪引用。git_fetch()api/workspace_git.py#L1684-L1699执行git fetch --prune --no-recurse-submodules不合并分支、不创建提交、不改动工作树、不推送。提交信息生成会把 diff 上下文发给模型提供方。staged_commit_message_prompt()与selected_commit_message_prompt()分别取暂存区或所选文件的 diff截断到COMMIT_MESSAGE_DIFF_LIMIT 64KB见 api/workspace_git.py#L33组装 prompt文档明确要求 UI 在生成前标注这一点。系统提示词COMMIT_MESSAGE_SYSTEM_PROMPTapi/workspace_git.py#L1395-L1406约束了生成风格禁止 update、improve 等空泛标题大提交要求 2-5 条正文要点禁止在提交信息中提及 AI/工具名称。未跟踪文件的 diff 先做尺寸检查。git_diff()对未跟踪文件走_synthetic_untracked_diff()api/workspace_git.py#L1221-L1260文件超过DIFF_SIZE_LIMIT 512KB时只返回too_large元数据不含 diff 文本检测到\0字节或 UTF-8 解码失败则标记为binary并省略内容。跟踪文件的 diff 同样受 512KB 上限截断。status 端点自身还有STATUS_FILE_LIMIT 500的文件数上限超出后返回truncated: trueapi/workspace_git.py#L800-L802。三、变更操作HERMES_WEBUI_WORKSPACE_GIT_DESTRUCTIVE 显式开启以下操作在开关未设置时一律被封锁与文档 What requires explicit enablement 一致stage 与 unstagediscard changescommit 与 selected-file commit只提交所选文件pull 与 pushcheckout带“自动暂存/恢复本地改动”的分支切换stash-checkout开关的判定函数是workspace_git_destructive_enabled()api/workspace_git.py#L103-L109环境变量HERMES_WEBUI_WORKSPACE_GIT_DESTRUCTIVE接受1、true、yes、on大小写不敏感其余取值包括未设置都视为关闭。路由层的门禁_git_reject_destructive_if_unsafe()api/routes.py#L24873-L24900对所有破坏性 POST 端点生效返回结构化错误开关未开HTTP 403错误码destructive_git_disabled消息中直接给出启用方式Set HERMES_WEBUI_WORKSPACE_GIT_DESTRUCTIVE1 to enable them开关已开但会话有进行中的流HTTP 409错误码active_stream。受门禁保护的 POST 端点全部注册在 api/routes.py#L16432-L16467/api/git/stage、/api/git/unstage、/api/git/discard、/api/git/commit-message、/api/git/commit-message-selected、/api/git/commit、/api/git/commit-selected、/api/git/fetch、/api/git/pull、/api/git/push、/api/git/checkout、/api/git/stash-checkout。其中 commit-message 两个端点在文档语义上属于“建议生成”不改动仓库但仍走破坏性门禁——文档给出的部署建议是只在浏览器用户可信地修改这些仓库时才设置该变量只读检视的部署请保持未设置。分支切换的自动 stash park/restore开关开启后git_stash_and_checkout()api/workspace_git.py#L1146-L1206实现了文档描述的“park and restore”语义源码细节如下若当前分支有未提交改动执行git stash push -u -m hermes-webui branch switch to 目标分支——stash 主题带有hermes-webui branch switch前缀api/workspace_git.py#L48使 WebUI 能识别自己创建的 stash不会误碰用户手工的 stash执行git switch按 local / new / remote / detached 四种模式分别走--recurse-submodulesno的switch调用进入目标分支后_restore_branch_switch_stash_locked()查找该分支对应的 WebUI 自有 stash 并git stash pop --index恢复若恢复失败冲突stash 原样保留在仓库中接口返回restore_failed: true与restore_error绝不静默丢弃若 checkout 本身中途失败则回滚弹出刚 push 的stash{0}api/workspace_git.py#L1180-L1189。测试用例test_git_stash_and_checkout_restores_branch_changes_when_returning与test_git_stash_and_checkout_reports_restore_conflicts_without_dropping_stashtests/test_workspace_git.py#L912-L973分别覆盖了恢复成功与冲突不丢 stash 两条路径。普通git_checkout()则走dirty_modeblock工作区不干净时直接拒绝切换报错dirty_worktree。所选文件提交临时索引GIT_INDEX_FILE 例外文档明确GIT_INDEX_FILE是环境清洗的唯一有意例外。其用途是“所选文件提交”_selected_temp_index_env()api/workspace_git.py#L1431-L1464创建一个私有临时索引文件设置GIT_INDEX_FILE指向它然后read-tree HEAD或read-tree --empty支持首次提交git add -A -- pathspec只把所选文件装进临时索引git_commit_selected()api/workspace_git.py#L1631-L1670在该索引上commit成功后git reset HEAD -- pathspec把真实索引中的相应条目还原最后finally删除临时索引。这样“只提交所选文件”完全不污染用户的真实暂存区。测试test_git_commit_selected_ignores_unrelated_real_index验证了真实索引中的无关暂存内容不受影响。四、子进程安全shellFalse、超时与 GIT_* 环境清洗文档与源码一致所有 Git 命令通过subprocess.run(..., shellFalse)执行api/workspace_git.py#L239-L253本地 status/diff 类命令 5 秒超时GIT_TIMEOUT 5fetch/pull/push 等远端操作 60 秒超时GIT_REMOTE_TIMEOUT 60api/workspace_git.py#L29-L30commit 单独使用 10 秒超时。任何 Git 子进程启动前_clean_git_env()api/workspace_git.py#L112-L122从继承环境中删除以下变量并注入GIT_TERMINAL_PROMPT0被清洗的变量威胁GIT_DIR、GIT_WORK_TREE把 Git 重定向到另一个仓库GIT_CONFIG_GLOBAL、GIT_CONFIG_SYSTEM、GIT_CONFIG_COUNT、GIT_CONFIG_PARAMETERS及全部注入的GIT_CONFIG_KEY_*/GIT_CONFIG_VALUE_*向子进程注入任意 Git 配置GIT_ASKPASS、SSH_ASKPASS、GIT_SSH、GIT_SSH_COMMAND替换认证/传输命令执行父进程指定的 helper 代码GIT_TERMINAL_PROMPT0让远端认证失败快速报错而不是挂起在交互式提示上。文档解释了清洗动机这些变量可以“把 Git 重定向到别的仓库、注入配置或执行 helper 命令因此 WebUI 不信任父进程继承下来的值”。GIT_INDEX_FILE之所以保留就是为了第三节所述的临时索引提交。值得强调的一点仓库提供的加固配置不是通过GIT_CONFIG_COUNT/GIT_CONFIG_KEY_n环境变量注入的——恰恰相反该命名空间在父环境中被清除后由 WebUI 自己为本次调用重建api/workspace_git.py#L234-L238注释说明这样做是为了防止攻击者可控的 filter/section 名中的注入配置。五、仓库级加固把“仓库配置”当作不可信输入文档的信任模型章节是理解这部分加固的关键。原文警告docs/workspace-git.md一旦允许变更型 Git 操作浏览器动作就能在挂载的工作区里执行 Git 命令。某些 Git 命令还会执行.git/hooks/或core.hooksPath中的钩子代码。这些钩子以 WebUI 进程用户、进程权限与环境运行。应把“下载或执行代码”的钩子视为 WebUI 进程用户的代码执行。在此信任模型之上当前源码对每次workspace Git 调用都追加一组-c加固参数_GIT_HARDENED_CONFIGapi/workspace_git.py#L49-L68其注释逐条说明了针对的向量加固项值目的源码注释core.fsmonitorfalse仓库可能配置外部 fsmonitor 命令读取/状态调用不应触发它core.sshCommandssh强制使用系统 ssh 二进制覆盖指向攻击者 helper 的仓库本地core.sshCommand不用空值是因为会破坏合法 ssh fetchcore.askPass禁用 askpass 命令credential.helper禁用仓库本地凭据 helper即文档“凭据 helper 与 askpass 被禁用”的实现protocol.ext.allownever禁止ext::协议扩展core.gitProxy中和git://远端 fetch 时可触达的外部代理命令submodule.recurse/fetch.recurseSubmodulesfalse防止子模块递归进入嵌套仓库、触发钩子或从攻击者控制的 submodule URL fetch破坏性操作额外叠加一组_GIT_DESTRUCTIVE_HARDENED_CONFIGapi/workspace_git.py#L69-L79commit.gpgSignfalse、push.gpgSignfalse、gpg.program、gpg.ssh.program、gpg.x509.program、core.alternateRefsCommand即禁用签名 helper 命令解析与自定义 refs 命令。文档还提到“WebUI 不绕过 Git hooks”这一信任模型前提而当前源码在此基础上更进了一步对破坏性操作以及强制加固的 fetch_run_git()会把core.hooksPath指向一个临时的空目录api/workspace_git.py#L231-L233使仓库本地钩子含reference-transaction等在 WebUI 进程中不会执行CHANGELOG 中 #3777 条目与test_git_commit_skips_repo_local_hooks_when_destructive_mode_enabled等测试tests/test_workspace_git.py#L1318证实了该行为。换言之文档描述的是“必须按最坏情况假设”的信任基线当前实现则把基线中最高危的一条仓库钩子即代码执行在 WebUI 侧直接切断——阅读文档的 hooks 章节时应结合这一实现演进来理解。同类思路还覆盖了仓库定义的三类“可执行程序”配置且都是先枚举仓库本地--local/--worktree作用域声明的名字再逐条覆盖filter.name.clean/smudge/process读路径统一降级为catraw-byte 可见性优先于执行仓库程序requiredfalsemerge.name.driver替换为 Git 自带三方合并二进制git merge-file %A %O %B防止 stash 恢复时调用工作区可控的 helperremote.name.uploadpack/receivepack恢复为git-upload-pack/git-receive-pack并在fetch/pull/push命令中追加--upload-packgit-upload-pack/--receive-packgit-receive-pack兜底api/workspace_git.py#L426-L443。当仓库定义了本地内容过滤器clean/smudge 会改写字节时会改写工作区内容的操作采取“失败关闭”策略stage、discard、pull、checkout/switch、stash 恢复、selected-commit 的暂存都会抛出filtered_path错误提示改用终端手动操作api/workspace_git.py#L452-L454 的_block_filtered_destructive_write而不是静默写入未过滤内容。六、与活跃运行的协调active_stream 与仓库级锁文档描述了两道并发防线路由层与模块层各承担一道活跃流门禁。变更型 Git 操作在同一会话存在进行中的流live stream时直接拒绝而不是与可能正在同一工作区写文件的 agent 竞争。实现见_git_locked_by_active_stream()检查会话的active_stream_id是否仍在STREAMS注册表中api/routes.py#L24860-L24870命中则返回 409 active_stream。测试test_git_commit_route_rejects_active_streamtests/test_workspace_git.py#L2309验证了 commit 路由在该场景下的拒绝行为。每仓库锁。git_stash_and_checkout之外的每个变更入口都在_git_mutation_lock(ctx)中执行api/workspace_git.py#L144-L157。锁以repo_root字符串为键同一仓库包括其中多个 worktree的变更串行化而不同 worktree 共享的 Git 元数据仍由 Git 自身锁保护。锁等待超时时间为 60 秒超时后抛出operation_in_progress“Another Git operation is still running”。七、Hook、远端与错误分类结构化失败而不是吞掉错误远端操作语义与 docs/workspace-git.md 的 Hook and remote behavior 章节对应源码佐证如下pull固定使用--ff-only --no-recurse-submodulesapi/workspace_git.py#L1713永不产生 merge commitpush保留 Git 原生 non-fast-forward 保护且当前分支无 upstream 时自动push -u origin branchapi/workspace_git.py#L1729-L1736non-FF 拒绝被单独分类为non_fast_forward与一般性 Git 失败区分开凭据 helper 与 askpass 被禁用后依赖存储凭据 helper 的私有 HTTPS 远端在 WebUI 中 fetch/pull/push 可能失败——文档给出的建议是使用 SSH 远端或其他外部认证的传输。错误分类。所有失败都通过GitWorkspaceError(message, code)携带稳定错误码返回分类逻辑在_classify_git_error()api/workspace_git.py#L160-L191路由层_git_bad()将其序列化为{error, code}JSON。完整错误码集合错误码触发条件timeoutGit 命令超时5s / 60s / commit 10smissing_git系统未安装 Gitnot_a_repo工作区不是 Git 仓库path_outside_workspace路径越过工作区或仓库根auth_failed认证失败 / 权限拒绝no_upstream无 upstream / 无配置 push 目的地non_fast_forwardpush 被非快进保护拒绝conflict合并冲突 / unmerged 状态dirty_worktree工作区不干净如 checkout 被未提交改动阻塞invalid_ref非法引用 / 未知 revision / 分支名格式错误hook_failed钩子失败文档要求钩子失败返回结构化 Git 错误而非隐藏operation_in_progress同仓库另一个变更操作仍在进行active_stream会话有进行中的 agent 流destructive_git_disabled破坏性操作被环境变量门禁封锁403filtered_path仓库定义了本地内容过滤器内容改写操作失败关闭git_failed兜底八、部署与验证要点小结只读部署推荐默认不设置HERMES_WEBUI_WORKSPACE_GIT_DESTRUCTIVE。浏览器可 status / branches / diff / fetch / 提交信息建议一切写操作收到 403destructive_git_disabled。可信用户部署在 WebUI 进程环境中设置HERMES_WEBUI_WORKSPACE_GIT_DESTRUCTIVE1或true/yes/on。此时需接受文档给出的信任模型仓库内.git/hooks/与core.hooksPath代码按 WebUI 进程用户执行来评估当前实现已额外对破坏性操作重定向 hooks 到空目录、中和仓库本地 fsmonitor/sshCommand/askPass/credential/gitProxy/协议扩展/子模块递归等可执行配置。私有 HTTPS 远端凭据 helper 被禁用建议改用 SSH 远端或外部认证传输。回归验证tests/test_workspace_git.py是这一层的主测试文件覆盖 status 噪音过滤CRLF / filemode、嵌套工作区作用域、diff 尺寸上限、所选提交与重命名语义、非快进与 upstream 行为、stash-checkout 恢复与冲突保留、环境清洗与仓库本地 helper 中和、路由级 active_stream 拒绝等。tests/test_git_subprocess_windows_flags.py则验证 Windows 下子进程标志。这套设计的整体脉络可以概括为一句话把“浏览器发起的 Git 操作”视为来自不可信前端的请求把“被挂载的工作区仓库”视为可能包含恶意配置的不可信输入然后在默认能力、显式开关、路径作用域、环境清洗、仓库配置加固、并发协调六个层面逐层设防——这正是 docs/workspace-git.md 所述信任模型在 api/workspace_git.py 与 api/routes.py 中的完整落地。【免费下载链接】hermes-webuiHermes WebUI: The best way to use Hermes Agent from the web or from your phone!项目地址: https://gitcode.com/GitHub_Trending/he/hermes-webui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表