← Files Awesome Maintainer DefenseARCHIVED FILE
skills/audit-repository-workflows/scripts/maintainer-defense.py
93.6 KB · Oct 2, 2026 · 00:29 UTC
#!/usr/bin/env python3
"""Audit repositories and preview, install, verify, or remove the Maintainer Defense Kit."""
from __future__ import annotations
import argparse
import base64
import difflib
import gzip
import hashlib
import io
import json
import os
import re
import shlex
import subprocess
import sys
import tarfile
import tempfile
from dataclasses import dataclass
from datetime import date, datetime, timezone
from pathlib import Path, PurePosixPath
ROOT = Path(__file__).resolve().parents[1]
KIT_VERSION = "1.1.1"
AUDITOR_VERSION = "1.1.1"
RULE_HELP_BASE = (
"https://github.com/thangldw/awesome-maintainer-defense/"
f"blob/v{AUDITOR_VERSION}/docs/AUDITOR_RULES.md"
)
MANIFEST = ".maintainer-defense-kit.json"
PROFILES = ("observe", "balanced", "hardened")
LANGUAGES = ("en", "vi", "ja")
EMBEDDED_FILES: dict[str, str] = {
'auditor-rules.json': 'H4sIAAAAAAAC/+1aTXMbNxK951egtIe9cMRE/lzvSbYU27VxpJLsuFJbWypwBiSxmgEmAIa01uX/vq8bmA/KFEVaPCRVPsSxhzOYRvfr168b8/kHIQ58PleVvFoo57U1By/ETyO67JpSefzr3/iHEJ/5T1zWBa4dvDvJXp/9lv34408Ho/aXoEOp6McLVVuvg3U3wqu8cTrciNqWOr8R2otKe6/NrH+uUFPZlOHKK5iAe2mJShW6qYb3+NzpOkQD+Q0uwGBRyRtRaJ+X1iuxaEqjnJzoUgetvKibCd5a3gjrhJxIU1gjHD/qxXKujDBW+KbmtQpRO72QQQlnG/wJSwubN5Uy+O2wt8TLqbpyigyUrTnHRSEuT199uHj7/vfDqhBLHeaDhZNn/UjI/iVsBvwQXzfCBV/jHiXUp1rlgdemJ0yBp5bSGbrX2CCC7TcM9zoV+o0O7Jyrsr6SJp9bxw4tspldrAasknWNVfsgc6APpk5WamndNT13VitzefmzuMytU7l0xcGoxcBlim12zrGlHxpX0i/zEGr/YjyewQ/N5DC31dh6Px37dpHxpLSTcSW1GcPJfgwE5tcervtbC5gsAubgSzLsP/z/L6ONYDxaA8ZXZyenZx9/Pb24FBPbmEK6vaIw4Ty3JjhbZnUpjRJTjdQRuTRiokQ+l2amIigQakSUQozN6SDsEnj1c113tm2BNKdkmZ4UUyCbQjUt7RJowaYaJYKqYEdQuBDm6nYORkwN3KKDV+WU74VpBkvmKq6PVy20WmKFWjosiExC2jglABxnAWVc2wJyR3uD3CtbqOyCjdoP3HJaMO7y4MvoGyx66WjD2bmzAVlLUdqLXZO4bN0vu1MiPNqcCIUFPIlLWiCWhGDgXhxGWx+UFsdi4qwsBuCmUtKmw1LJa2XuyIeEty5RV+DNuHV9zskm2IozY4ukkcKDVvVU52KcNjm+baGRFZEsGVTqmZ6UoGUJ7y/adCADyA3TGySLDENjuFp22UNpp1E4TKGKtKkt8uTRXyRPdkHi4zVIvAyuyUPj4JtIWHBatTUpAwp3Qu+D8f3abaUn4NmK0YV8KhrOJ4G9FMogVhRUbVBHJQoq7T3gPwQ7gN5mgK31YSt8GWsymMPvJ8j8fvzul+H+nPqjwc9c8oeG+KBqMHUs+zB7ouZyoa0bJbyXaiFNGIoIuswa5Su9E8XFVjrg8XqwbRXUJ2uC+k7mc/gNIJGFROqIQtWEfwPV19QFaZ4dNeCmQJ9rQxxyzC6MzFDL/BoBi/FGhBBFAfe2PoJFrQs77ZfsqhTVaO0r0omJgnIZ0QFjUdunekaQugcHr9obxQnvXU5AsVjyQhnLoo/ojKih542/+1jOFWB2AyRULcuxEa3fmrJs4eOBDyxDRrsZXLhFpJ/sjVZOupBmH9i07L215X4YpodLFredBVp7J7J5ugaXL29X0hWBLSRcSZWw1/qitLksy5vN4MQz6k50vutYhNFI68MVNb1rTWXgiFM4VV/4uIOge9UntDlGlsNa0wJym9p3kvbV80t8KXgk+n4kuGRldePn1ISA9vLUe0xuaul9qpKRdxTbSTkNh+aqANiTcEx1EfWygbVeBWI6HzdCnJBZA5VxfP4Wt+TK+y2A+3RvwP2zqbSPP9/ROn9MYkfMJSk0NHt1hgKgSvwNsqkDyAMF2rShEin+ayfMltrM6dmo21j2t1jL0vLi9dv3bz68vHp/9q/TX0WtHDM4JRCEkOkQvUWhHDz7Qnz+IiCjWoUn4k4JZzNHNY8hQ8SnqZ4P34q/k/UJe2h1AmdLt9IOJLmc7rUrfk9xys57W/cDNQ5/NnDBhnbl1cfTgRT8eJodPf3HGivypTqELHLq0LrZGJQrx4i2NprTf4yHDuehKnfC9NEmTHNQQQmAmspAsQnTw01thPVcz+YbQE0U6yD0fCcMqPjiakWIIpw3UHgNhAPxD7MsWUJ9BNBADXKworIF0ZhXBglAMm6o8ZW3DbjS3wPzC4X2G9qh3yizIPUVVY3XtDnFhGpAz9RbR7BHygeJErpT5kcrh+hvU+J+XB99x/VecL2uoX6tw5tm0kJNx466jsIUSJJgWYSe8AdqvXxz/EDOrprAKjbIGeE61h6GdYXWnd44TABq2PoWOxV96rrbZIxjqXuR7G25UEm0piWoJaFead0eaY5Jyic9kW6Exbgk+T5okVErFmDtQhnsQ6VBZ+zoB++KUtDfD/T99c6xtcg6rYvGaj9Qj9DIiuHCO4D9+dE3gB0P7Q72dT37OVpLXSoaZAK0qM1xX4LhFQJ6L+UybaZlQ21TxN+D2Bz1+5rn9u3iacRa9haw8EhvjIBXn1TekMhNcIMs+RQi+06AxGx1ehRZ/54UuEyzT2iJIMm/kGamKKmNnyLZhu8lGuDurTFxIKwnALSLtzC9Q7xBuFA/d5X6udsCeUjztBadUMQh//058Hh/jR5xg7ONz9rivacmr1u35aE/ZwasG3B8QEAbH8FVNyGNGSQ4uD2FoWHlqqLYnAEAfNDoNe/OgjXYJ/BkLXgY4SkVoDkaryesmMkq2ASWCVqWPs41Ok3P1M+Tzi1z4EJxiWn3SbjkjfqYAsT1TeedWoY5nURYOoIY5kG2miqjNLWhDpOQv5ClJrJHGXBBT6GB2O4u7VBu6p6EfKBZz/058eR7TuwpJ9YNV17RLqgE0DFsTeM1T2ojCt9c1lGwkMR7oPghLPVaPsr6TutMGl0WPEdNSRnPMmRrEZWjLhkYddQI6ODFN9WD0K6bDVLshZjiT8qDEs0Fr0a/5AxoKEXSh61sT11rmyJ8lGBrFec5sXQZ6Rw3xBKv6WBPTcH9kH/6F9T8O+Dw2UZujtKACQMep7mEq9nNRNusWD3cVrZQeohEuVOYsOggJMqSUEv0OFOGu7wivR7YRTdcxRYwihZApxsMJutoA/z4atzXjlbgyZlJ/WONTpUcyxKdznQX2lnDY8CFdJqzEjnE3AtoSj/sEGTqZ6jIJaT+0dgQm4BofbdIO9D2UPp5t7v7IfpsN4h+xXrPnu9Oes+e7855zzcr4a6ZSiEkP67U57aSPVQHdG+6StrS09SAMdaip5UC8WSJWPFmWJRXVQP1hnNsQsxtWZASuFvC3IO739LYl0u58xEl7bYJWb0NFJFRlCKRsQG9qB/SDXG6AuR5XcSF6q99/U9hyHXdvum2wi5NSaPLXjvcj8Hn35XBw5XBu7OTOybZJ4Oj0AqAa6sbHZITdxQP6w652HLx5uN5t3ooQgeMQV7Hjw74U6kRHetcM/wKkBWAA1jh4eqWSGWcdYOTeVOxoLh1gL9WF+AZqG4wbjx3ju0c7XzUFv6V5UZQttLTFH51L+mghb/o8jw0BDjpiJB0dTvXZhdS3qYPDojaNyIedtw95d4qxusmu29Z/QT+vm4Oj5OOAv984jPehqTZNEam867T/vqhUrCPm1FL5FMiwgnNSA2aiP+l9+o11qH1cWAHvM2DcomdnIrqAIHjStZMkB4sGbcZkJ1oHzUukxXtnCs6epkmfeXHdKz5iP5mJJD1NZ2Y0UnuqD0QJy1b6n4MtuKw+FkggGFIV4Ibg5LFFrE+ekCsH22fz+mcir5IJNHNzhgA9qEzz/5FtzI+RpyizyMTaaj28bn+wHse5i0U29cf78ZPJxDuNhupnOr2Pdsep3KZi/skoBhVjtKCfFy6/ntOuor486ecfH7qvzpWLRKgyHlbRPnRxij/QPz95Yf/A3YWpwHmKwAA',
'docs/PLAYBOOK.md': 'H4sIAAAAAAAC/21WS4/bNhC+61cMsFfZQN9A9xQkLVq0BYokvYeWRhJhimT5WK/31/eboWRvkh4WC0vUaOZ7jR7oL2N9wR8nGnlin5miM9dTCOeue3igb470MVkzc9f9nThzemIqC1NIdrbeOPrn/Z89JX6y2Qbf0yWk8+TChVLFr2JXzsWssSfjR8rG22JfeCScH9kPfKQPHE0yhSnzUJMt10NGFziGFyWOIZVMUworxXpydiBp94zn3jqTs52u2o1dVx6tVEk2n38mUwr7goYIvYSxp+qzmfjeXTRl6WlIPMo5jMHPMeSauAcMuaQ6aAOmlrCaopOFRHN44uQN+qbZxCO9C+RDQUsT4MPZJaS82ChH0SYqt85zuToUtvquctVSZhhCxYHF5hLS9ahgf3uk90CGL133NvjJplWnm6wfrZ/JzCAqF71mWoOTdazQClRADaVoCHj3cwFECw9nuiyMBxImLNK8AzMORJYsfcxg5EUHpBgAb2tOaLYYcpAm5pragWExfuZMJkacNCfrMMyRfg3pM5xKMngUs6WaC6i2Plb0HMiCSX4Gy1ot7IihCGXrzzL7ENJIk3GiwtBEkHW86iG+4J5QbxtPGEMbtrgNvO+O9KYVfIFY30AcsydD3kAaFC5QuBA21FV4EQhjCnjJVjEFJzL+t1pogiKn1WZRNFAaTTG4Ndho8Sgu6Hl7wszpILo/gQOeJh7kJj8PHBteGI3hnV4QYwE+1MLNLHyRstxswc9gE3edO5kBOPxm5+Vg1yhXt97y3tsrog86lBRP4cm4R9QCpKPck3YDPOHHRp3NKtQN8EZ4Q+17wI4XE1rrug/FpEIXWxZyYUDHWk57FMA+hZMGwCeBTpSHZ6uXjiQZ4EuVZIF/8mb3NQKaU0C9yZl5BrKNzP3XDUlB+khIGHGS5woRNbxU96rvjRrESakZYoS2M4kh92SApWo2W6lfvBFaBgeGe5nm3NOp/RvZ8c3SpxTMqG5NYilg5ewqSg3eXWkSldLKRqJhRGOMLFF8lroav1MpDeIXFGl3K/8gcs4xCHRBblo/aADcpXm/RhuTOBxNzeKegZPEMt7g8HbJW1rDyJsV735T0Fos75mqDQI6oBuvgt895rLUick+aVRu6Yrhw46hCzPA+x1WkxN3E998q/INZ26BKRTeiyPiPIRoR43zmiMcQVCUnYxaQ2FCeFx1LxxOCMBRzIZ3Aej1pnUkF8RbPUJGCjXC96FEU4h9mWvB4shZ/YyxRpuFbbkh0RAg3JGBIeopIz9uShePbZIaRD5d985mFYto/LYh1ERr2NbdTX6qO6r6mnZDMgs37vb9KAsJOW2cA6mruQLH7XdTVfWg0k4WT4mLwIAqID/SF9d3z0NqVfygahNhhcgQKs9IyFUwkq57sbgsRKNR9NpdWEuyBnzLCvBvWg618XiFDgwWR5LNZ9UFon/KGHasbkspQNXWrSmfKapB2VQqaxVXNKZkr4lfsXf3iP7pSH8wRw2qMbSUbAB23Rb+DdPbLpPikmS29Ls8mln6uysxEthbe6wt1wbf4qn/nxxX0WHYHpv3nrL39IZ+a/PYBVs3XPq2jg77OqJTHWcut0xvHxJfRfquBm0eF/bB5znxLIzdzYrdrNtO98gjje2bQv2Yl9cc7h8QltXGyVxuVtad6MtRvxvavrj5xe/fChtu+MpKYpmvepbgfiVkynjeCbelJi8p1uK/oSNhdOz+A7JiGaBACgAA',
'docs/ja/PLAYBOOK.md': 'H4sIAAAAAAAC/2VVW08qSRB+51d04vtJ9vYL9mmTTTbZZLOvhwOtThwZM4yafaNnQLm53kFEDyKoI4ocjrsevCA/pulh5l9sVQ0ori8w09PVVfVdqmeYdOrSuZXOmnTy0nkKDr6qZ1gpS+da2k3plKTjSLsTiczMsO8+wPasdFrSPoW90u5FIirjSHHD/vj9V5kSJl/RkpqRgMdVw1yY1Y1VZi7jq1exVfYRHy5rqrPmORl10pX2TlBpSpGWoua7T17hBFaGg2PVPpCiLMWzFBWZsqX9KO22dM4wMxRqNyBWbeel6KjMdVAqqM3y8BFCCtLOqexaUD2dDvduXS+9qfJVqNO7y/qpMpShNrpB5UGKFpsUCosxk8d5wtKiOvOeNqXIYmkpMTr5RzXyo8M0iy5bxmLUCjucM1a4mYgmYpwNexvKrsD5UlxKu0iV7KgMxF+Ncvfq5hDrgaTdz17qQtppr7Q+7O+GBQ/7R34qg4tQZPUWAr2/3XEItlCV9i50QQR8jwRcS2eXwHiKRLxcyj/HZN5RTuXvoR/p7Eu7TtRdSeESZMfAVEgcJPbTVFbmQm1lp2FSzVLguECXzpPcwrb/TY/u97zSfbB+DK++26aqrqQNcQ0pzgNxOdpz1WbHd/rYlyiqZk7am9AUFoK0FkanD35rYzrPnxNhSAEhHTh5OKgHJ0/0uhEGqswZ8UWE3tT8enFk39MJNQjx3MugskWvAOaOP+j73+rTKQirHz4wLzeA5IBSIaP6u8xYTXCTIVjdB2kLarGrsneoyvDIlJDOOil7EFBdyCEs2rfkEZL+BHzYg7+iB7SrfjeoDbCV5wLgiCfvf/HbX0Z7t6OrAkA19gZfZfGoxTFhuwHQoP5MQ9c/RWMLWJjvHgTFr29YCU+GFM0QnvS4ZuBBdJjJl4ykZhnmX5PuQOPUNbIxyPjnAkGCeqoNr/aIXze3vHrWX2+RADuTzUSFAKvl34nuxw9MdffBZpGIdNrU+RWIa3Kiyz4an5LcXOEf2ZJpzGo6Z2PmLgoI86SV4WPDr7tgI1Ip2PgeEYWm68XQZ7N6dA5C98lBNWnbZCh3stLCU1F4k08wK3p10KfnVryj6/dKG/bagH6YEGBetrQEnu+ypBW1lpMsNs9D2FW6RWZ9Df1ZN5JIk27EFuDv0/g/znVO9GkJi5vRGE4CpmuLmsVCKXu9azLJuIeQAfQGSOPhAYuesi/uP6qRlIuEfUHBpEFXnkixPewPyELTmv4J/I/OhinxjZRKohSuer70yheRyC+JmIYT7FXqKvcVOFXINYoQBhXJroCQvE4zMkX7NDRFksMctCiYpiuSbn8BwYErqa3DN7M6477B/L2Xyb846g/3xpP5zaTFPM0uDS/8Mipt4wjBI2HempY2CzDjplG6DptUsfTmahDH6I3n4pSN6BjwFzXdYprFFynJ2gY2E34dw34pRR7hyd7hK0jxokEXUjlEKzj+HFQbmLt6QzIKW8Wuxk4F2G4Kb0eP2nqx6A2Lxo0lAtjkMcOMs7FtiR6so3yGF1dKjI2D6GtzCUBlVUvE6U6ahQfQbRIfo3qSMzK8tsJxJbq0xGERnqx5kyfnDT3O4jw2uYNfMLFMbW6OMk5NIQJl62raor+N6/rfrGoeIeM9UNpgGufQky/dQlvhHMAuPbjsp2H5D/FBtq1pCAAA',
'docs/vi/PLAYBOOK.md': 'H4sIAAAAAAAC/11VTYvcRhS8z6944KsYyNclvsWnEAeM83F2T6tXakbqVlrdszu5hRBM8MFZggnGmOxmMUu8WXZNDCEjwh607P/QP0m9bml27MOMZjSt9+pV1au5Qw8qsV5Yu6TFsDmxtBq6x1QLbTxeys1md+7QB3P62mlRqNns/s1loG8e3s/IqZVutTUZ7Vu33KvsPrmAb17XqvWibmjVH3HRN6YgWQ7dn7heH/YnVNlhc6wpH7oLqjT6BTIlbq1JAkKNZv2xLKlVMjjt1+jUWOdpiRpPNTVhUWlJDHCp5vSg7P8wU0k3dKd4t+TdzeXQvZBUD5sL/ykBns0zCqYVe2oLOKNq6J6TdCpXxmtRZZQDuwvS65UiEbythceMVKL+P5IKu1LOCCMVFaKZ0xdl/xZTtWFNOQb5yZAHdCo05qBSrKn/DyMP3RNTkh+6M8oFf+rP8c7sNKVlcnjceWT6wzk9BK9qfza7/mXoDjWYQ7GrQHva5BqHoc8LjW+VQmWBGwzt2Mc2z/l3lJXWeHXgQWd3KiJ9gAp8P9YgBjdCpVrlM2osmFwzsmdJITkNip89d1v0rw2ZwvZHQNL/TZ6H4lbPNImmweNioSsWaRmpmNO3Ed8uw5tT0FIqC/ZBbetVDvGaECFvrlD+Bsp5tH8jcdETBLBfWtY/MfNRlPo15WENx/jZ7B6e+HnLrt2HWe9Sged5emehZaNcrdvk0diEXXZudo2XJcG25EXwsYBeQH2XkTqQqvGxBgZW7BEXFYKWXkW60axaCLkEh1HZA5Sc070E490GUlg2ORhhVRJquOB2rrvx6A90EIbNK59MI0KugWWkGMfhLj3x8300aOLoY7gHWGwAPZ+x8SPFp2HawspKUaVyyX7ORiM9sotWuZV6BNRpET3WID7Me8pMmajkqzUv1FmIi0raqxqluydYL5GcN0Lc3pzTzUVAJLCNHHCxp2ACft8uAcLChxbkKbmMACArAJ/Dv5VtVcawlxkt0iVXlQLvo0mQAsoJGXe00rXmlEhONWVEieGvDy1enAcnMqMy1MJMEnJ7dt5LuA6zjjR+MqfPjdScCUlfJXnv1+97Tk+HoowZ++oMk+3EhixvLgWv1uZCpgt4TDnrsVPm/Xg0OI/LyCKDFhyDTkUgFkwUY/6BqP4tS3Le7OyVOkBmJrNiQrtU72TbCk+UKW+F83oPvMX58v5fxjD6Na35Dif0lWAScXsyegYXdL9zd9gjugDnTxpO08c8X/cU7ZGjOgmDZzfsPrAmk+S8phjK2bZRKWu3I3MW4eRvGlp8yddbYHFvRG7jOkZVXE7F0P1aTwqYoj9aZ5OvM2p1YeD4fWQnZ9GYoS0+iapVyL9Wc/d22m18+A57uLnyt7FdYpTSVvj32O45JisKLG4yxzYM8Mc1hvUIZzcoRsNua8R/wrhmvuz/QlsE64o3f9zF+ex/r03VmpsHAAA=',
'kits/balanced/.github/workflows/pr-quality.yml': 'H4sIAAAAAAAC/41WbW8bNwz+7l9BOAP6pXLdIO4yAy2WdNkL1iydk2IYiiLQSbw7JjrpqpckHvbjR53Pjl+HBg7s00ORjyjy4VnZ4BQ+zuBrkobiHCoZcTBwdjoAaJMxtx6/JgwxPwPEeYthCp9dixb1S/C4/IWaYv4Oc6tq7yz9g18GgyOYodTCWbPwPIKbmgI8On9fGvcIFh/Qg6pR3QdwKYLzgE+oUsTQhRd9eFBO42jQom8oBHI2ZELK2Yg2MiPPYXiBsYTPj+selquDO1d0m6MnWeHiXD7ZwCynkIpkYxKGuYbYQSFiGxZWAAJsl7D3mTGEVPRsVukLVFlpQm/OhPS0D7RaSiEzbFHeuxBeSRtJBOPaHyffn05OkSmenP6gx6fF8WRSyuJYlicTORm/UcdKvkFegCN4GK+8PVKsp6snYPCantaYiSWzRZJH8LNLHkpJJnnOsfQIOUHkUUN0UBpZgeSCGK35bOSTWO6YwskaUhin7lGLwE4VisJLvv1sNBxu7Ve1tBVblmQyPhkfwA3ZjL8e89+aSc9RNJJs5H/0QkkrGqepnOcUJ9xjrTEoT22kfLVbNl1Q1zQURYMh8A0Jg7bibDK5faFL4psVFh8zxR13yriAovVTzq0J60hO0V6AC71po9BelpENwo7P3qBwuXj/XQMApMqHCoKLyW8CKbqS+Gj0mfd92cA0crNqyeu7mEfrHrjqd5GKKywVoo+4jfccM4/ta+8hZlQ7L2QITpFc3MXVX39czF5eXlye89f7qw8fzs6vZmc3V7Pd7UYWaPZ7bv3/oZJ7v8nVFl1XUgfSn83Y0TdYrpscoHPQ5Ah+oiALg6wIrFjckK9YCKPzc6gxef5JirvRatabOVu13j0R9ydZiDVCIQPmshttuPwdse3VBUqn+A40sBYFxaKctZmd6KSooCwALzvvvOgCLeLOK8JNlxojqihCK5vuRrPY7clHQ5bLQTmWSpElFLabWbNYzEXJKh82wWUvtakwpDhjLuvB/gg9mJu0NUyMk7rlLVvlA3ED+6wdXQ8dNhA+V9+uRWVcwZ190GZJuleLvp4bGVW9h3k+/rPsrDRlO0XYuDsSXQ53wTzsmHiJ3JUKtw611NxWxnqf0kqtGc1kF6NxvDW7LqVfH12BC0xGnsRcPHVqpOXzPhA+Po+wcroYgqNFqY14TrcphhFPg2QivH0LL/J0QP1itYfn6aZioaodDP/cnJLcOIrD5snDNc7DvKQqdYOoZt+1M3o0hHfvYPjdL7/d/Prp/Pb65uLj7fWny8uz2d/DHe/dqwV/rIs8wu6SrnIGgPWOXywWtxZqavM7BnXvDd/q/YkivB78B4WkulYrCQAA',
'kits/maintainer-defense-kit/locales/en/adoption-record.md': 'H4sIAAAAAAAC/0VSy27cMAy8+ysI7CUFNvmA3ooGPfWFpB+wtMRdE5FFg5K93Xx9R3LSHAQQnuGQM/SBfrDmiidOUc6SixBHW6paJpdgHofhnp5ksaLV/EacIwWbZ62fAfy6orMVX96bIldpH367nTUJ3Z1sLOKbnI50GjlxDhJbPbFHyag/NfqzXjInumqOdu1DCs8L+mepk8VG+QZM86XQeCNfAZlT6W0d5YTd+5q6SekSa3YpljbBylyk9EWXRUBt5kQb0oi2VnjaCX8mNE2WIgIJWpqnRuEVe7i+cnPZDYrPWhq+D5O/Vbx5QALc5HVRybVrfrVcXccVCd5v0ByxvZzPEnb4yVIaObwQSJcLTrHrcaiE9zbvJwZAdlO5voU8HA70+L4jAMQZZBi+a35BehlBvMKf43Zey5GqFAisGWX33E5HvCxuG6cHejTKVklzSGsUCi7Qq4qsjrS4bhj5oYXow4cn0k6tt917nRg6BfFnhFwK47c5g1Un+Z/pw/APt8jdIHwCAAA=',
'kits/maintainer-defense-kit/locales/en/bug.yml': 'H4sIAAAAAAAC/61UTY8TMQy991dYnIdKwG1uC0JiD4gVe0QcPInbWs0kwXGG9t/jmX5t22UrxJ4ykzzbz3nPidhTCx/rEoRyEp15Kk44K6fYwvdpD3A8lOSr4y4QeFqQ0/lMWYMFv/lh4T9tnXXJb9sZwFvQbbaTHmXt0+9oWwCoKtxVpdJO/wADhmqoh0BYCAqhuBXQhotyPPApgNHbd5+GEeKEbCsJZJKSIgbwqAgdLZLYee161jF6/oQFx1x1Ksm+hcECrbe/UArYUWjhbjF2SP6AHiu61FvyPS4HdLRKwZNYynfz9/MPJww8frmb7Rtk42cJjgWEflUWMiIqlZ6wVNooCuGRKMWBJcWeor5M9vMFEOBMw2+PDUiNyj01kNGtcUmmTLRFGhjvnNWarWLHu7sONGBUy5Ipeopue7iGMv//tg5O0psifOXIvSl8FXHR4IOkgT2BrswBFhCoqIlh0piARSmXZjJTYU2ybXY6Gb5gn8MYhnqsQWVKYz9m9P4V2kW7WQw33DZhzMQrHDjJcz3eRxeqcTaDGWclIJEk0ySEtLQhWSjJbkzG2dkPyivwp02eJuGGBfeo8x7+pbBbkVt3aUPlWNqluGDp8bZTPl0iAVI+qzsW2oPvT2J7E5vtEqO9cKXm8b05zfz8GPoM7auc1jZBTGqvzaSUB1PAhkcZg/lP05piaa5erheK/AGOruW9mgUAAA==',
'kits/maintainer-defense-kit/locales/en/config.yml.tmpl': 'H4sIAAAAAAAC/zWOuw6DMAwAd77CP1CyZ+/QiQq6dEIOuCXCJCh2qBDi34v6GE86nc4xhrH1IpmkpYCOqbfwQBYquhgUO23Zh1FsAXCCgBNZaKjLyesKS+ZACZ3ngw4BICe2MKjOYo15eh2yK7s4mW2rz9equdyq+r7vRn4Fg/3iJSZPYgK9Pgl0MauFmuaYFP4mfB9hTn5BJV7L4g2UO1xKvAAAAA==',
'kits/maintainer-defense-kit/locales/en/pull_request_template.md': 'H4sIAAAAAAAC/0VSy27cMAy8+ysI7HWz/5BDgRyCIEgL9FAECC1xY2Jl0aUou83Xh5J3sQdDBh/DmSEPB3isNonyF0UoQRYahmfOF7CJgEupBKIwI2fzjxSU/lYqBpi93tCoV45Sc3QEqRZkptMwHA7wY+VIOTjiq0r79+ZFJdZgLNm7aSlHMEfzp+HRPwwGKymfOWAvcrTZU+UEP/uwmpWKpNVnJZ7ZelXZ572Szk65BXa485mClSbICW+il3OSDZZ72dFHGmnGBBENoeU9GCSb8lhN9GHlwmMiwE76SrSQGedPCBPmT3Jyv5Wd3EeWTB+wTaTesCzJVXjvzu5NUhoxXIbhBefdNdmao3flSq7dmuGRC96n7gBP1Z0AvK6rCx+GB/gD7/AIU0+2JahvxQ3rsBgCLVb6sFJH98vcuJ316dbrMmdscXRTWnMgmLDASJQbJaatJR3OWYUkpZf2Q2CleMN5EbclKNmxOVzkZuqx6VmU17a+tSaXjCMntv8QyY8q+ZkB55BqvIP9akfhe3A/OvmdcnROE64su2l6dRQWtOk0fAOrSg3DzAIAAA==',
'kits/maintainer-defense-kit/locales/ja/adoption-record.md': 'H4sIAAAAAAAC/0VS3W4SQRS+36eYhGtfoHfGxLtGgz4ACzvAxGVnM7tAesfsim5FjBGlEkhLW6iU/mAlJrZBeZjDDstVX8Ezi43JzmbOnO873/nLkF2TOT4eKohFi9TxKIm/f4ib42TydfP+h2E8Ilnqco/5XOwRkBNS4JUK83fQ8ayONH3ZMtTBWBvPBS8ym94vohzPe1TUaA4aMpc3bdMpUCs1yqawqIPG/WJfc16wkmPapM4ci9e1ipr01OBSdW/V/IsGZKs2xfc/II9Azoi3xYP8jFiQ16SITOaUPI19atpYRZozq1EvDTeYJt9O1c3dZthc96816rHrUgRq7/rnR3U40I8vy4J6ZW5b2IwC8xh3Uvb+Mpm2k0YzLY+KCvO0K+XGo+4mnED4FsIFBMtNQ66Wx3Ez0lAI5hDiF0E4hbAD4XgL0n95kZy1QEYQtOJ3t//ks9y282bhFfEFK5VwJFr86nR9cgdyqjrt1e8Upi6P4/4hEbTGaJ1Ypk93DCOTIXE0Ut0rbEcyWajW0DDU+VE8e6PCZjy8geDTpjcC+RpbiFSXCx8n4VPPJ6Lq4JXraZJtrQhOc55DMAN5kDa+B43giaA4N5+ZNhJcwWqo/T/Yg/7F6lc7OZOYMy6Lg7Xkq7g9hKVUf09nbGJps+2KPcTvQ9BBCeMv8WYMlpQCAAA=',
'kits/maintainer-defense-kit/locales/ja/bug.yml': 'H4sIAAAAAAAC/7VUTVPaQBi+8ysyvdeZtjdu7am3Hjx2egjJtmYMSRo2Vm/srlgQGdQOVasWRKZaWkXGdnQQ8ce8LB//ortL8Kso03F6Irx5s8/7fOzr6HEU1YAtAz3ipTpfWYyYKGH4loct14lqfCHXzbd4vtZj50Cq7dMcT53w5TTQ1UE7kDUgLSAbkKQRbGFbHPfo9Yvg3RvxG4m55lw0ommPNTzniTdx3Z823Q+OKGmajrFvxQKMElH1X9NmdDsQXZ21Mj9YB3IYQtDVTmWr+6sssZKknyTtix2eyQH5AfQEWBNoDdhPYAyYGGyeJ7PtRgPYR/XqQo6aWexvVNSo34DkgZSAFIDMy5mvhrMcL8BqEsuMajPITwgJ7pjU1mPIFvKc1/tFhZAXZ68AzUopJewpsD1gx0qbIpAa0GNgxcGM4RmerRtoyrVN5Au4JxNPJ55pI/snXz6PhPJYpi6NuZzDR+8Dy0diXuwH6BoZjGax7iP9kg9yZizfdeLIwfdz6q4e8TILazfC8GpSyA/su+Qlda0AK6nKipyT1ofEvwLLDZ935YPooWVVOQC2oOwi8pUw83O5n9yV8aHZdmtb2C4q7QsqzL+tpPLqgSL4yPNdMzDwWGc7W0l+lB/Ev5PJ9ksLoxThhXx/Z0n6rxoHPAafCgaD75RCVWDbikpaPovKDZ+bwDLtVlacI/q7hapIdrfSuCuvD9RAN3Cg22NyfVjsf9mUFJY2eLYwivqoq3f9ohX54RJP7YulAXRfhaZ5RZodiH0j7/XJWfts/T8RRbMeMjAyxxld5K1U6NwV239BNqaQMR1zZ1HiEttwnbeWH9fHB61bbvSqubDmejcQJUTYBvS3jJCMSprXWr36zl+bZm8YwsFGlh4MdbyDwC0MMUdvv9lhKbF3VWrTQ4fF7SMj1uqyWMBEWTevIDeBfroX8g9HekvqbwYAAA==',
'kits/maintainer-defense-kit/locales/ja/config.yml.tmpl': 'H4sIAAAAAAAC/23NsU4CMRjA8f2eoi8g3bs7MGHQhenSOyo0lJ65r4UQQkJ7wIAmRBN00kCMkhgcbsa3+VLjvQXEmf2f3z9RXPdiCWAFxELzRIk2I7dcgYjSTBuemlhJ3QMWEXJBNO8LRtAf0H9j8YHFFxYL9O9/s0X4KX+nu1NFiM0VI11j7oBR2pGma5NamvXpeNy8vGpc128azdZkQkGkNpdmRHl7ICHLpQCqxfCf4ElmzblTWK+q7QP6p+r1Lcz31fM9ul3YlOFxie4F3Se6FboNujW6GU59dASWUo104gAAAA==',
'kits/maintainer-defense-kit/locales/ja/pull_request_template.md': 'H4sIAAAAAAAC/3VSTW/aQBC9+1esxLk/oseeemilHqpKMWabWjF2ahui3FgbIRDQoqKEUFBDG0odaIIgqAfCx48ZdjH/orML+ZCiHLzWzs68efPeJBJElJZxvwrsBIIKsPP1MM9bY0175XkZSoAtMAZsSNK6afv4UZe49HOGej6B4BuEfQhvIBgCa0CObZo1fv39HksUa+JHG9Pi6CyelWSOxGtCLtC0RILE0UyUO5rGC9X114UolTedAqL4CI4/cXWx/jUF1hfdNmYSw0mndTsl2667021D0e7Hfy7ECK/XvPhvPcnjqzj7gg2fdBPRJfIDFvH5aHO+1LR3jnvw0XKOyCF106bnmY6NkLx7ugkjktJ9nchXDEFwI6cMi2rcOoS/IZxBsJQnG8S9MrAiBGVRr67mbcz3qO+b9j4xPun2PpWEebEhKs3HlOLLMZ/XVaQPLM87E14rSqH3VKCxh9pHz+gG4ZVsHQ4grEEYovya9vpIWoM197KhT1nq+o88TJmenrQo2RF91pfVFOsHEKih1Hpo2gvynnwg8fjv6vZWalxhwLoqscK7JdGaSFtqBXRDwfXu1moL3ZAMEH2L8jLjO2ndpymio+qer9uGUgl572py7NCxTOMYyQ/4AmVC1N7mtLwz/inkG2q4VG4Neuk5tm4pA+XdNbPYimQzFgqkJ03L9I9JiuIuW8qZ2gDYUiG2IKg/IL5VK343HS+fSMlYRFzHspK6caCKh4t49FNqpWZGHx+E/A8w2BH1WwMAAA==',
'kits/maintainer-defense-kit/locales/vi/adoption-record.md': 'H4sIAAAAAAAC/1WSwW7TQBCG736KkXIBKfQBeoMDQgJBKTxA1vbUO8p6191dJ+TMoQdOiBMHRKIIVRQqioSEZB96MMp77JswuyYiPdheef+Zf+abmcCT0H8At9uAtxT6txrmUhDUgrTnBy2UeIbaYZY9gFNsjCNv7AoWwxoKU9fkj/nixZKV8fC8GtarO6ni3xNrzkgh3JuZ3KFd4GwKs1wooQss41kKW6Lm8/0of0WVFgqWpEuzHJ2GTSFBhe7LCurQfWuj7DHfk64ceIkGbMsG0oTudwEuJUgaoRxCqpoW6FIyb3c3of+kK5Ch3zZQyN2NgIpCtyU4b1ehu01NPWwa5HD4837Ygpahu9Ypfh4FLGR9lL0cI1gW+ndacjUWnTSqTGJdJTOCRg5XULK2v0jZT9DW5BwZPValdxvau5Sh/wGKGV60kA9XmrOYYZ1QvmYSyeoj16/l8F2zYeKSh/4z8Uw0089bHlKUnxqlclHM40iqiqeZAEhhwMdEhRyuueQ3fP4/PYsLwiV4BnLbJLjHWTaZwKPQ/WTTgql95e/53b6z7BnFUkc6ljfF+hGd4qFwb4dNxT43KygYYT0Fj44j2pHuIScW9JcCTNyuI3gqh1/sW0nu0iLviyehptBYWgiPe8t/K3Dotp9MKfh1AIj3M2XkMi4jxtiwP8r+ArLlX0QUAwAA',
'kits/maintainer-defense-kit/locales/vi/bug.yml': 'H4sIAAAAAAAC/61UwW4TMRC95ytGnEMk4JYb7aUXJCQQQkIcHO8QW7trb712SI6oBw4cIKcKIURDVBUQFS2thLR74OCo/7F/wnizDSmEFEQv693Zsd/Me/OsWIpd2PATDUlV7spWhDk3MrNSqybOwyOtytd2nkKBE7CiKnfA+okEIavyueq0rLQJnXXt0YbrP6a11dPRqNsCuA52lNGflJk40k8VhQCYtUb2nMW8W38DDFjiKOuBk5D4r6pPp39OobcoYY5Tow/8Hgz9CYMcuUELQlfFNw5RVX6BJKS5sAWU8O8VWHN2VJVvOMRCQr8qD2VnqSipMmfrCmTUhQGanDr/Q4UJ62HShbtC+k8KelUxVefQXKeptBQrX0CICxAB9Z3qN3uzhHEUOonQEMyNzs3OrV/23tu63Wp4kBELAiyADW47aZAKtMbhUvUWh5YZZIsGUA2k0SpFZdc3ccefyoaat4siL4i/RTzCbEx0PnMg/J4SbTBOWZliGzLGY9ZH0lTRYtoQy6r4Hsj2Ba/1yZZZijBDFaHiI9InhLcdU53/b9dgZnTkuL1UtE0/4WJ5YMFW5ZgoCF87bhUBm46GkFfFfhamiUOvmSMrUNcG+BjOOG6HKnQurTajc0lnY1onClI/pSmsypfhWezb1d6Z++oK6GDcOpasJ2IrKAkDWZdxTN2Qbqu6n73yh5QY+w9p4/umt0T3qb+qOBiFpTyAnLnaW0uOvIJecJghtxj9ZTepVnVZ5VT+MzYXyOOeHmK+QOdaPZEmZZcP1sMwGkHdQ9X80NkF2IDT5N4PppuNaSh+Uz84h3xxwTWcrkFKD2M35UBy7VIWvXcWJ69oahVkLPxpTc/ZEQPSh5xoJUvaYHWMav31SV7WP2/hNdA/AHbgVvFLBgAA',
'kits/maintainer-defense-kit/locales/vi/config.yml.tmpl': 'H4sIAAAAAAAC/02NsUoDQRRF+/2K+wNm+ikFC0GIRBurZWYy2R129o3uexORkMJPsBDrENIIgvVuGfE/5k+MVjb3NIdzbTTU1YE5e649GRv9UmNlIvvKJRLjpI6BOtYVcAYyvde4KtMb2jK9UgNbxn1CX8YPORlAHqJGK3LPWqkmSJvtzKVebTaLi+v5zeXtfHG33Sr2Lg9BnpRZrgOnIXhW5B//EsamLBrnx12C+511GQ+Er5cyPf//w0M26I7v1GIIJzSQ789Z9QM81+a30wAAAA==',
'kits/maintainer-defense-kit/locales/vi/pull_request_template.md': 'H4sIAAAAAAAC/1WTQW/TMBiG7/4Vn9Rr6X8ADhwYaEK7IaQ5rqk/JbGDY7fkzIEDpx04IIToVKGKikmbBkJLDhw87X/4n/A5abvu0KZxnfd73+d1RiM4VrE9L2GOcHt2dxm7lYBKhQ1MfRO7j46xIwwbDXls/znAuvYSlIntXwFN2HgQsV2n727NoeSoHX2khXlYgk7/D8+987FdgQjXMMPYfUVIQ/WEsdEInsT2Ss9AqNj91DPGnvp0F9sfFVhZWTP1wqHRY3Cydr1wQca0gvfhXECJ9EuocLFdmMDLNNaGPw9G0Za7S56W2hWSnSbZGuYfS1tSLhpR9+ouyd6exe5LcnOEiQLkRGRhbP62MAuo9k+MYcodh341S5j0zIQlklfFzaCkVfil6Z4CNZDF7juCMNpZzLwzAyja3PQTPyPU0jnUswk8UwinuQq/E41wfQqaLPvUzQ1F6lbV4P6VKYqMi5yxPrdZ7OjfO7ByLq3btTbFmmeFfMBs4HDQ+rZQMkqJ0qn4how9gtfwBl4QGLdf7Wv75MFZ0lEUNuEqIV0++N6HUH2Vmi4X+jDqZCv4mDiU3MkpcGJaO66FpC1hdX8gKQHKRS9H9kVham8laUkDlSlQNDut53teBFJY6caprNpoXgxNpfGCyDrsz+XcF8SLZ1iga6CyOCcjO7GTdN4yolhRxjWosCRe9J4c1pUs2W0FE/YfNNAppk8DAAA=',
'kits/maintainer-defense-kit/profiles/observe/.github/workflows/pr-quality.yml': 'H4sIAAAAAAAC/41VTY8TORC951eUmOs4ZEcTdugTsMte+JhVFokDQqjark6bcduNy56ZRvz4LSedkE9EpCjdfs9Vz+WqF48dVfDvAr5ldDYNEGqmeI/JBj+ZBF9NAPrs3JdI3zJxKu8AaeiJK/gUevJkLiHS5omMTeWXB6/bGLz9Tp8nkwtYEBoVvNtLMIUPrWV4CPGuceEBPN1TBN2SvmMIOUGIQI+kcyIuGnUwNJ30FDvLLPu5qNHBJ/JJ5ETJIQuCZfr5WtSrUf1mdfJVVJTNazG0PlXMnkVjBbnOPmXlUPKmFcSJel6zABT4VdUWpEM0wLke9WyLyHbp0TFYD6klkGzC6jqMwxgCIHPR2BPeBean6JNV7EL/Yv7nzfyGROT1zXMzu6mv5vMG6ytsruc4nz3TVxqfkSzABdzPttEebGqr7RsI+J993FGmNsrWxZ3CPyFHaNC6HKW2GAlKiWwkAylA43AJKCWf7sTs8FFtdlRwvYPULug7MoolqCZVR5TLL6QnTw726xb9UpiNdQWfz87gzvqC/zGTzw5l1Kg6tD7Jl6LS6FUXjG2GClLMdIJtiHW0fem4I84qaeg6m1RHzLgk5cgvpZoi7lTqxsrNKk8PReJROO0Ck+pjJbV1vIuUEp0EpMG7PikTsUlC4KOYI6EOpX1/7AAAqMuhWEkzxX0gp9BYOZr9JPs+72GGZFYNyvoxFskHmU06RpbSYblWY8ZDfNRYdBxe+wiJojZEhcxBW1zfxe3H968Xl+9ev3slP3/dvn378tXt4uWH28Xxdoc1udOR+/grFGX6u9JtKaxa6kz5C00C/QZzl3JGzlnKBfxtGWtHYI14lgzkUzHAFOIALeUoj1bLNHoxlTQIq4/h0dLWR2pkKm033Qv5hqjfdVVogpaLMCCPrMWYiz9LJJO1rW1xgctVClkMbNfJh6Wl/biGEumkuMduda3F8k4UpbNeekIHMUwlw1PB4UQbcYxBNWLxvA9uBqrPtbNayhaKKZzOMIJlUnsnwqSyB9EKqxxIpjgWA1kN0nmCiqVUx4ylC7WM91nORvRoGWNTd5h0e0J5Of5P79kay2GJqAtfrVrV8Bgs/3kivCEZTU0Hh9oYb4+pPWW3aIygRez6H3I2+R8yVjsi8AcAAA==',
'kits/workflow-hardening/.github/workflows/dependency-review.yml': 'H4sIAAAAAAAC/0VPO27DMAzddQoCnZX6k6i1pg45QS5Q6EPFKhzaNSUHuX1lu0g2ku9LMjfUcMYJySO5B8y4RLwLMZIWAFMehu8ZfzNy0kJMON8icxyJV9SNlJAS66IyXoif0W733WOdypyJZTGDbDOlLAeTitcGccKJdxaABNqqXDYt+Fcj1xu6Iv/zADJjSTQurTXeX0S5x8od+TK1bz4b2yqrQnuqO2Pq0AanWhVU1bnjh/feNt0R3mA5HapD9Qy4x9Tr5wYQTBzKC5JxwTmmh4Y+XnvxB3SBTF47AQAA',
'kits/workflow-hardening/.github/workflows/zizmor.yml': 'H4sIAAAAAAAC/7VSy27bMBC86ysW6c2AHpQSyNQpRg7JPwRBwce6YiORCh827KL/XtKMHSNIjr0Q3F1yZnZ2NZtxgEflnwKHjfDKaAcORbDKH4BpNh2cckVh9FAALGGaflp8C+h8imOG+dHlK0AJN9Uv5cfA672xr9vJ7F29Wt18lFermp1IqsM8fZ1nubAEN2ZcbpkWI7oBnmem9Mv/5C0WtLNyLtkwwJ+/RfHb8BPPUR1nYzOjDdqV0REIPGgfyon5aEjWdfX/nUYY7VH7iGeRyVPSeVyu1OvTEB5GFK9ggo/vFuOUN/YA+9hWSkXcOAePEoRFGeEUm9w7AEBwyZ7ciatFAoqf7qloKOG8JbwXRBBJaApps5ZrirdIpZRb7LCBH7Drq6ZqLoCJdrhEcKYvr8gH2MYTPzWxSRtzRLjM4ZPGbKMwFut8LbPqe0JbbInsKeMtpeuu7xpGOkm6dQzvRNs1/ZYLxpLWprqr+m+1MrmLC4OyPK/xWek/y5qAvuwCAAA=',
'maintainer-defense-config.schema.json': 'H4sIAAAAAAAC/5VSTXPaMBS88ytcDQfc2hbQXsolF6a9lKGn9hAcRsHPtjL+qiSXMuD/3ifLJlJCJ1MGjLza3bdPT+eJ55GpPORQMrLySK5UI1eUPsm6Cg0c1SKjiWCposv5ch4ulnTgB72YJ7Yw4ypvH6NDXVKVsyorkiNlR5B1CWHJeKXwByJMIIVKAn0NhYe6SnkWDcV1EFNIcVWALrW5ary10XhG0wqm+JV+anp2/fgEB2UwliRcM1jxXdQNCMVBIidlhYSeIOBXywXoju6JSbD/DUL2rh6RbdMIkPpVkrhXNLbRGRHEXggRJxhQKlwtumDg2Faj0orNhGAnEowwV1DavNsNDjtvtTnQnGZFW8AeRxlonEnTb33EQ9YL+NMgU+4Rju1C1WmbavkV8rBXxzflVQaiETgyEnfBv4kNUzkyrgS7zo1DHnsYYuszHg9EKqyW6djoqUDoCZCHzTqcfd3+uPz8ctls1354Pw8/x+eP3ZQ4qZzAb7tqFxam8Xn56ZVT35Gb1xrb6Ofulrz6BlXWCxcv9uy6s7t31MdH9H52t3q4UH+3i/CLL/Qy9c3ODj9+9GFKLBs34TDp2206UVyduRf/LbNu0W1tWouS6WMnCVNAni/DuDL/+tlNuslfYTCVA70EAAA=',
'policies/AI_CONTRIBUTIONS.ja.md': 'H4sIAAAAAAAC/21TzW7aQBC++ylW6pPk2ENvVe8usRJLgKkxldITu+Y3QEwCaUtDBGpISB2EmzRViQnJw4zXmLfo7NpQWlUCyTs7Mzvfz7wgOy9J2PVC5x7oDbAasAawH2Djrwa2C3YH7EuwH4H9AnuMcUWJ7ibBfA7sJGxSoKPA94E2Q6fNqz6vlPn0Aa+W7Uo0vgD6CYoU7MmfNkmkAuxBvCCPYb0Y9utAe/g4H97zNsZvtgZjJ7z2bdm9BjoG2gL6JFKL7LVhpAmwElH3tKxFoqobLDpAvXi+YDaXxxugLrCmrDoD1sFCRUmmrbfEfZEuu4Ow1gZ6KhMHJGXsagToNUnplmrpRpYItKN+dJ0AyGspU7NkSs7U36uWRnZVSxVpq96IH83jtLSe0rJ5TYT5FKk9FGCbPd44FRR99SO3FaMmlpa3RFr0vOCHw7g6Z2D9ARHk3jJgdIsdb1VtRVfIvruhaPWxsRz5opJeAXWADgUeWkK8YctZFWVTOkAIYWOI3flovKo6MXKi5/MFTQQrraWDRE0FabREEGfB1K0DkkqregYTYrEjdxJ+PlrLITK5by+9En7zUT08Qy95McXyUZEgk/+aSlF2Cta+YeofEoqpx2czFIQ/lyW2cSzzjlQXb+NZueNF9gKZl00raLRl91b2PcaSDZJcIZ0mpvaugMTiMWVkMrr4MLWckdctw0RM+2p2T4oTLPpS/5g7T3Y+3nZM8HS+Gj6uDXIlEyTs6SC6KGNJ4DdiN5GMqmct/GsmzjxFtcKSI/R/ks3R+/Qc40gWP7vDdcGSVa/Np1/k64mzFeVNIY0d1Ld6WrAvqClPUGGsFHshQJiCk7Gc1hXMiq3CdT0HNktWF3PX5tzoaBoFS1h7xoe3/Pjwv3Z5tY3Ai20fzFqSdRxwsFmxqFjGOO+jFNNEbrT1Txb4ldjBsVvWRhf2498d+c7aJ7hWa28D7UhVm/+sufIbq+sHFKQEAAA=',
'policies/AI_CONTRIBUTIONS.md': 'H4sIAAAAAAAC/02TQY7bMAxF9zkFgS66yeQOKbqZRVGgN5BlZsJWFl2RSpCevl+MkwkQOIYlkf+/T32h4/tbMhNznilr9SZTd9Fqu93xne5LqWYmMUql6BX7rmeulPDLWXv1NBWmc1/wpdeZ2zgw254aX4SveHE2xx++kl6rkZ+ZrE+LoLrWAx3JVQtpo/TB1SmnWtVp4tjZ2FbokdHlqVDbYbf7xidtr6X2dOEmpxuhDLd09zRzdM7iKYwNYYtecI5zY7dYXZtcsJ/m5GmPQ8tabnQVP1ORzNV46J9naDqni2h7O2nuhvov3maxXNRC0GDgksorwcC2Nv3N2WlVFL5Byt8+KpD4gb4rDd/hx+mUpiY5TADkzCixfzEGx53pVFQHa3jpTfxGuSRZLKRrd9RHw7lnDzogPDVNgHFO9YNHEsnRc0sP3GnmE8OL1C0+YD521GryL/CRuRTYWtcibMiubqEt3Tzk63qfjhC4p7Vj+2YzyMJbKIE0NUGSt00OLdwY2CfOCWyBJBQ9LJNWPtDPyRPELXiMFx4z05iXIQHTgAm0QVYCEVKMlbvykdEf5pUsQ2MgQqVA8KwBu7+gq40UbEVQKHPpZSiYpIhLQGvaP87bcD5MfLXnED3DwD4HgkHlcSgByASB4+J9htmi54F+PG0ZLI40xzz1GmMtcdHCI64RTn0O/n2IN8roYQvuKthErm/3JPkR+2H3HzkKCe33AwAA',
'policies/AI_CONTRIBUTIONS.vi.md': 'H4sIAAAAAAAC/4VTzW7TQBC+5ylG4mrlHUpPCHHrC2w2q+wo9jq1dw3h2gNCHAAhhKoKtSaHqgEUqiIhbCEOW/U9/CZ8u07qCCFxiByPd+f7m3lAh7mxBU+c5dyQ9Dd08Ih0134gW3TtajS6fXP3DX8kTf1PMwtf55op69pTS2YWvn1kkrjxyuGKr6Umo7lrX2QUHicuoUJVrJ4mZFVpqfIXVHbteQDZOCrdJOOyBPiYjvI8JZ13zQ9JYqaMBZT/DlCLsyeU4qbcsc2Lf4OOR6OjIrA6k5HorGu/ckLar5b0DAcpY6PRZqoiE8lWROW3b7cybdfUeUIpaNRMpZKF6kkvCq6EVTQVVkCL859MJHZJKUtlSoWi9uusl6n9BXAqjlenXMo0L12hgnFXcs9gAPsaEudBYSC8yNFuSUu/diS75tKN6XHvQpRCk665xksQf4XnBB4INIFpNiE46VTEnvUKUIMEV7BdkkwFgx18an47hLIo8qmTUf3WdKvFMvRq3zMdO18TGJ6iVQYN2ygExyHpEwGXVU4VfI/U+5yRwIGzOi/4eW9t1TWfTTi7sTRx6CjHdLAf75737XVw6LwXktDCpSnaHjtYmiC1LGNYC5L7TKEkj368hNlf9ujFKMkAkWnqzGxMhzDUUCbYWPxUEVu8AwX/C3FsxwZ6zpg4W6QqA8texN9Uh3z64dCwAtOGGdyESBucDbU6CzOAwg1seehBp3IpgMWE05DJsRM092uMyn1MuzmTGC0RxSU79H4FQ6mwYRNlrM614DE9GUQNDgRgnBiWbNdpWIWdonuQcPt/i92Py54HC81QEQfC4FjXviZ9V4e18qvBNGzv0Ho3LX8ARWurWYMEAAA=',
'policies/UNSOLICITED_PULL_REQUESTS.ja.md': 'H4sIAAAAAAAC/4VT3VLaQBS+z1PsTB+gT9KrPkFkomQmJjQkdugVm6hAAYNa0BYdtEahRAgonaGI8DCbzc9b9OwmOsw4nV6Q2YRz9nx/5x0KVpfJ1ZLgNrEaBN8RvE/wEJ6oYCoK0qVPplQ0BCGsreNhM5xXCV4juVg0JUTwiuAewT7aFWXVgJ+kvzTAn430Hur2CW7Cmbq1sDuDclLGdNyLbw5oDb57UXceexWCz/h0h+ArBgZglK2oN+JIdK2gFUUFLvVYc9tJrhtwiPx92n1gX/xVPL1G27IiwVtRypm6bJTe57TdgmjIW7ICb9A8J3hMn6dJbw1VoXsRD5Zh50/4CNRP4sF5vKxxFMDqOwwXhLgypPU2EncklRFiwOOH++DpCerDBibYRXlzV1SB9J4sfQb2BA82xYB54flR5C6iH0xVJJpGXtPlLwBKUxHX5yzl9E8XdAmoy4aml1AuL6o7ILt1EjxfhNUWsZ9Cp0UrixftfN52zBl0iXUKJIg9IvaSWB6xPQRi5PIMVPStx/o5o02bUXZxGW/pogq1BbOY54oahqzuvCLA43gwpQ7M6/OpFrG+bk4VhKh+H3n1FFiqNJTH9jPLwcThBg+RUSpoKFiPw9ENXBN1Z+HRLXXOotZhJoRVp1cz2qryML0OYeZ8zDzm+ujcHYiJvCcaEtozFRBfzGxnmFWJZWeelMGzm7cx+7DpmJ9UmrFbeY0XdTssYW4/qTiZbiCapsi5EkpwmzpNZt/6ko6rwbyZUsxsgVT9eoxmE2LfE/uU2LfMisz0fvTbChaHXMHmRubSPcuSjX16PIhXC2C5LeYgBMyE/6qfLlcwh93x2PlxyOPnRe0Jmw6iZpvYoOuD+A6zBDJUoEzvRW+f4otw9JNBhVXpTDjmfVp1w86IpX8KQ/FbJYW/2fyXy1AEAAA=',
'policies/UNSOLICITED_PULL_REQUESTS.md': 'H4sIAAAAAAAC/02TPZLbMAyFe58CM2lt3WGrTIpUOzkARUEWshTJEKQc7enzQMkbFyr4B7z3Pegb/YqagnipPFFuIVDhP4216uXyph808pwKk6w58MqxSryTI22jVoeVC+QXF+9MdXGVFqcUE7mcS9pQUFQbUyq0OokVH5dn/YHeSJdUKuFuTopKWLYwESpXq8d2MqLtlfCY44SC6lPmKwX54LDTLIH1Ssq+Fam7NfJpza7KKME2eJ7ZV1xxcaKNi8zicZriIdH5ZYDNVtPqzL+7w6HS2rTCRiXXKhRaWRhepVJ7gVUYqqWmsp8IlB6C+w3vUN/71GAZ+mlpK3YKb8IPADAt/Df3Oq9gjm7y2QUO9J2x6U7g8OQXCsm7AN9TYn0RKJ9gnDmeV19CvGKly7E/Fhf9cu2QTK/tvnhQrtZLAeR9RReqe07AWQoIQhCwuB3jYM44G4BJ7AhyHgtHy2snZ6MCRAfNL+ZGYaD3Z0zWtYDzyCFBhMQzbNks+K0FM34maFIjh4F+foE6lPiQlGlqxhHP4Gueb+d43A90PN22FNqKnWyx7bdNTBtsdgwtPun3mHrGqt3rM8mJcRx62paeTcvTfu8Ftz2IzOV8S/hfyE2/nccs4dnsPPii5UA/5v//0TGFqMouKNKxASxypCuY2mKvj7nCgNeUO84zVtyP/IA4L9ZyuPwDwcxxEscDAAA=',
'policies/UNSOLICITED_PULL_REQUESTS.vi.md': 'H4sIAAAAAAAC/32UzW7TQBSF93mKK7GNwjOwYwOqVF5gMnU9o9jXU3tsanYVQl10gViwQBWiIYpQWyqoWlRhL1hMxXvMm3Bm2rgJSCwsZ37uz/nucR7RVp1lVCZ7dVJZmil3zSndvvv93fcLSa07q0n67ktNtgx7x3I0euoWLSnfv9XDJgLDQvv+NeO3wEKJFnl8/17j5ebIOsPpqoJ0V6Srqk4eau3Ure8PLanCdzebpX1/TrnQbPEk5YS2ysIUlciIU99943iNiUNE47slx8IHYzLKd/OcGj2mXZ0ltOP7S5pp3/3iMVk3l/HiB/RTJbIutW0fyyI3wuqpzrCixp2g1blUtB9u55rVZDR6kiZsQ1eXQ/zf4JpQZNCS+v5iE0mZmOJ/tBnhEK50fHMabnwMYY1OXpIE/aMwktga41Z/mMdmRW1VUepXkFAwle4HnhPU+AfhC5ApCFKRQPp+KWkKJevj4VTdnoqH7oxyp4Zy338is+YZUK4rRdNSMFKtSawSazWn4LUN+YJsC8kcfBPTWxXsMGTHQXfBkASqkmzQbaKrImFJGY5NlIghvlkbB23fjy4yLW0c+iLEadqrBc3cGStq6gyyxf1YTakbYZMJPRuIbPTkroIl6mkOiwaOAP0TO+jhxowBpnAnet1cNsJUkXSGkc9BpRlumCLTsl154Z7wXakpWi1C+q+UQ9qKfrTzMGt9h8OC5NaqZmhyMXwyD3FLA9HYPAjxu0LaoqTMfY6fyAVP6Hn0k85NluQw8Z1NMNk5vBpcxBsuRV5gbpAudsrBeN0S4lT4C8AgzlE1zGRfc6wbPB9ij4AcTjnWk9Ef7BGlX2IEAAA=',
}
_RULE_CATALOG: dict[str, dict] | None = None
class KitError(Exception):
pass
class UsageArgumentParser(argparse.ArgumentParser):
def error(self, message: str) -> None:
self.print_usage(sys.stderr)
self.exit(1, f"{self.prog}: error: {message}\n")
SEVERITY_ORDER = {"critical": 4, "high": 3, "medium": 2, "low": 1, "note": 0}
WORKFLOW_SUFFIXES = {".yml", ".yaml"}
PRIVILEGED_EVENTS = ("pull_request_target", "workflow_run", "issue_comment")
UNTRUSTED_EXPRESSION = re.compile(
r"\$\{\{\s*github\.(?:event\.(?:issue|pull_request|comment|review|head_commit)(?:[^}]*)|head_ref|ref_name)\s*\}\}"
)
WRITE_PERMISSION_SCOPES = frozenset(
{
"actions",
"artifact-metadata",
"attestations",
"checks",
"code-quality",
"contents",
"deployments",
"discussions",
"id-token",
"issues",
"packages",
"pages",
"pull-requests",
"security-events",
"statuses",
}
)
IDENTITY_PROXIES = {
"detect-spam-usernames": "username pattern",
"min-account-age": "account age",
"max-daily-forks": "fork activity",
"require-public-profile": "public-profile state",
"min-profile-completeness": "profile completeness",
"min-global-merge-ratio": "global merge history",
"require-commit-author-match": "commit-author identity",
}
def digest(content: bytes) -> str:
return hashlib.sha256(content).hexdigest()
def read(path: str) -> bytes:
if EMBEDDED_FILES:
try:
return gzip.decompress(base64.b64decode(EMBEDDED_FILES[path]))
except KeyError as exc:
raise KitError(f"standalone installer is missing embedded asset: {path}") from exc
return (ROOT / path).read_bytes()
def rule_catalog() -> dict[str, dict]:
global _RULE_CATALOG
if _RULE_CATALOG is not None:
return _RULE_CATALOG
try:
data = json.loads(read("auditor-rules.json"))
except (json.JSONDecodeError, UnicodeDecodeError) as exc:
raise KitError(f"invalid embedded auditor rule registry: {exc}") from exc
if data.get("schema_version") != 1 or not isinstance(data.get("rules"), list):
raise KitError("unsupported or incomplete auditor rule registry")
catalog: dict[str, dict] = {}
required = {
"id", "title", "default_severity", "description", "safe_remediation",
"help_anchor", "mappings",
}
for item in data["rules"]:
if not isinstance(item, dict) or set(item) != required:
raise KitError("auditor rule registry contains an invalid entry")
rule_id = item["id"]
if rule_id in catalog or not re.fullmatch(r"MD-(?:GOV|WF|MOD)-[0-9]{3}", rule_id):
raise KitError(f"invalid or duplicate auditor rule ID: {rule_id!r}")
if item["default_severity"] not in SEVERITY_ORDER:
raise KitError(f"invalid severity for auditor rule {rule_id}")
catalog[rule_id] = item
_RULE_CATALOG = catalog
return catalog
def rule_metadata(rule_id: str) -> dict:
try:
return rule_catalog()[rule_id]
except KeyError as exc:
raise KitError(f"finding uses undocumented rule ID: {rule_id}") from exc
def rule_help_uri(rule_id: str) -> str:
return f"{RULE_HELP_BASE}#{rule_metadata(rule_id)['help_anchor']}"
def detect_repository(target: Path) -> str | None:
result = subprocess.run(
["git", "-C", str(target), "remote", "get-url", "origin"],
text=True,
capture_output=True,
check=False,
)
if result.returncode:
return None
remote = result.stdout.strip()
match = re.match(
r"(?:https://github\.com/|ssh://git@github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git)?$",
remote,
)
return match.group(1) if match else None
def valid_repository(value: str) -> bool:
return bool(re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", value))
def localized_source(language: str, stem: str) -> str:
return f"kits/maintainer-defense-kit/locales/{language}/{stem}"
def policy_source(language: str, name: str) -> str:
suffix = "" if language == "en" else f".{language}"
return f"policies/{name}{suffix}.md"
def playbook_source(language: str) -> str:
return "docs/PLAYBOOK.md" if language == "en" else f"docs/{language}/PLAYBOOK.md"
def label_spec(language: str) -> bytes:
descriptions = {
"en": "Neutral queue for maintainer review; not an authorship or intent judgment.",
"vi": "Hàng đợi trung lập để maintainer review; không kết luận về tác giả hay ý định.",
"ja": "メンテナー確認用の中立的なキュー。作成者や意図を断定するものではありません。",
}
payload = {
"labels": [
{
"name": "needs-human-review",
"color": "D4C5F9",
"description": descriptions[language],
"optional_for_manual_triage": ["balanced", "hardened"],
}
]
}
return (json.dumps(payload, ensure_ascii=False, indent=2) + "\n").encode()
def desired_files(profile: str, language: str, repository: str) -> dict[str, bytes]:
config = read(localized_source(language, "config.yml.tmpl")).replace(
b"{{REPOSITORY}}", repository.encode()
)
files = {
".github/PULL_REQUEST_TEMPLATE.md": read(
localized_source(language, "pull_request_template.md")
),
".github/ISSUE_TEMPLATE/bug.yml": read(localized_source(language, "bug.yml")),
".github/ISSUE_TEMPLATE/config.yml": config,
".github/maintainer-defense-labels.json": label_spec(language),
"docs/maintainer-defense/AI_CONTRIBUTIONS.md": read(
policy_source(language, "AI_CONTRIBUTIONS")
),
"docs/maintainer-defense/UNSOLICITED_PULL_REQUESTS.md": read(
policy_source(language, "UNSOLICITED_PULL_REQUESTS")
),
"docs/maintainer-defense/PLAYBOOK.md": read(playbook_source(language)),
"docs/maintainer-defense/ADOPTION_RECORD.md": read(
localized_source(language, "adoption-record.md")
),
}
if profile == "observe":
files[".github/workflows/pr-quality.yml"] = read(
"kits/maintainer-defense-kit/profiles/observe/.github/workflows/pr-quality.yml"
)
else:
files[".github/workflows/pr-quality.yml"] = read(
"kits/balanced/.github/workflows/pr-quality.yml"
)
if profile == "hardened":
files[".github/workflows/dependency-review.yml"] = read(
"kits/workflow-hardening/.github/workflows/dependency-review.yml"
)
files[".github/workflows/zizmor.yml"] = read(
"kits/workflow-hardening/.github/workflows/zizmor.yml"
)
return files
def destination(target: Path, relative: str) -> Path:
candidate = Path(relative)
if candidate.is_absolute() or ".." in candidate.parts or not candidate.parts:
raise KitError(f"unsafe manifest path: {relative!r}")
path = target.joinpath(*candidate.parts)
current = target
for part in candidate.parts:
current = current / part
if current.is_symlink():
raise KitError(f"refusing to traverse symbolic link: {current}")
return path
def load_manifest(target: Path) -> dict:
path = target / MANIFEST
if not path.is_file():
raise KitError(f"no installation manifest found at {path}")
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise KitError(f"invalid installation manifest: {exc}") from exc
if data.get("schema_version") != 1 or not isinstance(data.get("files"), list):
raise KitError("unsupported or incomplete installation manifest")
seen = set()
for item in data["files"]:
if not isinstance(item, dict) or set(item) != {"path", "sha256", "owned"}:
raise KitError("installation manifest has an invalid file entry")
if (
not isinstance(item["path"], str)
or not re.fullmatch(r"[0-9a-f]{64}", item["sha256"])
or not isinstance(item["owned"], bool)
):
raise KitError("installation manifest has invalid path, digest, or ownership data")
destination(target, item["path"])
if item["path"] in seen:
raise KitError(f"duplicate installation manifest path: {item['path']}")
seen.add(item["path"])
return data
def check_manifest_files(target: Path, manifest: dict) -> list[str]:
problems = []
for item in manifest["files"]:
path = destination(target, item["path"])
if path.is_symlink() or not path.is_file():
problems.append(f"MISSING {item['path']}")
elif digest(path.read_bytes()) != item["sha256"]:
problems.append(f"MODIFIED {item['path']}")
return problems
def install(target: Path, profile: str, language: str, repository: str, apply: bool) -> None:
if (target / MANIFEST).exists():
raise KitError(f"{MANIFEST} already exists; verify or uninstall the current kit")
files = desired_files(profile, language, repository)
conflicts = []
entries = []
for relative, content in sorted(files.items()):
try:
path = destination(target, relative)
except KitError as exc:
conflicts.append(f"CONFLICT {relative}: {exc}")
continue
if path.is_symlink() or (path.exists() and not path.is_file()):
conflicts.append(f"CONFLICT {relative} is not a regular file")
elif path.is_file() and path.read_bytes() != content:
conflicts.append(f"CONFLICT {relative} already exists with different content")
else:
owned = not path.exists()
print(f"{'CREATE' if owned else 'KEEP '} {relative}")
entries.append({"path": relative, "sha256": digest(content), "owned": owned})
if conflicts:
raise KitError("refusing to overwrite existing content:\n" + "\n".join(conflicts))
if not apply:
print("DRY RUN: no files written; add --apply to install")
return
created: list[Path] = []
try:
for relative, content in sorted(files.items()):
path = destination(target, relative)
if not path.exists():
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(content)
created.append(path)
manifest = {
"schema_version": 1,
"kit_version": KIT_VERSION,
"profile": profile,
"language": language,
"repository": repository,
"installed_at": datetime.now(timezone.utc).isoformat(),
"files": entries,
}
temporary_manifest = target / f"{MANIFEST}.tmp"
temporary_manifest.write_text(
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
os.replace(temporary_manifest, target / MANIFEST)
except Exception:
(target / f"{MANIFEST}.tmp").unlink(missing_ok=True)
for path in reversed(created):
path.unlink(missing_ok=True)
remove_empty_parents(path, target)
raise
print(f"INSTALLED {profile}/{language}; manifest: {MANIFEST}")
def verify(target: Path) -> None:
manifest = load_manifest(target)
problems = check_manifest_files(target, manifest)
if problems:
raise KitError("installation verification failed:\n" + "\n".join(problems))
print(
f"VERIFIED {manifest['profile']}/{manifest['language']}: "
f"{len(manifest['files'])} files match the manifest"
)
def remove_empty_parents(path: Path, target: Path) -> None:
current = path.parent
while current != target and target in current.parents:
try:
current.rmdir()
except OSError:
break
current = current.parent
def uninstall(target: Path) -> None:
manifest = load_manifest(target)
problems = check_manifest_files(target, manifest)
owned_problems = [
problem
for problem in problems
if next(
item["owned"]
for item in manifest["files"]
if item["path"] == problem.split(maxsplit=1)[1]
)
]
if owned_problems:
raise KitError(
"refusing to remove modified or missing installer-owned files:\n"
+ "\n".join(owned_problems)
)
removed = []
for item in reversed(manifest["files"]):
if not item["owned"]:
continue
path = destination(target, item["path"])
path.unlink()
removed.append(path)
remove_empty_parents(path, target)
(target / MANIFEST).unlink()
print(f"UNINSTALLED {len(removed)} installer-owned files; pre-existing files were kept")
def relative_path(target: Path, path: Path) -> str:
return path.relative_to(target).as_posix()
def line_column(text: str, needle: str) -> tuple[int, int]:
offset = text.find(needle)
if offset < 0:
return 1, 1
line = text.count("\n", 0, offset) + 1
previous = text.rfind("\n", 0, offset)
return line, offset - previous
def unified_patch(path: str, before: str, after: str) -> str:
return "".join(
difflib.unified_diff(
before.splitlines(keepends=True),
after.splitlines(keepends=True),
fromfile=f"a/{path}",
tofile=f"b/{path}",
)
)
def make_finding(
rule_id: str,
severity: str,
confidence: str,
path: str,
line: int,
column: int,
message: str,
threat_scenario: str,
recommendation: str,
*,
before: str | None = None,
after: str | None = None,
fix_safety: str | None = None,
) -> dict:
metadata = rule_metadata(rule_id)
if severity != metadata["default_severity"]:
raise KitError(
f"finding severity {severity!r} does not match {rule_id} registry severity "
f"{metadata['default_severity']!r}"
)
finding = {
"rule_id": rule_id,
"severity": severity,
"confidence": confidence,
"location": {"path": path, "line": line, "column": column},
"message": message,
"threat_scenario": threat_scenario,
"recommendation": recommendation,
"fingerprint": hashlib.sha256(
f"{rule_id}:{path}:{message}".encode()
).hexdigest()[:24],
"fix": {"available": False},
}
if before is not None and after is not None and before != after:
finding["fix"] = {
"available": True,
"safety": fix_safety or "review-required",
"patch": unified_patch(path, before, after),
}
return finding
def replace_line_value(text: str, line_index: int, value: str) -> str:
lines = text.splitlines(keepends=True)
ending = "\n" if lines[line_index].endswith("\n") else ""
prefix = lines[line_index].split(":", 1)[0]
comment = ""
if " #" in lines[line_index]:
comment = " #" + lines[line_index].split(" #", 1)[1].rstrip("\n")
lines[line_index] = f"{prefix}: {value}{comment}{ending}"
return "".join(lines)
def governance_findings(target: Path) -> list[dict]:
findings: list[dict] = []
security_candidates = (
target / "SECURITY.md",
target / ".github/SECURITY.md",
target / "docs/SECURITY.md",
)
if not any(path.is_file() for path in security_candidates):
findings.append(
make_finding(
"MD-GOV-001", "medium", "high", "SECURITY.md", 1, 1,
"No repository security policy was found.",
"A reporter may disclose a vulnerability publicly or abandon the report because no private, supported route is documented.",
"Add SECURITY.md with supported versions, a private reporting route, response expectations, and a warning not to include secrets in public issues.",
)
)
codeowners_candidates = (
target / ".github/CODEOWNERS",
target / "CODEOWNERS",
target / "docs/CODEOWNERS",
)
codeowners = next((path for path in codeowners_candidates if path.is_file()), None)
if codeowners is None:
findings.append(
make_finding(
"MD-GOV-002", "medium", "high", ".github/CODEOWNERS", 1, 1,
"No CODEOWNERS file protects repository control-plane files.",
"A workflow, issue-template, or policy change can be merged without review from the people responsible for repository security and moderation.",
"Add a real owner for .github/workflows/, .github/ISSUE_TEMPLATE/, SECURITY.md, and CODEOWNERS; do not use a placeholder account.",
)
)
else:
content = codeowners.read_text(encoding="utf-8", errors="replace")
if not re.search(r"(?m)^\s*(?:/)?\.github(?:/|\s)", content):
rel = relative_path(target, codeowners)
findings.append(
make_finding(
"MD-GOV-003", "medium", "medium", rel, 1, 1,
"CODEOWNERS does not explicitly cover .github/.",
"A broad ownership rule may be changed or bypassed without an explicit review boundary for workflows and repository automation.",
"Add an explicit /.github/ ownership rule naming the responsible team.",
)
)
issue_dir = target / ".github/ISSUE_TEMPLATE"
issue_forms = (
[path for path in issue_dir.iterdir() if path.suffix in WORKFLOW_SUFFIXES and path.name != "config.yml"]
if issue_dir.is_dir() else []
)
if not issue_forms:
findings.append(
make_finding(
"MD-GOV-004", "low", "high", ".github/ISSUE_TEMPLATE", 1, 1,
"No structured issue form was found.",
"Unstructured reports can omit reproduction steps and expected behavior, increasing maintainer triage cost and security-report noise.",
"Add at least one YAML issue form that requests reproducible evidence and directs vulnerabilities to a private reporting channel.",
)
)
update_configs = (
target / ".github/dependabot.yml",
target / ".github/dependabot.yaml",
target / "renovate.json",
target / ".github/renovate.json",
target / "renovate.json5",
)
if not any(path.is_file() for path in update_configs):
findings.append(
make_finding(
"MD-GOV-005", "low", "high", ".github", 1, 1,
"No machine-readable dependency update policy was found.",
"Immutable Action and package pins can remain on vulnerable versions when no update mechanism or review cadence is configured.",
"Configure Dependabot or Renovate for the ecosystems used by the repository, including github-actions where applicable.",
)
)
local_expectation = any(
pattern in path.read_text(encoding="utf-8", errors="replace").lower()
for path in list(target.glob("*.md")) + list((target / "docs").glob("*.md") if (target / "docs").is_dir() else [])
for pattern in ("branch protection", "ruleset", "required review", "protected branch")
)
settings_files = (target / ".github/settings.yml", target / ".github/repository.yml")
if not local_expectation and not any(path.is_file() for path in settings_files):
findings.append(
make_finding(
"MD-GOV-006", "note", "medium", ".github", 1, 1,
"Branch-protection expectations are not documented in the checkout.",
"Contributors and maintainers cannot tell which reviews, status checks, or force-push restrictions are intended, and local auditing cannot detect configuration drift.",
"Document expected rulesets or branch protections and verify actual settings separately with a read-only GitHub API audit.",
)
)
return findings
def yaml_block(lines: list[str], index: int, indentation: int) -> str:
"""Return the surrounding indentation-delimited YAML block."""
start = index
while start > 0:
candidate = lines[start]
stripped = candidate.lstrip()
current = len(candidate) - len(stripped)
if current == indentation and stripped and not stripped.startswith("#"):
break
start -= 1
end = index + 1
while end < len(lines):
candidate = lines[end]
stripped = candidate.lstrip()
current = len(candidate) - len(stripped)
if stripped and not stripped.startswith("#") and current <= indentation:
break
end += 1
return "\n".join(lines[start:end])
def declared_write_scopes(lines: list[str], indentation: int) -> tuple[bool, set[str]]:
prefix = " " * indentation
for index, line in enumerate(lines):
match = re.match(rf"^{re.escape(prefix)}permissions\s*:\s*(.*?)\s*$", line)
if not match:
continue
value = match.group(1).split("#", 1)[0].strip()
if value == "write-all":
return True, set(WRITE_PERMISSION_SCOPES)
if value in {"{}", "read-all"}:
return True, set()
scopes: set[str] = set()
for candidate in lines[index + 1 :]:
stripped = candidate.lstrip()
current = len(candidate) - len(stripped)
if stripped and not stripped.startswith("#") and current <= indentation:
break
permission = re.match(r"\s*([a-z-]+)\s*:\s*write\b", candidate)
if permission and permission.group(1) in WRITE_PERMISSION_SCOPES:
scopes.add(permission.group(1))
return True, scopes
return False, set()
def permission_scope(lines: list[str], evidence_index: int) -> set[str]:
"""Return effective write or credential-minting scopes for one job."""
job_block = yaml_block(lines, evidence_index, 2).splitlines()
job_declared, job_scopes = declared_write_scopes(job_block, 4)
if job_declared:
return job_scopes
_, top_scopes = declared_write_scopes(lines, 0)
return top_scopes
def permission_scope_writes(lines: list[str], evidence_index: int) -> bool:
return bool(permission_scope(lines, evidence_index))
def run_block_injection(lines: list[str], run_index: int) -> tuple[int, int] | None:
"""Locate a direct untrusted expression in one run scalar or block."""
line = lines[run_index]
run_match = re.match(r"^(\s*)(?:-\s*)?run\s*:\s*(.*)$", line)
if not run_match:
return None
value = run_match.group(2)
inline = UNTRUSTED_EXPRESSION.search(value)
if inline:
value_column = line.find(value) if value else len(line)
return run_index + 1, value_column + inline.start() + 1
if value and not re.fullmatch(r"[|>][+-]?\d*", value.split("#", 1)[0].strip()):
return None
indentation = len(run_match.group(1))
for index in range(run_index + 1, len(lines)):
candidate = lines[index]
stripped = candidate.lstrip()
current = len(candidate) - len(stripped)
if stripped and not stripped.startswith("#") and current <= indentation:
break
match = UNTRUSTED_EXPRESSION.search(candidate)
if match:
return index + 1, match.start() + 1
return None
@dataclass(frozen=True)
class WorkflowRecord:
path: Path
name: str
triggers: frozenset[str]
workflow_run_names: frozenset[str]
uploads_artifact: bool
uploaded_artifact_names: frozenset[str]
downloads_artifact: bool
executes_downloaded_content: bool
executed_artifact_names: frozenset[str]
has_privileged_authority: bool
@dataclass(frozen=True)
class Suppression:
rule_id: str
reason: str
owner: str
expires_on: date
fingerprint: str | None = None
path: str | None = None
expired: bool = False
def load_suppressions(path: Path, today: date) -> list[Suppression]:
try:
data = json.loads(path.expanduser().read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
raise KitError(f"invalid suppression JSON {path}: {exc}") from exc
if not isinstance(data, dict) or set(data) != {"schema_version", "suppressions"}:
raise KitError("suppression config must contain only schema_version and suppressions")
if data["schema_version"] != 1 or not isinstance(data["suppressions"], list):
raise KitError("suppression config must use schema_version 1 and a suppressions array")
required = {"rule_id", "reason", "owner", "expires_on"}
allowed = required | {"fingerprint", "path"}
entries: list[Suppression] = []
selectors: set[tuple[str, str | None, str | None]] = set()
for index, item in enumerate(data["suppressions"]):
label = f"suppression #{index + 1}"
if not isinstance(item, dict) or not required <= set(item) or not set(item) <= allowed:
raise KitError(f"{label} has missing or unknown fields")
rule_id = item["rule_id"]
if not isinstance(rule_id, str) or rule_id not in rule_catalog():
raise KitError(f"{label} uses unknown rule_id: {rule_id!r}")
reason = item["reason"]
owner = item["owner"]
if not isinstance(reason, str) or not reason.strip():
raise KitError(f"{label} requires a non-empty reason")
if not isinstance(owner, str) or not owner.strip():
raise KitError(f"{label} requires a non-empty owner")
raw_expiry = item["expires_on"]
if not isinstance(raw_expiry, str) or not re.fullmatch(r"\d{4}-\d{2}-\d{2}", raw_expiry):
raise KitError(f"{label} expires_on must use YYYY-MM-DD")
try:
expires_on = date.fromisoformat(raw_expiry)
except ValueError as exc:
raise KitError(f"{label} has invalid expires_on date: {raw_expiry}") from exc
fingerprint = item.get("fingerprint")
relative = item.get("path")
if fingerprint is None and relative is None:
raise KitError(f"{label} requires fingerprint or path")
if fingerprint is not None and (
not isinstance(fingerprint, str) or not re.fullmatch(r"[0-9a-f]{24}", fingerprint)
):
raise KitError(f"{label} fingerprint must be 24 lowercase hexadecimal characters")
if relative is not None:
if not isinstance(relative, str) or not relative or "\\" in relative:
raise KitError(f"{label} path must be a non-empty repository-relative POSIX path")
normalized = PurePosixPath(relative)
if normalized.is_absolute() or ".." in normalized.parts or str(normalized) != relative:
raise KitError(f"{label} path must be a normalized repository-relative POSIX path")
selector = (rule_id, fingerprint, relative)
if selector in selectors:
raise KitError(f"duplicate suppression selector for {rule_id}")
selectors.add(selector)
entries.append(
Suppression(
rule_id=rule_id,
reason=reason.strip(),
owner=owner.strip(),
expires_on=expires_on,
fingerprint=fingerprint,
path=relative,
expired=expires_on < today,
)
)
return entries
def apply_suppressions(
report: dict, entries: list[Suppression]
) -> tuple[dict, int, list[str]]:
active = [(index, entry) for index, entry in enumerate(entries) if not entry.expired]
warnings = [
f"Suppression {entry.rule_id} owned by {entry.owner} expired on {entry.expires_on.isoformat()}"
for entry in entries
if entry.expired
]
matched: set[int] = set()
emitted: list[dict] = []
suppressed = 0
for finding in report["findings"]:
matching = []
for index, entry in active:
if entry.rule_id != finding["rule_id"]:
continue
if entry.fingerprint is not None and entry.fingerprint != finding["fingerprint"]:
continue
if entry.path is not None and entry.path != finding["location"]["path"]:
continue
matching.append(index)
if matching:
matched.update(matching)
suppressed += 1
else:
emitted.append(finding)
unmatched = [entry for index, entry in active if index not in matched]
if unmatched:
entry = unmatched[0]
selector = entry.fingerprint or entry.path
raise KitError(
f"active suppression {entry.rule_id} selector {selector!r} matches no finding"
)
return report_with_findings(report, emitted), suppressed, warnings
def inline_yaml_values(value: str) -> frozenset[str]:
value = value.split("#", 1)[0].strip()
if value.startswith("[") and value.endswith("]"):
value = value[1:-1]
return frozenset(
item.strip().strip("\"'")
for item in value.split(",")
if item.strip().strip("\"'")
)
def workflow_triggers(lines: list[str]) -> frozenset[str]:
for index, line in enumerate(lines):
match = re.match(r"^on\s*:\s*(.*?)\s*$", line)
if not match:
continue
value = match.group(1)
if value:
return inline_yaml_values(value)
triggers: set[str] = set()
for candidate in lines[index + 1 :]:
stripped = candidate.lstrip()
indentation = len(candidate) - len(stripped)
if stripped and not stripped.startswith("#") and indentation == 0:
break
event = re.match(r"^ ([A-Za-z_][A-Za-z0-9_-]*)\s*:", candidate)
if event:
triggers.add(event.group(1))
return frozenset(triggers)
return frozenset()
def workflow_run_sources(lines: list[str]) -> frozenset[str]:
for index, line in enumerate(lines):
if not re.match(r"^ workflow_run\s*:\s*$", line):
continue
for source_index in range(index + 1, len(lines)):
candidate = lines[source_index]
stripped = candidate.lstrip()
indentation = len(candidate) - len(stripped)
if stripped and not stripped.startswith("#") and indentation <= 2:
break
match = re.match(r"^ workflows\s*:\s*(.*?)\s*$", candidate)
if not match:
continue
value = match.group(1)
if value:
return inline_yaml_values(value)
names: set[str] = set()
for item in lines[source_index + 1 :]:
item_stripped = item.lstrip()
item_indentation = len(item) - len(item_stripped)
if item_stripped and not item_stripped.startswith("#") and item_indentation <= 4:
break
item_match = re.match(r"^\s+-\s*(.*?)\s*$", item)
if item_match:
names.update(inline_yaml_values(item_match.group(1)))
return frozenset(names)
return frozenset()
def yaml_job_bounds(lines: list[str], evidence_index: int) -> tuple[int, int]:
start = evidence_index
while start >= 0 and not re.match(r"^ [A-Za-z0-9_.-]+\s*:\s*$", lines[start]):
start -= 1
if start < 0:
return evidence_index, evidence_index + 1
end = start + 1
while end < len(lines):
stripped = lines[end].lstrip()
indentation = len(lines[end]) - len(stripped)
if stripped and not stripped.startswith("#") and indentation <= 2:
break
end += 1
return start, end
def run_scalar_text(lines: list[str], run_index: int) -> str:
match = re.match(r"^(\s*)(?:-\s*)?run\s*:\s*(.*)$", lines[run_index])
if not match:
return ""
chunks = [match.group(2)]
indentation = len(match.group(1))
for candidate in lines[run_index + 1 :]:
stripped = candidate.lstrip()
current = len(candidate) - len(stripped)
if stripped and not stripped.startswith("#") and current <= indentation:
break
chunks.append(stripped)
return "\n".join(chunks)
def action_inputs(lines: list[str], action_index: int) -> dict[str, str]:
action_indent = len(lines[action_index]) - len(lines[action_index].lstrip())
with_indent: int | None = None
inputs: dict[str, str] = {}
for candidate in lines[action_index + 1 :]:
stripped = candidate.lstrip()
indentation = len(candidate) - len(stripped)
if stripped and indentation <= action_indent:
break
if re.match(r"^with\s*:\s*$", stripped):
with_indent = indentation
continue
if with_indent is None:
continue
if stripped and indentation <= with_indent:
break
match = re.match(r"^([A-Za-z0-9_-]+)\s*:\s*(.*?)\s*$", stripped)
if match:
inputs[match.group(1)] = match.group(2).split(" #", 1)[0].strip().strip("\"'")
return inputs
def literal_relative_path(value: str) -> PurePosixPath | None:
if not value or "${{" in value or any(character in value for character in "`$<>\\"):
return None
path = PurePosixPath(value.removeprefix("./"))
if path.is_absolute() or ".." in path.parts:
return None
return path
def executed_local_paths(command: str) -> frozenset[PurePosixPath]:
paths: set[PurePosixPath] = set()
interpreters = {"bash", "sh", "python", "python3", "node", "ruby", "perl"}
for segment in re.split(r"(?:&&|\|\||[;|\n])", command):
try:
tokens = shlex.split(segment, comments=True, posix=True)
except ValueError:
continue
while tokens and re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*=.*", tokens[0]):
tokens.pop(0)
if not tokens:
continue
candidate: str | None = None
executable = PurePosixPath(tokens[0]).name
if executable in interpreters:
for token in tokens[1:]:
if token in {"-c", "-m"}:
candidate = None
break
if not token.startswith("-"):
candidate = token
break
elif tokens[0] in {"source", "."} and len(tokens) > 1:
candidate = tokens[1]
elif "/" in tokens[0] and not tokens[0].startswith("/"):
candidate = tokens[0]
if candidate:
path = literal_relative_path(candidate)
if path is not None:
paths.add(path)
return frozenset(paths)
def path_is_within(path: PurePosixPath, destination: PurePosixPath) -> bool:
return destination == PurePosixPath(".") or path == destination or destination in path.parents
def index_workflows(target: Path) -> list[WorkflowRecord]:
records: list[WorkflowRecord] = []
workflow_dir = target / ".github/workflows"
if not workflow_dir.is_dir():
return records
for path in sorted(workflow_dir.rglob("*")):
if not path.is_file() or path.suffix not in WORKFLOW_SUFFIXES:
continue
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
name_match = next((re.match(r"^name\s*:\s*(.*?)\s*$", line) for line in lines if re.match(r"^name\s*:", line)), None)
name = name_match.group(1).strip("\"'") if name_match else path.stem
upload_indexes = [
index for index, line in enumerate(lines)
if re.search(r"uses:\s*[\"']?actions/upload-artifact@", line)
]
uploaded_names: set[str] = set()
for upload_index in upload_indexes:
upload_name = action_inputs(lines, upload_index).get("name", "artifact")
if upload_name and "${{" not in upload_name:
uploaded_names.add(upload_name)
download_indexes = [
index for index, line in enumerate(lines)
if re.search(r"uses:\s*[\"']?actions/download-artifact@", line)
]
executed_names: set[str] = set()
privileged = False
for download_index in download_indexes:
inputs = action_inputs(lines, download_index)
destination = literal_relative_path(inputs.get("path", "."))
remote_run = "github.event.workflow_run.id" in inputs.get("run-id", "")
authenticated = bool(inputs.get("github-token"))
if destination is None or not remote_run or not authenticated:
continue
_, job_end = yaml_job_bounds(lines, download_index)
for execution_index in range(download_index + 1, job_end):
if re.match(r"^\s*(?:-\s*)?run\s*:", lines[execution_index]):
executed_paths = executed_local_paths(
run_scalar_text(lines, execution_index)
)
else:
local_action = re.match(
r"^\s*-\s*uses:\s*[\"']?(\./[A-Za-z0-9_.-]+(?:/[A-Za-z0-9_.-]+)*)",
lines[execution_index],
)
executed_path = (
literal_relative_path(local_action.group(1)) if local_action else None
)
executed_paths = (
frozenset({executed_path})
if executed_path is not None
else frozenset()
)
if not any(
path_is_within(executed_path, destination)
for executed_path in executed_paths
):
continue
job_start, _ = yaml_job_bounds(lines, execution_index)
job_text = "\n".join(lines[job_start:job_end])
execution_is_privileged = bool(
permission_scope(lines, execution_index)
or re.search(r"\$\{\{\s*secrets\.|secrets:\s*inherit", job_text)
)
if execution_is_privileged:
name = inputs.get("name")
if name and "${{" not in name:
executed_names.add(name)
elif not name:
executed_names.add("*")
privileged = True
break
records.append(
WorkflowRecord(
path=path,
name=name,
triggers=workflow_triggers(lines),
workflow_run_names=workflow_run_sources(lines),
uploads_artifact=bool(upload_indexes),
uploaded_artifact_names=frozenset(uploaded_names),
downloads_artifact=bool(download_indexes),
executes_downloaded_content=bool(executed_names),
executed_artifact_names=frozenset(executed_names),
has_privileged_authority=privileged,
)
)
return records
def artifact_trust_findings(target: Path, records: list[WorkflowRecord]) -> list[dict]:
producers = [
record for record in records
if record.uploads_artifact and "pull_request" in record.triggers
]
findings: list[dict] = []
for consumer in records:
if not (
"workflow_run" in consumer.triggers
and consumer.downloads_artifact
and consumer.executes_downloaded_content
and consumer.has_privileged_authority
):
continue
for producer in producers:
if producer.name not in consumer.workflow_run_names:
continue
if not (
"*" in consumer.executed_artifact_names
or producer.uploaded_artifact_names & consumer.executed_artifact_names
):
continue
producer_rel = relative_path(target, producer.path)
consumer_rel = relative_path(target, consumer.path)
text = consumer.path.read_text(encoding="utf-8", errors="replace")
line, column = line_column(text, "actions/download-artifact@")
findings.append(
make_finding(
"MD-WF-008", "critical", "high", consumer_rel, line, column,
f"Privileged workflow executes an artifact from pull-request workflow {producer_rel}.",
f"Untrusted pull-request code in {producer_rel} can alter an uploaded artifact that {consumer_rel} downloads and executes with secrets, OIDC, or repository write authority.",
"Treat the artifact as untrusted data: verify an immutable digest and parse it without execution, or rebuild it from trusted source in the privileged workflow.",
)
)
return findings
def workflow_findings(target: Path, path: Path) -> list[dict]:
findings: list[dict] = []
rel = relative_path(target, path)
text = path.read_text(encoding="utf-8", errors="replace")
lines = text.splitlines()
top_permissions = any(re.match(r"^permissions\s*:", line) for line in lines)
if not top_permissions:
insertion = "permissions: {}\n\n"
insert_at = 0
if lines and re.match(r"^name\s*:", lines[0]):
first_end = text.find("\n") + 1
insert_at = first_end if first_end > 0 else len(text)
after = text[:insert_at] + insertion + text[insert_at:]
findings.append(
make_finding(
"MD-WF-001", "medium", "high", rel, 1, 1,
"Workflow has no top-level permissions boundary.",
"A newly added job can silently inherit broader repository-default GITHUB_TOKEN permissions than its author intended.",
"Declare top-level permissions: {} and grant the minimum permissions required by each job.",
before=text, after=after, fix_safety="review-required",
)
)
for index, line in enumerate(lines):
if re.search(r"\bpermissions\s*:\s*write-all\b", line):
after = replace_line_value(text, index, "{}")
findings.append(
make_finding(
"MD-WF-002", "high", "high", rel, index + 1, line.find("write-all") + 1,
"Workflow grants write-all token permissions.",
"Any compromised Action or command in this permission scope can modify repository content and other sensitive resources.",
"Replace write-all with an empty default and grant only the exact job-level write permissions required.",
before=text, after=after, fix_safety="review-required",
)
)
uses = re.search(r"\buses:\s*[\"']?([^@\"'\s]+)@([^\"'\s#]+)[\"']?", line)
if uses and not re.fullmatch(r"[0-9a-fA-F]{40}", uses.group(2)):
findings.append(
make_finding(
"MD-WF-003", "medium", "high", rel, index + 1, uses.start(2) + 1,
f"Action {uses.group(1)} is not pinned to a full commit SHA.",
"A mutable tag or branch can move to compromised code without a reviewed workflow change.",
"Resolve the reviewed release to a 40-character commit SHA, retain the release tag in a comment, and configure automated pin updates.",
)
)
privileged = [event for event in PRIVILEGED_EVENTS if re.search(rf"(?m)^\s{{0,2}}{event}\s*:", text)]
checkout_indexes = [
index for index, line in enumerate(lines)
if re.search(r"uses:\s*[\"']?actions/checkout@", line)
]
checkout = bool(checkout_indexes)
untrusted_ref = bool(re.search(r"github\.(?:event\.pull_request\.(?:head\.(?:sha|ref)|head)|head_ref)|refs/pull/", text))
if privileged and checkout and untrusted_ref:
needle = privileged[0]
line, column = line_column(text, needle)
findings.append(
make_finding(
"MD-WF-004", "high", "high", rel, line, column,
f"Privileged event {needle} checks out an attacker-influenced revision.",
"A fork or attacker-controlled event influences the checked-out revision inside a context that can receive base-repository authority.",
"Split metadata handling from untrusted-code execution; use pull_request with read-only permissions for code execution and never check out a fork head in the privileged job.",
)
)
if privileged:
untrusted_pattern = re.compile(
r"github\.(?:event\.pull_request\.(?:head\.(?:sha|ref)|head)|head_ref)|refs/pull/"
)
for evidence_index, evidence_line in enumerate(lines):
if not untrusted_pattern.search(evidence_line):
continue
job_block = yaml_block(lines, evidence_index, 2)
job_has_secrets = bool(
re.search(r"\$\{\{\s*secrets\.|secrets:\s*inherit", job_block)
)
scopes = permission_scope(lines, evidence_index)
if not job_has_secrets and not scopes:
continue
needle = "secrets." if "secrets." in job_block else evidence_line.strip()
line, column = line_column(text, needle)
findings.append(
make_finding(
"MD-WF-005", "critical", "high", rel, line, column,
"Untrusted pull-request input can reach a privileged workflow with secrets or write authority.",
"An attacker can modify fork code or metadata so a privileged job executes attacker-controlled commands and exfiltrates credentials or changes the repository.",
"Remove the untrusted checkout/execution path from the privileged event and move it to an isolated pull_request workflow with no secrets and read-only permissions.",
)
)
break
for checkout_index in checkout_indexes:
checkout_line = lines[checkout_index]
indentation = len(checkout_line) - len(checkout_line.lstrip())
step_block = yaml_block(lines, checkout_index, max(0, indentation - 2))
if permission_scope_writes(lines, checkout_index) and "persist-credentials: false" not in step_block:
column = checkout_line.find("actions/checkout@") + 1
findings.append(
make_finding(
"MD-WF-006", "medium", "medium", rel, checkout_index + 1, column,
"Checkout may persist a write-capable token in the workspace.",
"A later command or compromised build step can recover the credential from Git configuration and use its repository authority.",
"Set persist-credentials: false unless a reviewed step explicitly needs authenticated Git operations.",
)
)
for run_index, run_line in enumerate(lines):
if not re.match(r"^\s*(?:-\s*)?run\s*:", run_line):
continue
injection = run_block_injection(lines, run_index)
if not injection:
continue
line, column = injection
findings.append(
make_finding(
"MD-WF-007", "high", "high", rel, line, column,
"Untrusted GitHub event data is interpolated directly into a shell command.",
"An attacker can craft event text that changes the generated shell program and executes unintended commands in the job.",
"Assign the expression to an environment variable or pass it as a reviewed action input, then quote the shell variable for the selected shell.",
)
)
destructive_lines: list[int] = []
for index, line in enumerate(lines):
destructive = re.search(r"\b(close-pr|lock-pr|auto-close|delete-(?:issue|pr))\s*:\s*(true|yes)\b", line, re.IGNORECASE)
if destructive:
destructive_lines.append(index)
after = replace_line_value(text, index, "false")
findings.append(
make_finding(
"MD-MOD-001", "high", "high", rel, index + 1, destructive.start(1) + 1,
f"Automation enables destructive operation {destructive.group(1)}.",
"A false positive, poisoned classifier input, or configuration mistake can close, lock, or delete legitimate contributor work without human review.",
"Start in report-only mode, require human review, publish an appeal path, and enable destructive behavior only after measuring false positives.",
before=text, after=after, fix_safety="safe",
)
)
proxy = re.search(r"\b(" + "|".join(re.escape(key) for key in IDENTITY_PROXIES) + r")\s*:\s*([^\s#]+)", line)
if proxy:
key, raw = proxy.group(1), proxy.group(2).lower()
enabled = raw in {"true", "yes"} or (raw.isdigit() and int(raw) > 0)
if enabled:
disabled = "false" if raw in {"true", "yes"} else "0"
after = replace_line_value(text, index, disabled)
findings.append(
make_finding(
"MD-MOD-002", "medium", "high", rel, index + 1, proxy.start(1) + 1,
f"Automated triage uses {IDENTITY_PROXIES[key]} as a contributor-risk proxy.",
"A legitimate newcomer can be penalized for identity or history characteristics unrelated to the quality and safety of the submitted change.",
"Disable the proxy and evaluate reproducibility, scope, tests, policy compliance, and contributor responsiveness instead.",
before=text, after=after, fix_safety="safe",
)
)
if destructive_lines:
policy_text = "\n".join(
path.read_text(encoding="utf-8", errors="replace")
for path in list(target.glob("*.md")) + list((target / "docs").glob("*.md") if (target / "docs").is_dir() else [])
).lower()
if not any(term in policy_text for term in ("appeal", "reopen", "kháng nghị", "異議")):
first = destructive_lines[0]
findings.append(
make_finding(
"MD-MOD-003", "medium", "medium", rel, first + 1, 1,
"Destructive moderation is enabled without a discoverable appeal or reopening policy.",
"A false positive can become permanent because contributors have no documented path to request human reconsideration.",
"Document the appeal channel, review owner, response expectation, and process for reopening false positives before enabling enforcement.",
)
)
return findings
def audit_repository(target: Path) -> dict:
findings = governance_findings(target)
records = index_workflows(target)
for record in records:
findings.extend(workflow_findings(target, record.path))
findings.extend(artifact_trust_findings(target, records))
findings.sort(
key=lambda item: (
-SEVERITY_ORDER[item["severity"]],
item["location"]["path"],
item["location"]["line"],
item["rule_id"],
)
)
counts = {severity: 0 for severity in SEVERITY_ORDER}
for finding in findings:
counts[finding["severity"]] += 1
return {
"schema_version": 1,
"tool": {"name": "maintainer-defense", "version": AUDITOR_VERSION},
"target": str(target),
"summary": {"total": len(findings), "by_severity": counts},
"findings": findings,
}
def report_with_findings(report: dict, findings: list[dict]) -> dict:
counts = {severity: 0 for severity in SEVERITY_ORDER}
for finding in findings:
counts[finding["severity"]] += 1
updated = dict(report)
updated["findings"] = findings
updated["summary"] = {"total": len(findings), "by_severity": counts}
return updated
def load_baseline(path: Path) -> set[str]:
try:
data = json.loads(path.expanduser().read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
raise KitError(f"invalid baseline JSON {path}: {exc}") from exc
if not isinstance(data, dict) or data.get("schema_version") != 1:
raise KitError("baseline must be a schema-v1 maintainer-defense JSON report")
findings = data.get("findings")
if not isinstance(findings, list):
raise KitError("baseline report must contain a findings array")
fingerprints: set[str] = set()
for finding in findings:
fingerprint = finding.get("fingerprint") if isinstance(finding, dict) else None
if not isinstance(fingerprint, str) or not fingerprint:
raise KitError("every baseline finding must contain a non-empty fingerprint")
fingerprints.add(fingerprint)
return fingerprints
def filter_new(report: dict, baseline_fingerprints: set[str]) -> dict:
findings = [
finding for finding in report["findings"]
if finding["fingerprint"] not in baseline_fingerprints
]
return report_with_findings(report, findings)
def audit_git_ref(target: Path, ref: str) -> dict:
"""Archive and audit one local commit without executing repository code."""
verify = subprocess.run(
["git", "-C", str(target), "rev-parse", "--verify", "--end-of-options", f"{ref}^{{commit}}"],
text=True,
capture_output=True,
check=False,
)
if verify.returncode != 0:
raise KitError(f"unknown or non-commit Git ref: {ref}")
commit = verify.stdout.strip()
archived = subprocess.run(
["git", "-C", str(target), "archive", "--format=tar", commit],
capture_output=True,
check=False,
)
if archived.returncode != 0:
detail = archived.stderr.decode(errors="replace").strip()
raise KitError(f"cannot archive Git ref {ref}: {detail}")
with tempfile.TemporaryDirectory(prefix="maintainer-defense-ref-") as tmp:
snapshot = Path(tmp)
with tarfile.open(fileobj=io.BytesIO(archived.stdout), mode="r:") as archive:
for member in archive.getmembers():
parts = Path(member.name).parts
if member.name.startswith("/") or ".." in parts:
raise KitError(f"unsafe path in Git archive: {member.name}")
if not member.isfile():
continue
source = archive.extractfile(member)
if source is None:
continue
destination = snapshot.joinpath(*parts)
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(source.read())
return audit_repository(snapshot)
def summary_headline(report: dict) -> str:
summary = report["summary"]
return " · ".join(
[f"{summary['total']} finding{'s' if summary['total'] != 1 else ''}"]
+ [
f"{count} {severity}"
for severity, count in summary["by_severity"].items()
if count
]
)
def render_summary(report: dict) -> str:
rows = [summary_headline(report)]
if report["findings"]:
rows.append("")
for finding in report["findings"]:
rows.append(
f"{finding['severity'].upper():8} {finding['rule_id']} {finding['message']}"
)
return "\n".join(rows) + "\n"
def render_human(report: dict) -> str:
headline = summary_headline(report)
if not report["findings"]:
return headline + "\n"
rows = [headline, ""]
for finding in report["findings"]:
location = finding["location"]
rows.extend(
[
f"{finding['severity'].upper():8} {finding['rule_id']} {location['path']}:{location['line']}:{location['column']}",
f" Evidence: {finding['message']}",
f" Risk: {finding['threat_scenario']}",
f" Safe remediation: {finding['recommendation']}",
f" Rule: {rule_help_uri(finding['rule_id'])}",
]
)
if finding["fix"]["available"]:
rows.append(f" Patch: available ({finding['fix']['safety']})")
rows.append("")
return "\n".join(rows) + "\n"
def render_sarif(report: dict) -> dict:
rules: dict[str, dict] = {}
results = []
levels = {"critical": "error", "high": "error", "medium": "warning", "low": "note", "note": "note"}
for finding in report["findings"]:
rule_id = finding["rule_id"]
metadata = rule_metadata(rule_id)
mappings = [f"{item['framework']}:{item['id']}" for item in metadata["mappings"]]
rules.setdefault(
rule_id,
{
"id": rule_id,
"name": rule_id.replace("-", "_"),
"shortDescription": {"text": metadata["title"]},
"fullDescription": {"text": metadata["description"]},
"help": {"text": metadata["safe_remediation"]},
"helpUri": rule_help_uri(rule_id),
"properties": {
"security-severity": str(SEVERITY_ORDER[metadata["default_severity"]] * 2.5),
"tags": mappings,
},
},
)
location = finding["location"]
result = {
"ruleId": rule_id,
"level": levels[finding["severity"]],
"message": {"text": finding["message"]},
"partialFingerprints": {"maintainerDefenseFingerprint/v1": finding["fingerprint"]},
}
if (Path(report["target"]) / location["path"]).is_file():
result["locations"] = [
{
"physicalLocation": {
"artifactLocation": {"uri": location["path"]},
"region": {"startLine": location["line"], "startColumn": location["column"]},
}
}
]
results.append(result)
return {
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": report["tool"]["name"],
"version": report["tool"]["version"],
"informationUri": "https://github.com/thangldw/awesome-maintainer-defense",
"rules": list(rules.values()),
}
},
"results": results,
}
],
}
def combined_patch(report: dict, safe_only: bool) -> str:
selected = [
finding for finding in report["findings"]
if finding["fix"]["available"]
and (not safe_only or finding["fix"]["safety"] == "safe")
]
by_path: dict[str, list[dict]] = {}
for finding in selected:
by_path.setdefault(finding["location"]["path"], []).append(finding)
patches: list[str] = []
target = Path(report["target"])
for relative, file_findings in sorted(by_path.items()):
path = target / relative
before = path.read_text(encoding="utf-8", errors="replace")
lines = before.splitlines(keepends=True)
insert_permissions = any(item["rule_id"] == "MD-WF-001" for item in file_findings)
for finding in sorted(file_findings, key=lambda item: item["location"]["line"], reverse=True):
index = finding["location"]["line"] - 1
if finding["rule_id"] == "MD-WF-002":
ending = "\n" if lines[index].endswith("\n") else ""
prefix = lines[index].split(":", 1)[0]
lines[index] = f"{prefix}: {{}}{ending}"
elif finding["rule_id"] in {"MD-MOD-001", "MD-MOD-002"}:
ending = "\n" if lines[index].endswith("\n") else ""
prefix = lines[index].split(":", 1)[0]
current = lines[index].split(":", 1)[1].strip().split("#", 1)[0].strip().lower()
disabled = "false" if current in {"true", "yes"} else "0"
lines[index] = f"{prefix}: {disabled}{ending}"
after = "".join(lines)
if insert_permissions:
first_end = after.find("\n") + 1 if re.match(r"^name\s*:", after) else 0
after = after[:first_end] + "permissions: {}\n\n" + after[first_end:]
if before != after:
patches.append(unified_patch(relative, before, after))
return "".join(patches)
def parse_install_args(arguments: list[str] | None = None) -> argparse.Namespace:
parser = UsageArgumentParser(description=__doc__)
parser.add_argument("--target", required=True, type=Path, help="target repository")
parser.add_argument("--profile", choices=PROFILES, default="observe")
parser.add_argument("--language", choices=LANGUAGES, default="en")
parser.add_argument("--repo", help="GitHub OWNER/REPOSITORY (auto-detected when possible)")
mode = parser.add_mutually_exclusive_group()
mode.add_argument("--apply", action="store_true", help="write the previewed installation")
mode.add_argument("--verify", action="store_true", help="verify files against the manifest")
mode.add_argument("--uninstall", action="store_true", help="remove unmodified installer-owned files")
return parser.parse_args(arguments)
def parse_audit_args(command: str, arguments: list[str]) -> argparse.Namespace:
parser = UsageArgumentParser(prog=f"maintainer-defense {command}")
parser.add_argument("target", nargs="?", default=".", type=Path, help="repository checkout")
if command == "audit":
parser.add_argument(
"--format", choices=("human", "summary", "json", "sarif"), default="human"
)
parser.add_argument("--output", type=Path, help="write output to a file")
parser.add_argument(
"--fail-on", choices=("critical", "high", "medium", "low", "note"),
help="exit 2 when a finding at or above this severity is present",
)
comparison = parser.add_mutually_exclusive_group()
comparison.add_argument("--baseline", type=Path, help="schema-v1 JSON report")
comparison.add_argument("--compare-ref", help="local Git commit to audit as baseline")
parser.add_argument(
"--config", type=Path,
help="suppression config (default: TARGET/.maintainer-defense.json when present)",
)
parser.add_argument(
"--new-only", action="store_true",
help="emit only fingerprints absent from exactly one comparison source",
)
else:
parser.add_argument("--output", type=Path, help="write unified diff to a file")
parser.add_argument("--safe-only", action="store_true", help="exclude review-required patches")
parser.add_argument(
"--dry-run", action="store_true",
help="compatibility flag; fix always emits a patch and never edits files",
)
args = parser.parse_args(arguments)
if command == "audit":
has_comparison = bool(args.baseline or args.compare_ref)
if args.new_only and not has_comparison:
parser.error("--new-only requires exactly one of --baseline or --compare-ref")
if has_comparison and not args.new_only:
parser.error("--baseline and --compare-ref require --new-only")
return args
def emit_output(content: str, output: Path | None) -> None:
if output is None:
sys.stdout.write(content)
return
output.parent.mkdir(parents=True, exist_ok=True)
descriptor, temporary = tempfile.mkstemp(prefix=f".{output.name}.", dir=output.parent)
try:
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
handle.write(content)
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary, output)
except BaseException:
try:
os.unlink(temporary)
except FileNotFoundError:
pass
raise
print(f"WROTE {output}", file=sys.stderr)
def run_auditor(command: str, arguments: list[str]) -> None:
args = parse_audit_args(command, arguments)
target = args.target.expanduser().resolve()
if not target.is_dir():
raise KitError(f"target is not a directory: {target}")
report = audit_repository(target)
if command == "fix":
patch = combined_patch(report, args.safe_only)
emit_output(patch, args.output)
return
config_path = args.config.expanduser() if args.config else target / ".maintainer-defense.json"
if args.config or config_path.is_file():
entries = load_suppressions(config_path, date.today())
report, suppressed, warnings = apply_suppressions(report, entries)
for warning in warnings:
print(f"WARNING: {warning}", file=sys.stderr)
if suppressed:
noun = "finding" if suppressed == 1 else "findings"
print(f"Suppressed {suppressed} {noun} via {config_path}", file=sys.stderr)
if args.new_only:
if args.baseline:
fingerprints = load_baseline(args.baseline)
else:
baseline_report = audit_git_ref(target, args.compare_ref)
fingerprints = {item["fingerprint"] for item in baseline_report["findings"]}
report = filter_new(report, fingerprints)
if args.format == "human":
content = render_human(report)
elif args.format == "summary":
content = render_summary(report)
elif args.format == "json":
content = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
else:
content = json.dumps(render_sarif(report), ensure_ascii=False, indent=2) + "\n"
emit_output(content, args.output)
if args.fail_on:
threshold = SEVERITY_ORDER[args.fail_on]
if any(SEVERITY_ORDER[item["severity"]] >= threshold for item in report["findings"]):
raise SystemExit(2)
def main() -> None:
if len(sys.argv) > 1 and sys.argv[1] == "--version":
print(f"maintainer-defense auditor {AUDITOR_VERSION}; kit {KIT_VERSION}")
return
if len(sys.argv) == 1:
print(
"usage: maintainer-defense {audit,fix,install} ...\n"
" maintainer-defense --target REPOSITORY [legacy installer options]\n\n"
"commands:\n"
" audit inspect local governance, workflows, and moderation automation\n"
" fix emit a reviewable unified diff; never modify the target\n"
" install preview or install a defense-kit profile\n\n"
"Run a command with --help for details."
)
return
if len(sys.argv) > 1 and sys.argv[1] in {"audit", "fix"}:
try:
run_auditor(sys.argv[1], sys.argv[2:])
except (KitError, OSError) as exc:
print(f"ERROR: {exc}", file=sys.stderr)
raise SystemExit(1) from exc
return
install_arguments = sys.argv[2:] if len(sys.argv) > 1 and sys.argv[1] == "install" else None
args = parse_install_args(install_arguments)
target = args.target.expanduser().resolve()
try:
if not target.is_dir():
raise KitError(f"target is not a directory: {target}")
if args.verify:
verify(target)
return
if args.uninstall:
uninstall(target)
return
repository = args.repo or detect_repository(target) or "OWNER/REPOSITORY"
if not valid_repository(repository):
raise KitError(f"invalid GitHub repository: {repository}")
if args.apply and repository == "OWNER/REPOSITORY":
raise KitError("--repo OWNER/REPOSITORY is required when no GitHub origin is detectable")
install(target, args.profile, args.language, repository, args.apply)
except (KitError, OSError) as exc:
print(f"ERROR: {exc}", file=sys.stderr)
raise SystemExit(1) from exc
if __name__ == "__main__":
main()
SHA-256: e8f82d980c47abd082f3e7746a99cdde452aa79d8a23295423402b5de3fe35bd