Merge pull request #1924 from pikasTech/feat/selfmedia-production-delivery
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Failed
Pipelines as Code CI / platform-infra-gitea-nc01- Success
Pipelines as Code CI / unidesk-host- Success

fix: 显式暴露 selfmedia GitHub hook 权限阻塞
This commit is contained in:
Lyon
2026-07-13 17:36:05 +08:00
committed by GitHub
4 changed files with 135 additions and 10 deletions
+10 -10
View File
@@ -757,7 +757,7 @@ NODE
python3 - "$tmp/repos.json" "$bridge_ready" "$service_exists" "$mirror_status_rc" "$bridge_logs_rc" "$inbox_status_rc" "$tmp/mirror-status.json" "$tmp/mirror-status.err" "$tmp/bridge.log" "$tmp/bridge.err" "$tmp/inbox-status.json" "$tmp/inbox-status.err" "$tmp/hook-topology.json" <<'PY'
import hashlib, json, os, sys, urllib.error, urllib.parse, urllib.request
sys.path.insert(0, os.environ["tmp"])
from platform_infra_gitea_status_evaluator import evaluate_repository, select_committed_head_record, select_target_delivery
from platform_infra_gitea_status_evaluator import evaluate_repository, github_hook_url, parse_github_hooks_response, select_committed_head_record, select_target_delivery
repos = json.load(open(sys.argv[1], encoding="utf-8"))
bridge_ready = sys.argv[2]
service_exists = bool(sys.argv[3])
@@ -783,12 +783,15 @@ topology_repositories = hook_topology.get("repositories") if isinstance(hook_top
def url_hash(value):
return "sha256:" + hashlib.sha256(value.encode("utf-8")).hexdigest()[:16]
def hook_url(item):
config = item.get("config") if isinstance(item.get("config"), dict) else {}
return config.get("url") if isinstance(config.get("url"), str) else ""
return github_hook_url(item)
def is_managed_url(value):
return value == managed_url_prefix or value.startswith(managed_url_prefix.rstrip("/") + "/")
def hook_config_matches(item):
config = item.get("config") if isinstance(item.get("config"), dict) else {}
config = item.get("config")
if isinstance(config, str):
return item.get("name") == "web" and item.get("active") is True and set(item.get("events") or []) == set(desired_events)
if not isinstance(config, dict):
return False
return item.get("name") == "web" and item.get("active") is True and set(item.get("events") or []) == set(desired_events) and config.get("content_type") == "json" and str(config.get("insecure_ssl", "0")) == "0"
def topology_observation(repository, hooks):
spec = next((item for item in topology_repositories if isinstance(item, dict) and item.get("repository") == repository), {})
@@ -989,11 +992,8 @@ for repo in repos:
repository = repo["upstream"]["repository"]
branch = repo["upstream"]["branch"]
result = request(f"/repos/{repository}/hooks")
hooks = []
try:
hooks = json.loads(result.get("body") or "[]")
except Exception:
hooks = []
hooks_result = parse_github_hooks_response(result.get("ok"), result.get("status"), result.get("body"))
hooks = hooks_result["hooks"]
matches = [item for item in hooks if hook_url(item) == url]
topology = topology_observation(repository, hooks)
head = github_head(repository, branch)
@@ -1044,7 +1044,7 @@ for repo in repos:
"latest": latest_delivery,
"latestPush": latest_push_delivery,
"selectedPush": selected_push_delivery,
"error": result.get("body", "")[:500] if not result.get("ok") else (delivery_errors[0] if delivery_errors else topology_reason),
"error": hooks_result["error"] or (delivery_errors[0] if delivery_errors else topology_reason),
}
rows.append(evaluate_repository(repo, hook, head, mirror, bridge_events, inbox_records, inbox_status.get("journal")))
bridge = {
@@ -5,6 +5,33 @@ import { resolve } from "node:path";
const evaluator = resolve(import.meta.dir, "platform_infra_gitea_status_evaluator.py");
describe("Gitea source authority status evaluator", () => {
test("fails closed for GitHub hooks error objects and malformed lists", () => {
expect(evaluate({
action: "parse-github-hooks-response",
ok: false,
status: 403,
body: JSON.stringify({ message: "forbidden" }),
})).toEqual({ ok: false, status: 403, hooks: [], error: "github-hooks-http-403" });
expect(evaluate({
action: "parse-github-hooks-response",
ok: true,
status: 200,
body: JSON.stringify({ message: "not a list" }),
}).error).toBe("github-hooks-response-not-list");
expect(evaluate({
action: "parse-github-hooks-response",
ok: true,
status: 200,
body: JSON.stringify([{ id: 1 }, "malformed"]),
}).error).toBe("github-hooks-item-not-object");
});
test("reads GitHub hook URLs from object and string config shapes", () => {
expect(evaluate({ action: "github-hook-url", hook: { config: { url: "https://hooks.example/object" } } })).toBe("https://hooks.example/object");
expect(evaluate({ action: "github-hook-url", hook: { config: "https://hooks.example/string" } })).toBe("https://hooks.example/string");
expect(evaluate({ action: "github-hook-url", hook: "malformed" })).toBe("");
});
test("parses branch and exact snapshot from ls-remote output larger than the display tail budget", () => {
const head = "2".repeat(40);
const prefix = "refs/unidesk/snapshots/gitea-actions/unidesk-master-nc01";
@@ -13,6 +13,31 @@ def parse_ls_remote_lines(lines):
return refs
def parse_github_hooks_response(ok, status, body):
if ok is not True:
return {"ok": False, "status": status, "hooks": [], "error": f"github-hooks-http-{status or 'unknown'}"}
try:
payload = json.loads(body or "[]")
except (TypeError, json.JSONDecodeError):
return {"ok": False, "status": status, "hooks": [], "error": "github-hooks-response-invalid-json"}
if not isinstance(payload, list):
return {"ok": False, "status": status, "hooks": [], "error": "github-hooks-response-not-list"}
if any(not isinstance(item, dict) for item in payload):
return {"ok": False, "status": status, "hooks": [], "error": "github-hooks-item-not-object"}
return {"ok": True, "status": status, "hooks": payload, "error": None}
def github_hook_url(item):
if not isinstance(item, dict):
return ""
config = item.get("config")
if isinstance(config, str):
return config
if isinstance(config, dict) and isinstance(config.get("url"), str):
return config["url"]
return ""
def select_committed_head_record(repo_key, github_head, records):
candidates = []
for record in records:
@@ -255,6 +280,10 @@ def main():
action = payload.get("action")
if action == "parse-ls-remote":
result = parse_ls_remote_lines(str(payload.get("output") or "").splitlines())
elif action == "parse-github-hooks-response":
result = parse_github_hooks_response(payload.get("ok"), payload.get("status"), payload.get("body"))
elif action == "github-hook-url":
result = github_hook_url(payload.get("hook"))
elif action == "select-committed-head-record":
result = select_committed_head_record(payload.get("repoKey"), payload.get("githubHead"), payload.get("records", []))
elif action == "select-target-delivery":