| 1 | // Inspect the same maintainer credential used for the atomic tag push. A dry |
| 2 | // run cannot prove server-side ruleset authorization, so read the live policy. |
| 3 | import { execFileSync } from "node:child_process"; |
| 4 | import { appendFileSync } from "node:fs"; |
| 5 | import { pathToFileURL } from "node:url"; |
| 6 | |
| 7 | const repository = "esengine/DeepSeek-Reasonix"; |
| 8 | |
| 9 | // Release tags contain no slash after refs/tags/. Unknown pattern syntax is |
| 10 | // treated conservatively: it may include a tag, but cannot exclude one. |
| 11 | function matches(pattern, ref, unknown) { |
| 12 | if (pattern === "~ALL") return true; |
| 13 | if (typeof pattern !== "string" || /[\[\]{}\\]/.test(pattern) || pattern.includes("**") || pattern.startsWith("~")) return unknown; |
| 14 | const expression = pattern.split("").map(char => char === "*" ? "[^/]*" : char === "?" ? "[^/]" : char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(""); |
| 15 | return new RegExp(`^${expression}$`).test(ref); |
| 16 | } |
| 17 | |
| 18 | export function verifyIdentity({ actor, repo, rulesets, expectedActor, expectedID, version }) { |
| 19 | if (!/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(expectedActor || "")) throw new Error("Configure RELEASE_TAG_ACTOR with the authorized maintainer login"); |
| 20 | if (!/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.test(version)) throw new Error("Invalid release version"); |
| 21 | if (actor.type !== "User" || actor.login?.toLowerCase() !== expectedActor.toLowerCase() || !Number.isSafeInteger(actor.id)) throw new Error("Release tag credential does not identify the configured maintainer"); |
| 22 | if (expectedID && String(actor.id) !== String(expectedID)) throw new Error("Release tag identity changed after preflight"); |
| 23 | if (repo.full_name !== repository || repo.permissions?.push !== true) throw new Error("Release tag identity cannot push to the official repository"); |
| 24 | if (!Array.isArray(rulesets)) throw new Error("Missing repository rulesets"); |
| 25 | const refs = [`v${version}`, `npm-v${version}`, `desktop-v${version}`].map(tag => `refs/tags/${tag}`); |
| 26 | const checked = []; |
| 27 | for (const rule of rulesets) { |
| 28 | if (!["active", "evaluate", "disabled"].includes(rule.enforcement)) throw new Error("Unknown ruleset enforcement"); |
| 29 | if (rule.enforcement !== "active" || !["tag", "push"].includes(rule.target)) continue; |
| 30 | const conditions = rule.conditions?.ref_name; |
| 31 | const applies = rule.target === "push" || !conditions || refs.some(ref => |
| 32 | (!Array.isArray(conditions.include) || conditions.include.some(pattern => matches(pattern, ref, true))) && |
| 33 | !(conditions.exclude || []).some(pattern => matches(pattern, ref, false))); |
| 34 | if (!applies) continue; |
| 35 | if (!Array.isArray(rule.rules)) throw new Error(`Cannot inspect ruleset ${rule.id}`); |
| 36 | // Updates and deletions are never requested. All other rules must explicitly |
| 37 | // allow this maintainer to bypass; do not guess at server rule semantics. |
| 38 | if (rule.rules.some(item => !["update", "deletion"].includes(item.type)) && rule.current_user_can_bypass !== "always") { |
| 39 | throw new Error(`Release tag identity cannot bypass active ruleset ${rule.id}; configure an already-authorized maintainer, not weaker protections`); |
| 40 | } |
| 41 | checked.push(rule.id); |
| 42 | } |
| 43 | return { actor: actor.login, actorID: String(actor.id), rulesets: checked }; |
| 44 | } |
| 45 | |
| 46 | if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { |
| 47 | if (!process.env.GH_TOKEN) throw new Error("RELEASE_TAG_TOKEN is required; the default Actions token is not a release identity"); |
| 48 | if (process.env.GITHUB_ACTIONS === "true" && (process.env.GITHUB_REPOSITORY !== repository || process.env.GITHUB_REF !== "refs/heads/main-v2" || process.env.GITHUB_REF_PROTECTED !== "true")) throw new Error("Release identity checks require protected official main-v2"); |
| 49 | const api = endpoint => JSON.parse(execFileSync("gh", ["api", endpoint], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] })); |
| 50 | const listed = JSON.parse(execFileSync("gh", ["api", "--paginate", "--slurp", `repos/${repository}/rulesets?includes_parents=true&per_page=100`], { encoding: "utf8" })).flat(); |
| 51 | const result = verifyIdentity({ |
| 52 | actor: api("user"), repo: api(`repos/${repository}`), |
| 53 | rulesets: listed.filter(rule => ["tag", "push"].includes(rule.target) && rule.enforcement === "active").map(rule => api(`repos/${repository}/rulesets/${rule.id}?includes_parents=true`)), |
| 54 | expectedActor: process.env.RELEASE_TAG_ACTOR, expectedID: process.env.RELEASE_TAG_ACTOR_ID, version: process.argv[2], |
| 55 | }); |
| 56 | console.log(JSON.stringify(result)); |
| 57 | if (process.env.GITHUB_OUTPUT) appendFileSync(process.env.GITHUB_OUTPUT, `actor_id=${result.actorID}\n`); |
| 58 | } |
| 59 |