patbond-ai 纳入发布语义(ADR-023 §12)。本仓是四仓中唯一直接持有 AI 服务商凭证的仓库,因此不得成为唯一没有防泄漏门禁的仓库——建仓即配齐, 不等代码落地。 - .gitea/workflows/ci.yml:ai-gates 作业,push(dev) + pull_request 双触发, 与另三仓同构。Secret scan 为第一个 step 且先于任何依赖安装。 pip 走腾讯云镜像(与 patbond-doc 装 mkdocs 同一策略)。 门禁命令 ruff format --check / ruff check / pytest,对应 flutter 仓的 dart format + analyze + test 三步。requirements*.txt 落地前只装工具本身, 使 CI 自建仓第一天即为绿;pytest 无测试时退出码 5 视为通过。 - scripts/check-secrets.sh:四仓同构副本,md5 640994c12bfa78b4512e872d709ed685。 已覆盖 AI 服务商密钥形态(sk-ant- / sk- / sk-proj- / sk-svcacct-)与 api_key/auth_token 实值赋值——本仓正是这些规则的主要适用对象。 - scripts/hooks/pre-commit:与另三仓同构。四仓 core.hooksPath 均已启用, ADR-023 §12 登记的「防泄漏第一层从未生效」至此闭环。 实测验证:含 5 种真实凭证形态的 .env 夹具被钩子拦下,提交未产生。 - .gitignore:Python 产物与工具缓存;凭证载体(.env / credentials* / *.pem / *.key)一律排除,占位样例走 .env.sample(对应脚本的 DENY_NAME_OK)。 - README.md:职责边界(不访问数据库、不持有对象存储凭证、对上游同步异步 一律对外呈现为异步)、与 Java 侧的接口约定(REST 非 gRPC、独立 openapi-internal.yaml、服务身份 JWT 独立密钥对、请求体不含 userId)、 本地门禁命令与凭证纪律。细节以 ADR-023 为准。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
# Gitea Actions 门禁:与本地门禁同一套命令(规范见 patbond-doc
|
||||||
|
# docs/development/git-workflow.md)。零外部 action / 零 GitHub 依赖,
|
||||||
|
# 原因见 patbond-api 的 ci.yml 头注释。
|
||||||
|
# pip 与 PyPI 走腾讯云镜像,与 patbond-doc 的 mkdocs 安装同一策略。
|
||||||
|
#
|
||||||
|
# 本仓是唯一存放 AI 服务商凭证的仓库,因此 Secret scan 是第一个 step,
|
||||||
|
# 且先于任何依赖安装执行(ADR-021、ADR-023 §12)。
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [dev]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ai-gates:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout (manual)
|
||||||
|
run: |
|
||||||
|
git init -q .
|
||||||
|
AUTH_URL=$(echo "${{ github.server_url }}" | sed "s#https://#https://oauth2:${{ github.token }}@#")
|
||||||
|
git remote add origin "$AUTH_URL/${{ github.repository }}.git"
|
||||||
|
git fetch -q --depth 1 origin "+${{ github.ref }}:refs/ci-head"
|
||||||
|
git checkout -q refs/ci-head
|
||||||
|
# 凭证防泄漏兜底(ADR-021):与本地 pre-commit 同一脚本、同一规则表,
|
||||||
|
# 四仓副本内容同构,扫全部已跟踪文件(本次 push 变更的超集)。
|
||||||
|
- name: Secret scan
|
||||||
|
run: sh scripts/check-secrets.sh --all
|
||||||
|
- name: Set up Python
|
||||||
|
run: |
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq --no-install-recommends python3 python3-venv python3-pip
|
||||||
|
python3 -m venv .venv
|
||||||
|
echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
|
||||||
|
echo "PIP_INDEX_URL=https://mirrors.cloud.tencent.com/pypi/simple" >> "$GITHUB_ENV"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install -q --upgrade pip
|
||||||
|
# requirements-dev.txt 落地前,门禁只装 lint/test 工具本身,
|
||||||
|
# 使 CI 从建仓第一天就是绿的而非「等代码来了再配」。
|
||||||
|
if [ -f requirements-dev.txt ]; then
|
||||||
|
pip install -q -r requirements-dev.txt
|
||||||
|
else
|
||||||
|
pip install -q ruff pytest
|
||||||
|
fi
|
||||||
|
if [ -f requirements.txt ]; then pip install -q -r requirements.txt; fi
|
||||||
|
# 与本地门禁同一套命令。ruff 兼任 format 检查与 lint,
|
||||||
|
# 对应 patbond-flutter 的 dart format + flutter analyze 两步。
|
||||||
|
- name: Gates
|
||||||
|
run: |
|
||||||
|
python --version
|
||||||
|
ruff format --check .
|
||||||
|
ruff check .
|
||||||
|
# 尚无测试文件时 pytest 退出码为 5(no tests collected),
|
||||||
|
# 该情形视为通过;真实失败仍会以非 0/5 退出。
|
||||||
|
pytest -q || [ $? -eq 5 ]
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Keep application sources visible even if a global gitignore ignores lib/.
|
||||||
|
!/lib/
|
||||||
|
!/lib/**
|
||||||
|
|
||||||
|
# Virtualenvs
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Tooling caches
|
||||||
|
.pytest_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
htmlcov/
|
||||||
|
coverage.xml
|
||||||
|
|
||||||
|
# 凭证载体一律不入库(ADR-021)。真实值只存在于被忽略的文件中,
|
||||||
|
# 占位样例走 .env.sample / *.example(check-secrets.sh 的 DENY_NAME_OK)。
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.sample
|
||||||
|
!.env.example
|
||||||
|
credentials*
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
|
||||||
|
# 本地产物与临时文件
|
||||||
|
*.log
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
tmp/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# patbond-ai
|
||||||
|
|
||||||
|
Patbond 的 AI 生成服务(Python)。负责调用 AI 服务商 API 完成图片与视频生成,
|
||||||
|
是四仓中唯一直接持有 AI 服务商凭证的仓库。
|
||||||
|
|
||||||
|
架构与边界的完整决策见 **patbond-doc `docs/architecture/decisions.md` ADR-023**。
|
||||||
|
以下为要点,改动前请以 ADR 原文为准。
|
||||||
|
|
||||||
|
## 职责边界
|
||||||
|
|
||||||
|
- **只负责调用 AI 服务商 API。** 不访问数据库——生成任务状态的唯一事实来源是
|
||||||
|
Java 侧的 `creation.generation_jobs`。
|
||||||
|
- **不持有对象存储凭证。** 产物由 Java 下发预签名 PUT 地址,本服务直传后回报,
|
||||||
|
再由 Java 核实并建 `media.assets` 行(即客户端两步上传,本服务站在客户端位置)。
|
||||||
|
- **对上游同步或异步一律对外呈现为异步**:`submit` 立即返回任务 id,同步型
|
||||||
|
服务商(长连接等待返回)在本服务内部以后台任务承载。这样 Java 侧无长连接占线程,
|
||||||
|
且 `provider_request_id` 立即可得,超时不再产生「是否已扣费」的歧义。
|
||||||
|
|
||||||
|
## 与 Java 侧的接口
|
||||||
|
|
||||||
|
- 传输为 **REST(JSON)**,非 gRPC(理由见 ADR-023 §4:避免引入第二套无门禁契约体系)。
|
||||||
|
- 契约为本仓内独立的 **`openapi-internal.yaml`**,独立版本号,
|
||||||
|
**不进** patbond-api 的四模块快照锁,**也不写入**面向客户端的 `openapi.yaml`。
|
||||||
|
- 鉴权为 **服务身份 JWT**(独立密钥对,非用户 token 转发):本服务强制校验
|
||||||
|
`aud=patbond-ai` 与 `typ=service`。
|
||||||
|
- Java 主动轮询本服务查询进度;回调是将来的可选优化,当前不启用。
|
||||||
|
- 请求体**不含 `userId`**(数据最小化):完成工作只需 `jobId`、生成参数与预签名上传地址。
|
||||||
|
|
||||||
|
## 本地门禁
|
||||||
|
|
||||||
|
与 CI 同一套命令(`.gitea/workflows/ci.yml`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sh scripts/check-secrets.sh --all # 凭证防泄漏全量自查
|
||||||
|
ruff format --check .
|
||||||
|
ruff check .
|
||||||
|
pytest -q
|
||||||
|
```
|
||||||
|
|
||||||
|
**每人每仓启用一次** pre-commit 防泄漏钩子:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config core.hooksPath scripts/hooks
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/check-secrets.sh` 是四仓同构副本,**调整规则时四仓同步提交**。
|
||||||
|
它覆盖 AI 服务商密钥形态(`sk-ant-`、`sk-`/`sk-proj-`/`sk-svcacct-`)与
|
||||||
|
`api_key`/`auth_token` 实值赋值——本仓正是这些规则的主要适用对象。
|
||||||
|
|
||||||
|
## 凭证纪律
|
||||||
|
|
||||||
|
真实密钥**只允许存在于被 gitignore 的文件中**(`.env`),占位样例进 `.env.sample`。
|
||||||
|
拦下真实凭证时的第一动作是**去服务商控制台轮换/禁用该密钥**,然后才是清理提交。
|
||||||
|
|
||||||
|
## 分支与发布
|
||||||
|
|
||||||
|
`dev` 直推 + CI 绿,`main` 经 PR 合并。本仓纳入四仓 tag 同步与发布回归
|
||||||
|
(ADR-023 §12)。发布流程见 patbond-doc `docs/development/releases.md`。
|
||||||
|
|||||||
Executable
+108
@@ -0,0 +1,108 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# check-secrets.sh —— 凭证防泄漏检查(ADR-021,规范见 patbond-doc docs/development/git-workflow.md)
|
||||||
|
#
|
||||||
|
# 规则单一来源:本地 pre-commit 与 CI 兜底跑的是同一个脚本、同一张规则表。
|
||||||
|
# 三仓(patbond-api / patbond-flutter / patbond-doc)各存一份同构副本,改规则时三仓同步。
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# sh scripts/check-secrets.sh --staged # pre-commit:扫暂存区内容(经 scripts/hooks/pre-commit 调用)
|
||||||
|
# sh scripts/check-secrets.sh --all # CI 兜底 / 手动自查:扫全部已跟踪文件(缺省模式)
|
||||||
|
# sh scripts/check-secrets.sh <文件...> # 扫指定文件
|
||||||
|
#
|
||||||
|
# 拦下真实凭证时的第一动作:去云控制台轮换/禁用该密钥,然后才是清理提交。
|
||||||
|
set -u
|
||||||
|
|
||||||
|
mode="${1:---all}"
|
||||||
|
|
||||||
|
# 允许清单:行内出现任一形态即放行(${} 注入、占位值、明显示例值)
|
||||||
|
# 注:x{3,}(xxx / xxxx…)是本项目文档描述凭证形态时的既有占位约定,
|
||||||
|
# 放行它使「讲解规则的文档」不会被规则自身拦下(如 iteration-4/08 §漏检清单)。
|
||||||
|
ALLOW='\$\{[^}]*\}|\{\{[^}]*\}\}|changeme|change[-_]me|your[-_][a-zA-Z0-9_-]+|<[a-zA-Z0-9 ,_.-]+>|placeholder|example|sample|dummy|fake|redacted|\*\*\*|x{3,}'
|
||||||
|
|
||||||
|
# 内容扫描跳过:本脚本与 hook 自身(含规则文本,非凭证)
|
||||||
|
SKIP_PATHS='(^|/)scripts/(check-secrets\.sh|hooks/pre-commit)$'
|
||||||
|
|
||||||
|
# 文件名黑名单:凭证载体文件本体禁止入库(.sample/.example 除外)
|
||||||
|
DENY_NAME='(^|/)\.env(\.[^/]+)?$|(^|/)credentials[^/]*$|[Aa]ccess[Kk]eys?[^/]*\.csv$|(^|/)rootkey\.csv$'
|
||||||
|
DENY_NAME_OK='\.(sample|example)$'
|
||||||
|
|
||||||
|
# 规则表:ID<TAB>大小写旗标(i=忽略大小写,-=敏感)<TAB>文件范围ERE(-=全部文件)<TAB>行模式ERE
|
||||||
|
RULES=$(cat <<'EOF'
|
||||||
|
AK-AWS - - AKIA[0-9A-Z]{16}
|
||||||
|
AK-QCLOUD - - AKID[0-9A-Za-z]{16,}
|
||||||
|
AK-ALIYUN - - LTAI[0-9A-Za-z]{12,}
|
||||||
|
AK-ANTHROPIC - - sk-ant-[A-Za-z0-9_-]{16,}
|
||||||
|
AK-OPENAI - - sk-(proj-|svcacct-)?[A-Za-z0-9]{20,}
|
||||||
|
MINIO-DEFAULT i - minio[-_.]?admin
|
||||||
|
PRIVATE-KEY - - ^[[:space:]]*-----BEGIN [A-Z ]*PRIVATE KEY-----[[:space:]]*$
|
||||||
|
KEY-ASSIGN i - (access[-_]?key(_?id)?|secret[-_]?(access[-_]?)?key|api[-_]?(key|secret|token)|auth[-_]?token)["']?[[:space:]]*[:=][[:space:]]*["']?[A-Za-z0-9+/=_-]{8,}
|
||||||
|
JWT-SECRET i - (jwt[-_.]?secret|signing[-_]?key|token[-_]?secret|hmac[-_]?(key|secret))["']?[[:space:]]*[:=][[:space:]]*["']?[A-Za-z0-9+/=_-]{8,}
|
||||||
|
DB-PASSWORD i \.(ya?ml|properties|toml|conf|ini)(\.sample|\.example)?$ (password|passwd|pwd)["']?[[:space:]]*[:=][[:space:]]*["']?[^[:space:]"'$]{6,}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
--staged)
|
||||||
|
files=$(git diff --cached --name-only --diff-filter=ACM)
|
||||||
|
src=index
|
||||||
|
;;
|
||||||
|
--all)
|
||||||
|
files=$(git ls-files)
|
||||||
|
src=worktree
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "用法: $0 [--staged|--all|<文件...>]" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
files=$(printf '%s\n' "$@")
|
||||||
|
src=worktree
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[ -n "$files" ] || exit 0
|
||||||
|
|
||||||
|
tmp=$(mktemp) || exit 2
|
||||||
|
viol=$(mktemp) || exit 2
|
||||||
|
trap 'rm -f "$tmp" "$viol"' EXIT
|
||||||
|
|
||||||
|
# 第一道:文件名黑名单
|
||||||
|
printf '%s\n' "$files" | grep -E "$DENY_NAME" | grep -vE "$DENY_NAME_OK" |
|
||||||
|
sed 's/^/[NAME-DENY] /' >>"$viol" || true
|
||||||
|
|
||||||
|
# 第二道:逐文件逐规则内容扫描(二进制文件经 grep -I 自然跳过)
|
||||||
|
IFS='
|
||||||
|
'
|
||||||
|
for f in $files; do
|
||||||
|
printf '%s' "$f" | grep -qE "$SKIP_PATHS" && continue
|
||||||
|
if [ "$src" = index ]; then
|
||||||
|
git show ":$f" >"$tmp" 2>/dev/null || continue
|
||||||
|
else
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
cat -- "$f" >"$tmp"
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$RULES" | while IFS="$(printf '\t')" read -r id flag scope pat; do
|
||||||
|
[ -n "$id" ] || continue
|
||||||
|
if [ "$scope" != "-" ]; then
|
||||||
|
printf '%s' "$f" | grep -qE "$scope" || continue
|
||||||
|
fi
|
||||||
|
ci=""
|
||||||
|
[ "$flag" = "i" ] && ci="-i"
|
||||||
|
grep -InE $ci -e "$pat" "$tmp" 2>/dev/null | grep -viE "$ALLOW" |
|
||||||
|
sed "s|^|[$id] $f:|" >>"$viol" || true
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -s "$viol" ]; then
|
||||||
|
echo "凭证防泄漏检查未通过(ADR-021)——以下内容疑似真实凭证:" >&2
|
||||||
|
cat "$viol" >&2
|
||||||
|
cat >&2 <<'MSG'
|
||||||
|
处置:
|
||||||
|
1. 若是真实凭证:先去云控制台轮换/禁用该密钥,再从提交中移除;
|
||||||
|
2. 若是误报:改用 ${} 注入或占位值(changeme / your-xxx / <占位>),
|
||||||
|
或与团队确认后调整三仓同构的 scripts/check-secrets.sh 规则表。
|
||||||
|
敏感信息只允许存在于被 gitignore 的文件或 .sample 占位中(git-workflow.md)。
|
||||||
|
MSG
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# pre-commit —— 凭证防泄漏(ADR-021)。启用(每人每仓一次):
|
||||||
|
# git config core.hooksPath scripts/hooks
|
||||||
|
# 注意:core.hooksPath 会整体接管 hooks 目录;本仓无其他自定义 hook。
|
||||||
|
repo_root=$(git rev-parse --show-toplevel) || exit 1
|
||||||
|
exec sh "$repo_root/scripts/check-secrets.sh" --staged
|
||||||
Reference in New Issue
Block a user