diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index f3711fc55..0f48bce60 100755 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -84,6 +84,104 @@ jobs: python3 -m pip --version echo "CI environment is ready" + + - name: Secret detection (gitleaks) + shell: sh + run: | + set -eu + echo "=== Installing gitleaks ===" + GITLEAKS_VERSION="v8.18.4" + GITLEAKS_ARCH="linux_x64" + GITLEAKS_FILE="gitleaks_${GITLEAKS_VERSION#v}_${GITLEAKS_ARCH}.tar.gz" + GITHUB_BASE="https://github.com/gitleaks/gitleaks/releases/download/${GITLEAKS_VERSION}/${GITLEAKS_FILE}" + + # 国内镜像源(按大致稳定性排序) + MIRRORS=" + https://gh-proxy.com/${GITHUB_BASE} + https://ghproxy.net/${GITHUB_BASE} + https://hub.gitmirror.com/${GITHUB_BASE} + https://ghps.cc/${GITHUB_BASE} + https://mirror.ghproxy.com/${GITHUB_BASE} + ${GITHUB_BASE} + " + + INSTALLED=false + for url in $MIRRORS; do + echo "Trying: $url" + if curl -fsSL --connect-timeout 8 --max-time 90 --retry 2 --retry-delay 3 \ + -o /tmp/gitleaks.tar.gz "$url" 2>/dev/null; then + echo "Download successful from: $url" + if tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks 2>/dev/null; then + chmod +x /tmp/gitleaks + /tmp/gitleaks version + INSTALLED=true + break + else + echo "Download OK but tar extraction failed, trying next..." + fi + else + echo "Download failed from: $url, trying next..." + fi + done + + # Fallback: 尝试 go install 从源码编译 + if [ "$INSTALLED" = "false" ] && command -v go >/dev/null 2>&1; then + echo "All binary mirrors failed, trying go install..." + if go install github.com/gitleaks/gitleaks/v8@${GITLEAKS_VERSION} 2>/dev/null; then + GOPATH_BIN="$(go env GOPATH)/bin" + if [ -x "$GOPATH_BIN/gitleaks" ]; then + cp "$GOPATH_BIN/gitleaks" /tmp/gitleaks + chmod +x /tmp/gitleaks + /tmp/gitleaks version + INSTALLED=true + echo "Installed via go install" + fi + fi + fi + + if [ "$INSTALLED" = "false" ]; then + echo "WARNING: Failed to install gitleaks from all sources" + echo "gitleaks 安装失败,密钥检测跳过(告警模式,不阻断流水线)" + echo "请检查Runner网络或手动安装gitleaks到Runner" + exit 0 + fi + echo "" + echo "=== Running gitleaks scan ===" + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "PR mode: scanning changed files (origin/${{ github.base_ref }}..HEAD)" + set +e + /tmp/gitleaks detect \ + --source . \ + --config .gitleaks.toml \ + --verbose \ + --exit-code 1 \ + --log-opts="origin/${{ github.base_ref }}..HEAD" + GITLEAKS_EXIT=$? + set -e + else + echo "Push mode: full repository scan" + set +e + /tmp/gitleaks detect \ + --source . \ + --config .gitleaks.toml \ + --verbose \ + --exit-code 1 + GITLEAKS_EXIT=$? + set -e + fi + if [ "$GITLEAKS_EXIT" = "1" ]; then + echo "" + echo "==========================================" + echo "ERROR: Secrets detected!" + echo "==========================================" + echo "If these are false positives, add them to .gitleaks.toml allowlist." + exit 1 + elif [ "$GITLEAKS_EXIT" != "0" ]; then + echo "WARNING: gitleaks exited with code $GITLEAKS_EXIT (non-zero but not detection failure)" + echo "This may indicate a configuration issue. Continuing for now..." + else + echo "gitleaks scan completed - no secrets detected" + fi - name: Install dependencies shell: sh run: | @@ -97,6 +195,38 @@ jobs: bandit --version pytest --version + + - name: Python dependency vulnerability scan (pip-audit) + shell: sh + run: | + set -eu + echo "=== Installing pip-audit ===" + python3 -m pip install -q pip-audit + pip-audit --version + echo "" + echo "=== Scanning Python dependencies ===" + set +e + HAS_VULN=0 + for req_file in requirements.txt requirements-base.txt requirements-dev.txt requirements-worker.txt; do + if [ -f "$req_file" ]; then + echo "--- Scanning $req_file ---" + pip-audit -r "$req_file" --desc on 2>&1 | head -30 + EXIT_CODE=${PIPESTATUS[0]:-0} + if [ "$EXIT_CODE" = "1" ]; then + HAS_VULN=1 + fi + echo "" + fi + done + set -e + echo "=== Summary ===" + if [ "$HAS_VULN" = "1" ]; then + echo "WARNING: Vulnerabilities found in dependencies." + else + echo "No known vulnerabilities found in scanned requirements." + fi + echo "Mode: advisory only (not blocking CI)" + exit 0 - name: Run code quality checks shell: sh run: | diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 000000000..6898591e6 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,71 @@ +# .gitleaks.toml - gitleaks 白名单配置 +# 仓库: xiaoxia/xiaoxia-saas +# 用途: 排除已知的测试密钥、示例配置等误报 +# 注意: paths 使用正则表达式(Regex),不是 glob 语法 + +# 允许路径/文件排除 +[allowlist] +description = "全局白名单 - 排除示例配置和测试文件" +paths = [ + # 环境配置示例(无真实密钥) + '.env.example', + '.env.sample', + '\.env\.example$', + '\.env\.sample$', + # 测试文件 + 'tests/', + 'test/', + # 文档 + 'docs/', + '\.md$', + '\.rst$', + # 前端依赖 + 'node_modules/', + # Python包 + 'site-packages/', + # 锁定文件(自动生成) + 'poetry.lock', + 'Pipfile.lock', + 'requirements.*\.txt\.lock', + # CI配置本身 + '.gitea/', + # Docker相关 + 'docker-compose.*\.yml', + # gitleaks配置自身 + '.gitleaks.toml', + # vulture配置 + 'vulture.conf', + 'vulture_whitelist.py', + # 前端构建产物 + 'dist/', + 'build/', + # 图片/二进制文件 + '\.png$', + '\.jpg$', + '\.jpeg$', + '\.gif$', + '\.ico$', + '\.svg$', + '\.woff$', + '\.woff2$', + '\.ttf$', + # 项目文档 + 'README', + 'CHANGELOG', + 'LICENSE', +] + +# 允许的密钥值/占位符正则 +# 使用三引号字符串避免引号转义问题 +regexes = [ + '''(?i)(your[_-]?password|your[_-]?secret|your[_-]?key|your[_-]?token|changeme|change[_-]?me|placeholder|example[_-]?key|test[_-]?key|dummy|fake|mock|xxx|none|not[_-]?set|TODO|FIXME)''', + '''postgresql://[^:]+:changeme@''', + '''postgresql://[^:]+:your-password@''', + '''postgresql://[^:]+:password@localhost''', + '''redis://:changeme@''', + '''redis://:your-redis-password@''', + '''(?i)jwt[_-]?secret\s*[:=]\s*["']?(your[_-]?jwt|change|placeholder|secret|example)''', + '''postgresql://postgres:postgres@''', + '''redis://localhost''', + '''mysql://root:root@''', +] diff --git a/docs/ci/first-security-scan-briefing.md b/docs/ci/first-security-scan-briefing.md new file mode 100644 index 000000000..2ca759ee4 --- /dev/null +++ b/docs/ci/first-security-scan-briefing.md @@ -0,0 +1,137 @@ +# 首次安全扫描简报 + +> 仓库: xiaoxia/xiaoxia-saas +> 扫描时间: 2026-07-13 +> 负责人: 代码审计 Agent +> 状态: CI 排队中(Runner 环境修复中),gitleaks 下载链路二次优化已提交,待 CI 跑通后获取真实扫描数据 + +## 一、概览 + +| 工具 | 优先级 | PR | 接入状态 | CI 验证 | 扫描结果 | +|---|---|---|---|---|---| +| gitleaks(密钥检测) | P0 | [#256](https://git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/pulls/256) | ✅ 二次优化提交 | 🕐 CI 排队中 | 待验证 | +| pip-audit(Python 依赖漏洞) | P1 | [#256](https://git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/pulls/256) | ✅ 代码已提交 | 🕐 CI 排队中 | 待验证 | +| vulture(死代码检测) | P2 | [#259](https://git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/pulls/259) | ✅ 代码已提交 | 🕐 等待 black 修复 + CI 排队 | 待验证 | + +## 二、各工具详情 + +### 1. gitleaks 密钥检测(P0) + +**接入配置** +- 位置:validate Job 第 3 步(Verify CI environment 之后) +- PR 模式:增量扫描(`--log-opts="origin/base..HEAD"`),只扫描改动文件 +- Push 模式:全量扫描 +- 阻断策略:发现密钥直接阻断合并(exit code 1) +- 白名单:`.gitleaks.toml`,排除以下路径/内容: + - `.env.example`、示例配置文件 + - `tests/`、`docs/`、`node_modules/`、`site-packages/` + - 锁定文件(poetry.lock 等) + - 占位符字符串(`your-password`、`changeme`、`placeholder` 等) + +**下载问题(第二轮修复已提交)** +- 第一轮修复(ghproxy + 99988866 + GitHub 直连):全部失败 + - `mirror.ghproxy.com`: 连接超时(7.7s) + - `gh.api.99988866.xyz`: SSL 握手失败 + - GitHub 直连: 120s 超时,仅下载 1.6MB/2.9MB +- 第二轮修复(6 个镜像 + go install 降级): + - 新增 4 个国内镜像:`gh-proxy.com`、`ghproxy.net`、`hub.gitmirror.com`、`ghps.cc` + - 每个镜像重试 2 次,connect-timeout 8s,max-time 90s + - 增加 `go install` 源码编译降级方案 + - 全部失败时告警跳过(不阻断 CI),避免阻塞开发流程 +- 修复 commit:`fix(ci): add more Chinese mirrors for gitleaks download + graceful degradation` + +**长期建议**:在 Runner 镜像中预装 gitleaks 二进制,彻底避免下载问题 + +**CI 状态** +- 当前状态:大量 CI 任务排队中(9 个 Runner 在线但任务堆积) +- 历史失败 Run #4071(旧 Runner):gitleaks 下载失败(见上) +- 历史失败 Run #4075(新 Runner):Job in_progress 但步骤全 queued(新 Runner 执行环境问题) +- 最新 Run #4096:queued 状态,等待执行 +- 构建运维 Agent 正在修复:Runner 标签匹配 + 新 Runner 执行环境 + 并发优化 + +### 2. pip-audit Python 依赖漏洞扫描(P1) + +**接入配置** +- 位置:validate Job 第 5 步(Install dependencies 之后) +- 扫描范围:`requirements.txt`、`requirements-base.txt`、`requirements-dev.txt`、`requirements-worker.txt` +- 数据源:OSV(PyPA 官方推荐) +- 阻断策略:告警模式,不阻断 CI +- 计划:运行 1-2 周摸清漏洞存量后,按严重等级设置阻断阈值 + +**CI 状态** +- 同 PR #256,因 Runner 卡住暂未执行 + +### 3. vulture 死代码检测(P2) + +**接入配置** +- 位置:validate Job,Run security scan (bandit) 之后 +- 置信度阈值:80% +- 扫描范围:`alembic/`、`apps/`、`packages/`、`scripts/` +- 排除:测试文件、迁移文件、文档、node_modules、site-packages +- 白名单:框架自动调用代码 + - FastAPI routes / dependencies / middleware + - SQLAlchemy models / Pydantic schemas + - Celery tasks + - Alembic migration functions + - CLI scripts / 工具函数 +- 阻断策略:告警模式,不阻断 CI + +**CI 状态** +- Workflow Run #4054 +- 失败原因:`scripts/check_migration_safety.py` 不符合 black 格式 + ``` + would reformat scripts/check_migration_safety.py + 1 file would be reformatted, 376 files would be left unchanged. + ``` +- 说明:非 vulture 引入的问题(vulture 步骤还没执行到),是其他 Agent 修改了迁移安全检查脚本但没跑 black 格式化 +- 建议:后端开发 Agent 在迁移安全 PR 中同步修复 black 格式问题 + +## 三、PR #264(前端清理)安全审查 + +**审查结论:✅ 无安全风险,可合并** + +**审查范围**:PR #264 `cleanup/phase3-frontend` → `develop`,6 个文件变更(+261/-1359 行) + +**检查项**: +| 检查项 | 结果 | 说明 | +|---|---|---| +| 危险 DOM 操作(innerHTML/eval 等) | ✅ 通过 | 未发现 dangerouslySetInnerHTML、eval、document.write 等 | +| 硬编码密钥/Token | ✅ 通过 | 未发现 API Key、Secret、Password 等硬编码 | +| 本地存储操作(localStorage 等) | ✅ 通过 | 未新增本地存储操作 | +| 开放重定向漏洞 | ✅ 通过 | 未新增 window.location / redirect 操作 | +| 新增第三方依赖 | ✅ 通过 | 仅新增 `@ant-design/icons` 的 DatabaseOutlined 图标 | +| 删除文件安全性 | ✅ 通过 | 删除 `apps/web/src/api/accounts.ts`(Mock API 文件),无安全影响 | + +**核心变更性质**: +- 删除 Mock 数据(accounts.ts),替换为真实 API 调用或占位 +- 清理 Admin.css 冗余样式(-460 行) +- Dashboard / GeneratePage / TitleLibrary 页面 Mock 替换为真实数据调用 +- 整体净删除 1098 行,代码量减少,攻击面缩小 + +## 四、发现的其他 CI 问题 + +### Runner 环境问题 +1. **新服务器 Runner 卡住**:`xiaoxia-ci-runner-new-2` 上的 Job 一直停留在 queued 状态,无法执行步骤 +2. **Unit Tests 快速失败**:Unit Tests Job 18 秒就失败了,可能是环境/依赖问题,非代码问题 +3. **Integration Tests 快速失败**:24 秒失败,同样可能是环境问题 + +### 代码质量预存问题 +1. `scripts/check_migration_safety.py` 不符合 black 格式(可能是后端开发刚改动过) + +## 五、下一步计划 + +1. **等待 CI 环境修复**:构建运维修复 Runner 标签匹配 + 执行环境 + 并发配置后,CI 才能正常运行 +2. **收集首次扫描数据**:CI 跑通后,第一时间整理 gitleaks / pip-audit / vulture 的首次真实扫描结果 +3. **根据结果调优白名单**:如有误报,及时更新 `.gitleaks.toml` 和 `vulture_whitelist.py` +4. **跟进 PR #259 black 格式问题**:等待后端开发修复 `check_migration_safety.py` 后 rebase 验证 vulture +5. **推动 gitleaks 预装**:建议在 Runner 镜像中预装 gitleaks,彻底规避下载链路问题 +6. **推进 npm audit**:前端开发 PR #260 已提交 npm audit,等待 CI 验证 +7. **PR #264 合并**:安全审查通过,CI 跑通后可合并 + +## 六、相关文档 + +- [安全工具接入方案](docs/ci/代码安全扫描CI集成方案_report.md) +- [安全工具路线图](docs/ci/security-scanning-roadmap.md) +- [gitleaks 白名单配置](../.gitleaks.toml) +- [vulture 配置](../vulture.conf) +- [vulture 白名单](../vulture_whitelist.py) diff --git a/docs/ci/security-scanning-roadmap.md b/docs/ci/security-scanning-roadmap.md new file mode 100644 index 000000000..502f6752b --- /dev/null +++ b/docs/ci/security-scanning-roadmap.md @@ -0,0 +1,105 @@ +# 安全与质量扫描工具接入路线图 + +> 仓库: xiaoxia/xiaoxia-saas +> 更新时间: 2026-07-13 +> 负责人: 代码审计 Agent + +## 整体概览 + +``` +Phase 0 (已完成) Phase 1 (进行中) Phase 2 (规划中) Phase 3 (远期) + ───────────── ────────────── ───────────── ──────────── + bandit gitleaks (P0) npm audit (P1) mypy (类型检查) + (已存在) pip-audit (P1) vulture (P2) semgrep (SAST) + 迁移安全检查 依赖自动更新 + License 合规 +``` + +## 各阶段详情 + +### Phase 0: 基线能力(已存在) + +| 工具 | 类型 | 状态 | 说明 | +|---|---|---|---| +| bandit | Python 代码安全审计 | ✅ 已接入 | validate Job 中运行,`-ll` 级别,阻断模式 | +| black | 代码格式化 | ✅ 已接入 | `--check --fast` 模式,阻断模式 | +| isort | 导入排序 | ✅ 已接入 | `--check-only` 模式,阻断模式 | +| flake8 | 代码风格 | ✅ 已接入 | 阻断模式 | +| compileall | 语法检查 | ✅ 已接入 | Python 编译检查 | + +### Phase 1: 安全基础(进行中 / P0-P1) + +预计完成时间:2026-07-13 ~ 2026-07-14 + +#### P0: gitleaks 密钥检测 +- **状态**: ✅ PR 已提交 ([PR #256](https://git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/pulls/256)),CI 验证中 +- **接入位置**: validate Job(最前面,快速反馈) +- **扫描模式**: + - PR 触发:增量扫描(只扫改动文件) + - 主分支 push:全量扫描 +- **阻断策略**: 发现密钥直接阻断合并 +- **配置文件**: `.gitleaks.toml`(白名单排除示例配置、测试文件等) +- **预估收益**: 防止 API Key、密码、Token 等敏感信息泄露 + +#### P1: pip-audit Python 依赖漏洞扫描 +- **状态**: ✅ PR 已提交(同 PR #256),CI 验证中 +- **接入位置**: validate Job(依赖安装后) +- **扫描范围**: 全部 4 个 requirements 文件 +- **数据源**: OSV(PyPA 官方推荐) +- **阻断策略**: 告警模式(初期不阻断) + - 运行 1-2 周摸清漏洞存量 + - 评估后根据严重程度设置阻断阈值(HIGH/CRITICAL 阻断) +- **预估收益**: 及时发现 Python 依赖中的 CVE 漏洞 + +### Phase 2: 质量与前端安全(P1-P2) + +预计启动时间:Phase 1 稳定后(约 2026-07-15 ~ 2026-07-18) + +#### P1: npm audit 前端依赖漏洞扫描 +- **状态**: 📋 规划中(前端开发负责) +- **接入位置**: frontend-lint Job +- **扫描范围**: `package.json` / `package-lock.json` +- **初始阈值**: `--audit-level=high` +- **阻断策略**: 告警模式(初期不阻断) +- **相关 PR**: PR#255(前端缓存优化)后续接入 + +#### P2: vulture 死代码检测 +- **状态**: ✅ PR 已提交 ([PR #259](https://git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/pull/259)) +- **接入位置**: validate Job(安全扫描之后) +- **置信度阈值**: 80%(逐步调高) +- **扫描范围**: `alembic` / `apps` / `packages` / `scripts` +- **白名单**: 框架自动调用代码(FastAPI routes、SQLAlchemy models、Celery tasks 等) +- **阻断策略**: 告警模式(不阻断) + - 运行 1-2 周统计死代码存量 + - 分批清理确认的死代码 + - 稳定后逐步提高置信度并考虑阻断 +- **预估收益**: 减少维护负担,降低安全攻击面 + +### Phase 3: 深度质量(远期规划) + +预计启动时间:Phase 2 完成后(约 2026-07 下旬) + +| 工具 | 类型 | 优先级 | 说明 | +|---|---|---|---| +| mypy | 类型检查 | P1 | 先宽松模式,逐步收紧,提升代码质量 | +| semgrep | SAST 静态分析 | P2 | 自定义规则集,检测特定安全模式 | +| 依赖自动更新 | 依赖管理 | P2 | Dependabot / Renovate,自动更新依赖版本 | +| License 合规 | 合规检查 | P3 | 检查第三方依赖的 License 是否合规 | + +## 接入原则 + +1. **先告警后阻断**:新工具初期一律告警模式,摸清存量后再设阈值 +2. **增量优先**:PR 阶段增量扫描,主分支全量扫描,平衡速度与覆盖 +3. **白名单先行**:提前配置好合理的白名单,减少误报干扰 +4. **分层递进**:P0 → P1 → P2 → P3,按优先级逐步接入 +5. **文档同步**:每个工具接入都同步更新接入方案和最佳实践 + +## 进度跟踪 + +| 阶段 | 工具 | PR | 状态 | 备注 | +|---|---|---|---|---| +| Phase 1 | gitleaks | #256 | 🟡 CI 验证中 | 国内镜像下载修复已提交 | +| Phase 1 | pip-audit | #256 | 🟡 CI 验证中 | 告警模式 | +| Phase 2 | vulture | #259 | 🟡 等待 CI | 80% 置信度,告警模式 | +| Phase 2 | npm audit | - | 📋 规划中 | 前端开发负责 | +| Phase 3 | mypy | - | 📋 规划中 | 后端开发负责(同迁移安全 PR) |