#!/usr/bin/env python3 """Fail-closed Forgejo pre-receive gate for Lake Lamp repo-push grants.""" import json import os import re import sys import time GRANT_DIR = os.environ.get("REPO_AUTHORIZATION_DIR", "/var/lib/guanghu/repo-authorizations") PUBLIC_AUTHZ_URL = os.environ.get("LAKE_LAMP_PUBLIC_URL", "https://guanghulab.com/authz").rstrip("/") def normalize_repo(value): value = value.strip().lower().removesuffix(".git") match = re.search(r"(?:gitea-repositories|repositories)/([^/]+/[^/]+)$", value) if match: return match.group(1) # Forgejo's hook environment may expose only the repository name. # This instance is single-owner and the allowlist below remains authoritative. if re.fullmatch(r"[a-z0-9._-]+", value): return f"bingshuo/{value}" return value def check(repo, now=None): now = time.time() if now is None else now repo = normalize_repo(repo) if not re.fullmatch(r"bingshuo/[a-z0-9._-]+", repo): return False, "repository_not_allowlisted" filename = os.path.join(GRANT_DIR, repo.replace("/", "__") + ".json") try: with open(filename, encoding="utf-8") as handle: grant = json.load(handle) except (OSError, ValueError): return False, "repo_push_approval_required" if grant.get("repo") != repo or grant.get("target") != "JD-FD-PRIMARY": return False, "repo_push_grant_binding_mismatch" if now > float(grant.get("expires_at", 0)): return False, "repo_push_grant_expired" return True, "ok" def rejection_message(repo, reason, public_url=None): repo = normalize_repo(repo) public_url = (public_url or PUBLIC_AUTHZ_URL).rstrip("/") return "\n".join([ f"小湖灯推送门已锁定: {reason}", "[LL-REPO-PUSH-AUTH-REQUIRED] 这不是凭证错误;不要向冰朔索要邮箱、授权码、验证码或密码。", "从 fifth-domain 仓库根目录执行下面一条命令(把 换成本轮已登记人格编号):", ( "node server-tools/lake-lamp-authz/authorize-repo-push.js " f"--url {public_url} --persona --repo {repo}" ), "命令会创建一张无执行权申请单并打印 REQUEST_URL。把该链接交给冰朔,保持命令运行。", "冰朔打开 REQUEST_URL 后,服务器才向预登记邮箱发送批准邮件;冰朔点击邮件批准链接后,", "命令会自动领取受限会话、读取并确认导航图,并返回结构化操作回执。许可不是 Git 密码;只有回执显示安全推送接收器 ready 时才按其指令继续。", "说明: AI 不发送邮件、不需要知道邮箱;公开空工单本身不含任何推送权限。", ]) if __name__ == "__main__": repo = os.environ.get("FORGEJO_REPO") or os.environ.get("GIT_DIR") or os.getcwd() ok, reason = check(repo) if not ok: print(rejection_message(repo, reason), file=sys.stderr) sys.exit(1)