苍耳提请:一次授权3小时太短,胖头鱼升至12小时。 - server.js:WINDOW_HOURS 读 config.json(缺省3h保持向后兼容), TTL/全部文案/唤醒链/领取规则/git代理提示动态化;state() 新增 windowHours - config.example.json:新增 window_hours 字段(示例12) - INDEX.hdlp:窗口制说明与铁律同步 - 两套前端示例:授权按钮改读 s.windowHours 动态时长 - 钩子模板:拒绝文案去掉写死时长
95 lines
3.7 KiB
Shell
95 lines
3.7 KiB
Shell
#!/bin/bash
|
||
# mm-review · maomao-fifth-domain pre-receive 自动审核 · 铸渊 2026-08-06
|
||
# ⊢ 明文密钥必拦 ⊢ 破坏现有必拦 ⊢ 写入窗口必验 ⊢ 每次写入留痕
|
||
LOG_DIR=/srv/guanghu/mm-gz-001/access/logs
|
||
GRANTS=/srv/guanghu/mm-gz-001/access/grants.json
|
||
PUSHER="${GITEA_PUSHER_NAME:-unknown}"
|
||
VERDICT=PASS
|
||
REASONS=()
|
||
COMMITS=0
|
||
REFSLIST=""
|
||
PUSHER_ATTR=""
|
||
ZERO=0000000000000000000000000000000000000000
|
||
|
||
mkdir -p "$LOG_DIR"
|
||
|
||
active_grant() {
|
||
python3 - "$GRANTS" <<'PY'
|
||
import json,sys,datetime
|
||
try: g=json.load(open(sys.argv[1]))
|
||
except Exception: print("none"); sys.exit()
|
||
now=datetime.datetime.now(datetime.timezone.utc)
|
||
for x in g:
|
||
if x.get("status")=="active":
|
||
try: exp=datetime.datetime.fromisoformat(x["expiresAt"])
|
||
except Exception: continue
|
||
if exp>now: print(x.get("persona","persona")); sys.exit()
|
||
print("none")
|
||
PY
|
||
}
|
||
|
||
while read -r old new ref; do
|
||
REFSLIST="$REFSLIST $ref"
|
||
branch=${ref#refs/heads/}
|
||
|
||
if [ "$new" = "$ZERO" ]; then
|
||
case "$branch" in
|
||
guanghu/main|deploy) VERDICT=REJECT; REASONS+=("禁止删除受保护分支: $branch");;
|
||
esac
|
||
continue
|
||
fi
|
||
|
||
if [ "$PUSHER" = "mm-writer" ]; then
|
||
W=$(active_grant)
|
||
if [ "$W" = "none" ]; then
|
||
VERDICT=REJECT
|
||
REASONS+=("无有效写入窗口(授权已过期或未申请)。人格体请先 POST https://maomao.guanghulab.com/api/access-request 申请,等苍耳审批")
|
||
continue
|
||
fi
|
||
PUSHER_ATTR="$W"
|
||
fi
|
||
|
||
if [ "$old" != "$ZERO" ]; then
|
||
if ! git merge-base --is-ancestor "$old" "$new" 2>/dev/null; then
|
||
VERDICT=REJECT; REASONS+=("禁止历史重写(非快进推送): $branch")
|
||
continue
|
||
fi
|
||
fi
|
||
|
||
for c in $(git rev-list "$old".."$new" 2>/dev/null || git rev-list -n 20 "$new" 2>/dev/null); do
|
||
COMMITS=$((COMMITS+1))
|
||
for f in $(git diff-tree --root --no-commit-id --name-only --diff-filter=AM -r "$c" 2>/dev/null | head -60); do
|
||
if git show "$c:$f" 2>/dev/null | head -c 400000 | grep -qE "sk-[A-Za-z0-9]{16,}|AKIA[A-Z0-9]{16}|-----BEGIN [A-Z ]*PRIVATE KEY-----|gh[pousr]_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|(api[_-]?key|apikey|secret[_-]?key|access[_-]?token|password|passwd)[\"']?[[:space:]]*[:=][[:space:]]*[\"'][A-Za-z0-9+/=_.-]{12,}"; then
|
||
VERDICT=REJECT; REASONS+=("文件疑似包含明文密钥: $f(提交 ${c:0:8})。密钥必须移出仓库")
|
||
fi
|
||
done
|
||
if [ "$PUSHER" != "maomao" ]; then
|
||
for f in $(git diff-tree --root --no-commit-id --name-only --diff-filter=D -r "$c" 2>/dev/null); do
|
||
case "$f" in
|
||
REPO-MAP.hdlp|GLW-ENTRY.hdlp|INDEX.hdlp|README.md|AGENTS.md|guanghu-nodes/MM-GZ-001/machine/NAVIGATION.json|guanghu-nodes/MM-GZ-001/recovery/WAKE-UP-ROUTE.json|guanghu-nodes/MM-GZ-001/personas/registry.json)
|
||
VERDICT=REJECT; REASONS+=("人格体不得删除入口文件: $f");;
|
||
esac
|
||
done
|
||
fi
|
||
done
|
||
done
|
||
|
||
python3 - "$LOG_DIR" "$PUSHER" "$PUSHER_ATTR" "$VERDICT" "$COMMITS" "$REFSLIST" "$(printf '%s|' "${REASONS[@]}")" <<'PY'
|
||
import json,sys,datetime,os
|
||
logdir,pusher,attr,verdict,commits,refs,reasons=sys.argv[1:8]
|
||
rec={"ts":datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||
"pusher":pusher,"persona":attr or None,"verdict":verdict,
|
||
"commits":int(commits),"refs":refs.split(),
|
||
"reasons":[r for r in reasons.split("|") if r]}
|
||
os.makedirs(logdir,exist_ok=True)
|
||
p=os.path.join(logdir,"PUSH-%s.json"%datetime.datetime.now().strftime("%Y%m%dT%H%M%S%f"))
|
||
open(p,"w").write(json.dumps(rec,ensure_ascii=False,indent=1))
|
||
PY
|
||
|
||
if [ "$VERDICT" = "REJECT" ]; then
|
||
echo "── 守望自动审核:REJECT ──"
|
||
for r in "${REASONS[@]}"; do echo " ✗ $r"; done
|
||
exit 1
|
||
fi
|
||
echo "── 守望自动审核:PASS($COMMITS 提交 · 推送者 $PUSHER${PUSHER_ATTR:+ · 人格 $PUSHER_ATTR})──"
|
||
exit 0
|