From 56fb3a754715f3bae3f95352dd7d87bbac42caab Mon Sep 17 00:00:00 2001 From: chengma Date: Tue, 14 Jul 2026 12:00:53 +0800 Subject: [PATCH] feat(gitea): establish MCP security baseline (phase 0) --- .gitignore | 7 ++++ README.md | 5 ++- docs/README.md | 1 + docs/gitea-mcp.md | 79 ++++++++++++++++++++++++++++++++++++ gitea.env.example | 10 +++++ scripts/gitea-mcp.ps1 | 94 +++++++++++++++++++++++++++++++++++++++++++ tasks.md | 11 +++++ 7 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 docs/gitea-mcp.md create mode 100644 gitea.env.example create mode 100644 scripts/gitea-mcp.ps1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c35cda9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Local Gitea MCP credentials and diagnostics must never enter Git. +.codex/gitea.env +gitea.env +gitea.env.* +!gitea.env.example +*.stderr.log + diff --git a/README.md b/README.md index 4a80b9e..bc61315 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ | [`tasks.md`](tasks.md) | 本样本库自身的维护任务列表 | | [`init.sh`](init.sh) | 标准启动与验证入口脚本(Unix shell / WSL / Git Bash),统一安装 + 验证 + 打印启动命令 | | [`init.ps1`](init.ps1) | 标准启动与验证入口脚本(Windows 原生 PowerShell),与 `init.sh` 等价,按操作系统二选一 | +| [`gitea.env.example`](gitea.env.example) | Gitea MCP 本机私有配置示例;复制后替换,真实文件不得入库 | | [`progress.md`](progress.md) | 可选:历史归档 / 项目级大事记;执行记录默认写各任务文件 | | [`docs/README.md`](docs/README.md) | 文档导航,总览所有项目文档 | | [`docs/00-ai-start-here.md`](docs/00-ai-start-here.md) | AI coding agent 的入口、阅读顺序、任务领取规则 | @@ -28,6 +29,7 @@ | [`docs/routes.md`](docs/routes.md) | 页面路由、组件归属、导航规则 | | [`docs/clean-state-checklist.md`](docs/clean-state-checklist.md) | 会话收尾检查清单,保证下一轮无需人工修复即可开工 | | [`docs/current-state.md`](docs/current-state.md) | 当前实现状态快照,防止计划和代码现实脱节 | +| [`docs/gitea-mcp.md`](docs/gitea-mcp.md) | 可选:Gitea MCP 共享文档与任务协调接入、安全和降级规则 | | [`docs/method-map.md`](docs/method-map.md) | 失败模式 → 首要修复 → 工件的诊断 / 导航对照表 | | [`docs/evaluator-rubric.md`](docs/evaluator-rubric.md) | 单次会话输出的结构化评审评分表 | | [`docs/quality-document.md`](docs/quality-document.md) | 代码库长期健康度追踪(产品域 × 架构层评级) | @@ -67,6 +69,7 @@ 项目进入多轮长期开发后,再按需启用: +- `docs/gitea-mcp.md`:需要跨 agent 读取 Gitea 文档、Issue 和 PR 时启用。 - `docs/clean-state-checklist.md`:每轮结束前检查仓库是否可恢复。 - `docs/method-map.md`:遇到失败模式时定位该补哪个工件。 - `docs/evaluator-rubric.md`:评审单次 agent 输出质量。 @@ -74,4 +77,4 @@ `tasks.md` 是本样本库自身的维护任务;复制到新项目后,项目任务默认以一任务一文件写在 `docs/tasks/`,阶段路线图维护在 `docs/06-tasks.md`。 -核心原则:文档不是给人看的装饰,而是给 agent 执行时用的约束、事实来源和验收标准。 \ No newline at end of file +核心原则:文档不是给人看的装饰,而是给 agent 执行时用的约束、事实来源和验收标准。 diff --git a/docs/README.md b/docs/README.md index f1dc1e2..3acee1b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,6 +28,7 @@ - [API 合约](api.md):前后端接口形状、错误格式、鉴权约定。 - [路由与页面结构](routes.md):页面路由、页面职责、组件归属。 - [当前实现状态](current-state.md):可覆盖的当前快照,记录仓库现实状态、可运行命令和下一步可做任务。 +- [Gitea MCP 接入](gitea-mcp.md):可选的共享文档、Issue / PR 协调、安全配置和断连降级规则。 - [收尾检查清单](clean-state-checklist.md):会话结束前逐项检查,保证下一轮无需人工修复即可继续。 - [方法对照表](method-map.md):失败模式 → 首要修复 → 工件;出问题先查这里对症补工件。 - [评审评分表](evaluator-rubric.md):单次会话输出的结构化评审(6 维 0-2 分 + 校准说明)。 diff --git a/docs/gitea-mcp.md b/docs/gitea-mcp.md new file mode 100644 index 0000000..9b6524e --- /dev/null +++ b/docs/gitea-mcp.md @@ -0,0 +1,79 @@ +# Gitea MCP 接入 + +> 可选增强:让 agent 通过 Gitea 读取共享文档、Issue、分支和 PR。Git checkout 仍是本地编辑与离线降级入口,MCP 不取代 Git。 + +## 适用边界 + +- Gitea Git 仓库保存版本化文档和代码。 +- Gitea Issue / PR 保存实时协调状态。 +- Gitea MCP 提供受控的远端读取和写入工具。 +- `AGENTS.md`、`docs/00-ai-start-here.md` 等最小启动文件仍保留在项目 checkout 中。 + +## 私有配置 + +从根目录 [`gitea.env.example`](../gitea.env.example) 复制一份到 `$HOME/.codex/gitea.env`,替换示例值。也可用 `GITEA_ENV_FILE` 指向其他本机私有路径: + +```text +GITEA_URL=【Gitea 实例根地址,不含 /api/v1】 +GITEA_TOKEN=【最小权限 Personal Access Token】 + +# 仅当团队明确接受 HTTP 下 Token 明文传输风险时设置: +GITEA_ALLOW_INSECURE_HTTP=1 + +# 仅当该实例必须绕过本机代理直连时设置: +GITEA_DIRECT=1 +``` + +规则: + +- 不把 `gitea.env`、Token、Authorization header、私有实例地址提交到仓库或粘贴到 Issue。 +- Token 一旦出现在聊天、日志或提交历史中,立即撤销并轮换。 +- 推荐 HTTPS;如果项目长期使用 HTTP,必须在项目安全决策中记录风险接受人、网络边界和轮换策略。 +- `GITEA_URL` 填实例根地址;`gitea-mcp` 会自动追加 `/api/v1`。 + +## Codex 配置 + +复制 [`../scripts/gitea-mcp.ps1`](../scripts/gitea-mcp.ps1) 到稳定的本机路径,然后在全局 `~/.codex/config.toml` 或可信项目的 `.codex/config.toml` 注册: + +```toml +[mcp_servers.gitea] +command = "pwsh.exe" +args = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "【gitea-mcp.ps1 的绝对路径】"] +default_tools_approval_mode = "writes" +startup_timeout_sec = 30 +tool_timeout_sec = 60 +``` + +包装脚本固定使用 `gitea-mcp==0.5.1`,避免 `uvx` 自动升级造成协议或工具集合漂移。升级版本时先在独立分支验证 `initialize`、`tools/list` 和一条只读 API,再更新版本号。 + +## 工具审批 + +默认策略: + +- 自动允许只读:`list_repos`、`read_file`、`list_issues`、`get_issue`、`list_branches`、`list_pull_requests`。 +- 写入前确认:`create_issue`、`update_issue`、`add_comment`、`create_branch`、`commit_changes`、`create_pr`。 +- 破坏性动作再次确认:`merge_pr`、关闭 Issue、覆盖文件、批量操作。 + +如果 Codex 版本支持 `enabled_tools` / `disabled_tools`,应再用 allowlist 收窄工具,而不是只依靠提示词。 + +## 本地验证 + +只检查文件格式,不连接 Gitea、不显示 Token: + +```powershell +./scripts/gitea-mcp.ps1 -CheckConfig +``` + +连接预检: + +```powershell +./scripts/gitea-mcp.ps1 doctor +``` + +预检至少确认:实例可达、Token 有效、当前用户正确、MCP 版本固定。失败时查看系统临时目录中的 `gitea-mcp-.stderr.log`;日志不得复制 Token 或敏感正文。 + +## 降级规则 + +- Gitea / MCP 不可用:允许继续已领取任务的本地工作,不允许领取新任务或猜测远端状态。 +- 恢复连接后:先拉取默认分支并重新读取任务 Issue,再提交或更新状态。 +- MCP 读取结果与本地 checkout 冲突:以明确记录的提交 SHA 为比较基准,不静默覆盖本地未提交改动。 diff --git a/gitea.env.example b/gitea.env.example new file mode 100644 index 0000000..1cfb3fc --- /dev/null +++ b/gitea.env.example @@ -0,0 +1,10 @@ +# Copy to $HOME/.codex/gitea.env and replace every example value locally. +GITEA_URL=http://gitea.example.invalid:3000 +GITEA_TOKEN=【replace-with-minimum-scope-token】 + +# Required only when the team explicitly accepts HTTP credential exposure risk. +GITEA_ALLOW_INSECURE_HTTP=1 + +# Optional: bypass local HTTP/SOCKS proxy for this Gitea instance. +GITEA_DIRECT=1 + diff --git a/scripts/gitea-mcp.ps1 b/scripts/gitea-mcp.ps1 new file mode 100644 index 0000000..a9d50ba --- /dev/null +++ b/scripts/gitea-mcp.ps1 @@ -0,0 +1,94 @@ +#!/usr/bin/env pwsh + +[CmdletBinding()] +param( + [string]$EnvFile = $( + if ($env:GITEA_ENV_FILE) { + $env:GITEA_ENV_FILE + } else { + Join-Path $HOME ".codex/gitea.env" + } + ), + [string]$Version = "0.5.1", + [switch]$CheckConfig, + [Parameter(ValueFromRemainingArguments = $true)] + [string[]]$ServerArgs +) + +$ErrorActionPreference = "Stop" +$utf8 = [System.Text.UTF8Encoding]::new($false) +[Console]::OutputEncoding = $utf8 +$OutputEncoding = $utf8 + +if (-not (Test-Path -LiteralPath $EnvFile -PathType Leaf)) { + throw "Gitea MCP 配置文件不存在:$EnvFile" +} + +$values = @{} +foreach ($rawLine in Get-Content -LiteralPath $EnvFile) { + $line = $rawLine.Trim() + if (-not $line -or $line.StartsWith("#")) { + continue + } + + $pair = $line -split "=", 2 + if ($pair.Count -ne 2) { + throw "Gitea MCP 配置行必须使用 KEY=VALUE 格式。" + } + + $values[$pair[0].Trim()] = $pair[1].Trim() +} + +foreach ($name in @("GITEA_URL", "GITEA_TOKEN")) { + if (-not $values.ContainsKey($name) -or [string]::IsNullOrWhiteSpace($values[$name])) { + throw "$name 未配置或为空。" + } +} + +try { + $giteaUri = [Uri]$values["GITEA_URL"] +} catch { + throw "GITEA_URL 不是有效 URL。" +} + +if ($giteaUri.Scheme -notin @("http", "https")) { + throw "GITEA_URL 只支持 http 或 https。" +} + +if ($giteaUri.AbsolutePath.Trim("/") -ne "") { + throw "GITEA_URL 必须填写实例根地址,不要包含 /api/v1;gitea-mcp 会自动追加 API 路径。" +} + +if ($giteaUri.Scheme -eq "http" -and $values["GITEA_ALLOW_INSECURE_HTTP"] -ne "1") { + throw "当前使用 HTTP。确认接受 Token 明文传输风险后,在私有配置中设置 GITEA_ALLOW_INSECURE_HTTP=1。" +} + +foreach ($entry in $values.GetEnumerator()) { + if ($entry.Key -like "GITEA_*") { + Set-Item -Path "Env:$($entry.Key)" -Value $entry.Value + } +} + +$noProxyEntries = @($env:NO_PROXY -split "," | ForEach-Object { $_.Trim() } | Where-Object { $_ }) +if ($noProxyEntries -notcontains $giteaUri.Host) { + $noProxyEntries += $giteaUri.Host +} +$env:NO_PROXY = $noProxyEntries -join "," +$env:no_proxy = $env:NO_PROXY + +if ($values["GITEA_DIRECT"] -eq "1") { + foreach ($proxyVariable in @("ALL_PROXY", "all_proxy", "HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy")) { + Remove-Item -Path "Env:$proxyVariable" -ErrorAction SilentlyContinue + } +} + +if ($CheckConfig) { + Write-Output "Gitea MCP 配置有效:URL=$($giteaUri.GetLeftPart([UriPartial]::Authority)),Token 已设置,版本=$Version。" + exit 0 +} + +$uvx = Get-Command uvx -ErrorAction Stop +$stderrLog = Join-Path ([IO.Path]::GetTempPath()) "gitea-mcp-$PID.stderr.log" +& $uvx.Source --from "gitea-mcp==$Version" gitea-mcp @ServerArgs 2>> $stderrLog +exit $LASTEXITCODE + diff --git a/tasks.md b/tasks.md index 8e2329d..2544135 100644 --- a/tasks.md +++ b/tasks.md @@ -88,6 +88,17 @@ Get-ChildItem -Recurse -File | H-502 | 增加评审评分表 `docs/evaluator-rubric.md` | H-403 | 6 维 0-2 分 + Accept/Revise/Block 结论 + 校准说明 | DONE | | H-503 | 增加质量文档 `docs/quality-document.md` | H-106 | 产品域 × 架构层 A-D 评级 + 变更历史,区别于单次输出评审 | DONE | +## Phase 6 · Gitea 多 Agent 共享上下文 + +> 用 Gitea Git 保存版本化事实、Issue / PR 协调实时状态、MCP 按需读取;保持模板通用,不提交实例地址或 Token。 + +| ID | 任务 | 依赖 | 验收要点 | 状态 | +| --- | --- | --- | --- | --- | +| H-601 | Phase 0:建立 Gitea MCP 安全与连接基线 | H-409 | 固定 MCP 版本;私有配置不入库;HTTP 需显式确认风险;读写审批和断连降级规则清楚 | DONE | +| H-602 | Phase 1:增加上下文清单与按需读取流程 | H-601 | 有机器可读清单和无第三方依赖验证;agent 按任务类型读取;同一 SHA 不重复加载 | TODO | +| H-603 | Phase 2:建立 Issue / 任务文件 / PR 多 Agent 协调协议 | H-602 | 任务映射、领取读回校验、分支 / worktree 和写路径防撞规则完整 | TODO | +| H-604 | Phase 3:增加自动化治理与一致性检查 | H-603 | 导航、清单、任务元数据、敏感信息和 Gitea Actions 检查可运行 | TODO | + ## Backlog - 【Tier 3】增加初始化阶段 playbook:第一轮会话产出基线工件 + 第一个 clean commit。