Files
dingtalk-workspace-cli/scripts/ci/eval_poll_validate.py
T
chichuanandClaude Opus 4.6 0a0634cfc2 fix(eval): harden extract_payload against non-dict JSON and invalid field types
Address P1 finding: extract_payload now strictly requires the parsed JSON
to be a dict, and validates each field's type and format:
- pr_number: string of digits
- pr_head_sha: 40-char lowercase hex string
- products: alphanumeric with commas/dots/hyphens/underscores only
- run_id: string of digits
- cases_ref: string (may be empty)

validate_run_id also guards against non-string input.

Added tests for: integer/array/string/null JSON, numeric field types,
invalid SHA format, injection in products, missing required fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-11 20:17:27 +08:00

145 lines
4.7 KiB
Python

"""eval_poll_validate.py — 消费端校验 eval-dispatch 结构化评论的合法性。
三重防伪造校验:
1. comment.user.login == 'github-actions[bot]' — 平台级身份,普通用户不可冒充
2. comment.performed_via_github_app.slug == 'github-actions' — GitHub App 签名
3. payload.run_id 对应本仓库真实成功完成的 workflow run
任何一层不通过即返回 False,拒绝触发内网流水线。
"""
import json
import re
import subprocess
import sys
from typing import Optional
REPO = "DingTalk-Real-AI/dingtalk-workspace-cli"
TRUSTED_BOT_LOGIN = "github-actions[bot]"
TRUSTED_APP_SLUG = "github-actions"
def validate_comment_author(comment: dict) -> bool:
"""校验评论由 github-actions[bot] 通过 GitHub Actions App 发出。"""
user = comment.get("user", {})
if user.get("login") != TRUSTED_BOT_LOGIN:
return False
if user.get("type") != "Bot":
return False
app = comment.get("performed_via_github_app") or {}
if app.get("slug") != TRUSTED_APP_SLUG:
return False
return True
def validate_run_id(run_id, repo: str = REPO) -> bool:
"""校验 run_id 对应真实存在且成功完成的 workflow run。"""
if not isinstance(run_id, str) or not run_id.isdigit():
return False
result = subprocess.run(
["gh", "api", f"repos/{repo}/actions/runs/{run_id}",
"--jq", ".status + \"|\" + .conclusion + \"|\" + .name"],
capture_output=True, text=True
)
if result.returncode != 0:
return False
parts = result.stdout.strip().split("|")
if len(parts) < 3:
return False
status, conclusion, name = parts[0], parts[1], parts[2]
if status != "completed" or conclusion != "success":
return False
if "Eval Dispatch" not in name:
return False
return True
def validate_pr_head(pr_number: str, expected_sha: str, repo: str = REPO) -> bool:
"""二次校验 PR 仍然 open 且 head SHA 一致(防 TOCTOU)。"""
result = subprocess.run(
["gh", "api", f"repos/{repo}/pulls/{pr_number}",
"--jq", ".state + \"|\" + .head.sha"],
capture_output=True, text=True
)
if result.returncode != 0:
return False
parts = result.stdout.strip().split("|")
if len(parts) < 2:
return False
state, current_sha = parts[0], parts[1]
if state != "open":
return False
if current_sha != expected_sha:
return False
return True
_SHA_RE = re.compile(r"^[0-9a-f]{40}$")
_PRODUCTS_RE = re.compile(r"^[a-zA-Z0-9_,.-]+$")
def extract_payload(body: str) -> Optional[dict]:
"""从评论 body 提取 eval-dispatch JSON payload,严格要求为合法 dict 且字段类型正确。"""
match = re.search(r"<!-- eval-dispatch: ({.*?}) -->", body)
if not match:
return None
try:
data = json.loads(match.group(1))
except (json.JSONDecodeError, ValueError):
return None
if not isinstance(data, dict):
return None
pr_number = data.get("pr_number")
if not isinstance(pr_number, str) or not pr_number.isdigit():
return None
pr_head_sha = data.get("pr_head_sha")
if not isinstance(pr_head_sha, str) or not _SHA_RE.match(pr_head_sha):
return None
products = data.get("products")
if not isinstance(products, str) or not _PRODUCTS_RE.match(products):
return None
run_id = data.get("run_id")
if not isinstance(run_id, str) or not run_id.isdigit():
return None
cases_ref = data.get("cases_ref")
if not isinstance(cases_ref, str):
return None
return data
def validate_comment(comment: dict, verify_run: bool = True, verify_pr: bool = True) -> Optional[dict]:
"""完整校验流程,通过返回 payload dict,不通过返回 None。"""
if not validate_comment_author(comment):
print(f" REJECT: 评论作者不是可信 bot (id={comment.get('id')})")
return None
payload = extract_payload(comment.get("body", ""))
if not payload:
return None
if verify_run:
run_id = payload.get("run_id", "")
if not validate_run_id(run_id):
print(f" REJECT: run_id={run_id} 不是有效的成功 workflow run")
return None
if verify_pr:
pr_number = payload.get("pr_number", "")
pr_head_sha = payload.get("pr_head_sha", "")
if not validate_pr_head(pr_number, pr_head_sha):
print(f" REJECT: PR #{pr_number} 非 open 或 head 已变更")
return None
return payload
if __name__ == "__main__":
comment_json = json.load(sys.stdin)
result = validate_comment(comment_json)
if result:
print(json.dumps(result))
sys.exit(0)
else:
sys.exit(1)