Answer architecture round 3: 0 🔴, 2 🟡; a plan-only change runs the test that pins the plan's shape, and a module record carries its own path

This commit is contained in:
2026-10-14 13:09:08 +02:00
committed by Lillem4n
parent 2d86f4f0c0
commit 9049f90cc5
3 changed files with 33 additions and 29 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
else else
base='${{ github.event.before }}' base='${{ github.event.before }}'
fi fi
if git diff --quiet "$base"...HEAD -- '*.go' go.mod go.sum Dockerfile .dockerignore .github data release-tooling testdata README.md; then if git diff --quiet "$base"...HEAD -- '*.go' go.mod go.sum Dockerfile .dockerignore .github data release-tooling testdata README.md todo.md; then
echo "code=false" >> "$GITHUB_OUTPUT" echo "code=false" >> "$GITHUB_OUTPUT"
else else
echo "code=true" >> "$GITHUB_OUTPUT" echo "code=true" >> "$GITHUB_OUTPUT"
+29 -25
View File
@@ -20,15 +20,21 @@ GOAL = "1.1"
@dataclass @dataclass
class Module: class Module:
path: str
found_in: str found_in: str
fixed: list[str] = field(default_factory=list) fixed: list[str] = field(default_factory=list)
osvs: dict[str, dict] = field(default_factory=dict) osvs: dict[str, dict] = field(default_factory=dict)
def name(self, path: str) -> str: @property
return "Go" if path == "stdlib" else f"`{path}`" def name(self) -> str:
return "Go" if self.path == "stdlib" else f"`{self.path}`"
def version(self, path: str, version: str) -> str: def version(self, version: str) -> str:
return version.removeprefix("v") if path == "stdlib" else version return version.removeprefix("v") if self.path == "stdlib" else version
@property
def carried(self) -> str:
return f"{self.name} {self.version(self.found_in)}"
def messages(report: str): def messages(report: str):
@@ -41,19 +47,20 @@ def messages(report: str):
yield message yield message
def vulnerable_modules(report: str) -> dict[str, Module]: def vulnerable_modules(report: str) -> list[Module]:
"""Each module govulncheck found a vulnerability in, by module path, stdlib being Go.""" """Each module govulncheck found a vulnerability in, Go (stdlib) first, then by path."""
osvs, modules = {}, {} osvs, modules = {}, {}
for message in messages(report): for message in messages(report):
if "osv" in message: if "osv" in message:
osvs[message["osv"]["id"]] = message["osv"] osv = message["osv"]
osvs[osv["id"]] = {"id": osv["id"], "aliases": osv.get("aliases", []), "summary": osv.get("summary", "")}
elif "finding" in message: elif "finding" in message:
finding = message["finding"] finding = message["finding"]
frame = finding["trace"][0] frame = finding["trace"][0]
module = modules.setdefault(frame["module"], Module(frame["version"])) module = modules.setdefault(frame["module"], Module(frame["module"], frame["version"]))
module.osvs[finding["osv"]] = osvs[finding["osv"]] module.osvs[finding["osv"]] = osvs[finding["osv"]]
module.fixed.append(finding.get("fixed_version", "")) module.fixed.append(finding.get("fixed_version", ""))
return modules return sorted(modules.values(), key=lambda m: (m.path != "stdlib", m.path))
def version_key(version: str): def version_key(version: str):
@@ -85,42 +92,39 @@ def score(todo: str) -> str:
return f"{evaluate(ast.parse(formula, mode='eval').body, values):.1f}" return f"{evaluate(ast.parse(formula, mode='eval').body, values):.1f}"
def tagline(path: str, module: Module) -> str: def tagline(module: Module) -> str:
carried = f"{module.name(path)} {module.version(path, module.found_in)}"
if "" in module.fixed: if "" in module.fixed:
return f"Ship no release while fejkdata carries {carried}, some of whose known vulnerabilities no version fixes yet." return f"Ship no release while fejkdata carries {module.carried}, some of whose known vulnerabilities no version fixes yet."
fixed = module.version(path, max(module.fixed, key=version_key)) fixed = module.version(max(module.fixed, key=version_key))
if path == "stdlib": if module.path == "stdlib":
return f"Raise Go to {fixed}, so the release binaries carry none of {carried}'s known vulnerabilities." return f"Raise Go to {fixed}, so the release binaries carry none of {module.carried}'s known vulnerabilities."
return f"Raise {module.name(path)} to {fixed}, so fejkdata requires no version of it with a known vulnerability." return f"Raise {module.name} to {fixed}, so fejkdata requires no version of it with a known vulnerability."
def detail(path: str, module: Module) -> str: def detail(module: Module) -> str:
lines = [] lines = []
for osv in sorted(module.osvs.values(), key=lambda o: o["id"]): for osv in sorted(module.osvs.values(), key=lambda o: o["id"]):
aliases = f" ({', '.join(osv['aliases'])})" if osv["aliases"] else "" aliases = f" ({', '.join(osv['aliases'])})" if osv["aliases"] else ""
lines.append(f"- {osv['id']}{aliases}: {osv['summary']}") lines.append(f"- {osv['id']}{aliases}: {osv['summary']}")
carried = f"{module.name(path)} {module.version(path, module.found_in)}" return f"govulncheck found these in {module.carried}:\n\n" + "\n".join(lines) + "\n"
return f"govulncheck found these in {carried}:\n\n" + "\n".join(lines) + "\n"
def file_items(modules: dict[str, Module], todo: str) -> tuple[str, list[int]]: def file_items(modules: list[Module], todo: str) -> tuple[str, list[int]]:
"""Return todo.md with an item for each module whose vulnerabilities it does not yet name, and the items' IDs.""" """Return todo.md with an item for each module whose vulnerabilities it does not yet name, and the items' IDs."""
filed = [] filed = []
for path in sorted(modules, key=lambda m: (m != "stdlib", m)): for module in modules:
module = modules[path]
if all(osv in todo for osv in module.osvs): if all(osv in todo for osv in module.osvs):
continue continue
first = ROW.search(todo) first = ROW.search(todo)
if first is None: if first is None:
raise ValueError("todo.md has no item to take the next release from") raise ValueError("todo.md has no item to take the next release from")
next_id = int(NEXT_ID.search(todo).group(1)) next_id = int(NEXT_ID.search(todo).group(1))
line = tagline(path, module) line = tagline(module)
scores = " | ".join(str(SCORES[k]) for k in "RSCAG") scores = " | ".join(str(SCORES[k]) for k in "RSCAG")
row = f"| {next_id} | {first.group(2)} | security | **{line}** | {scores} | {GOAL} | {score(todo)} |\n" row = f"| {next_id} | {first.group(2)} | security | **{line}** | {scores} | {GOAL} | {score(todo)} |\n"
todo = todo[: first.start()] + row + todo[first.start() :] todo = todo[: first.start()] + row + todo[first.start() :]
todo = todo.replace(f"`Next ID = {next_id}`", f"`Next ID = {next_id + 1}`", 1) todo = todo.replace(f"`Next ID = {next_id}`", f"`Next ID = {next_id + 1}`", 1)
todo = todo.replace("## Details\n\n", f"## Details\n\n### {next_id}. {line}\n\n{detail(path, module)}\n", 1) todo = todo.replace("## Details\n\n", f"## Details\n\n### {next_id}. {line}\n\n{detail(module)}\n", 1)
filed.append(next_id) filed.append(next_id)
return todo, filed return todo, filed
@@ -132,7 +136,7 @@ def main(argv: list[str]) -> int:
if not modules: if not modules:
print("No known vulnerabilities.") print("No known vulnerabilities.")
return 0 return 0
count = sum(len(m.osvs) for m in modules.values()) count = sum(len(m.osvs) for m in modules)
known = f"{count} known {'vulnerability' if count == 1 else 'vulnerabilities'}" known = f"{count} known {'vulnerability' if count == 1 else 'vulnerabilities'}"
with open(todo_path, encoding="utf-8") as f: with open(todo_path, encoding="utf-8") as f:
todo, filed = file_items(modules, f.read()) todo, filed = file_items(modules, f.read())
+3 -3
View File
@@ -36,9 +36,9 @@ Why.
""" """
OSVS = ( OSVS = (
{"id": "GO-2026-0002", "summary": "Denial of service in net/http", "aliases": ["CVE-2026-2"]}, {"id": "GO-2026-0002", "summary": "Denial of service in net/http", "aliases": ["GHSA-2222", "CVE-2026-2"]},
{"id": "GO-2026-0001", "summary": "Cross-site scripting in html/template", "aliases": ["CVE-2026-1"]}, {"id": "GO-2026-0001", "summary": "Cross-site scripting in html/template", "aliases": ["CVE-2026-1"]},
{"id": "GO-2026-0003", "summary": "Panic in x/text", "aliases": []}, {"id": "GO-2026-0003", "summary": "Panic in x/text"},
) )
STDLIB = ( STDLIB = (
("GO-2026-0002", "stdlib", "v1.27.1", "v1.27.2"), ("GO-2026-0002", "stdlib", "v1.27.1", "v1.27.2"),
@@ -83,7 +83,7 @@ class FileItems(unittest.TestCase):
f"### 7. {GO_TAGLINE}\n\n" f"### 7. {GO_TAGLINE}\n\n"
"govulncheck found these in Go 1.27.1:\n\n" "govulncheck found these in Go 1.27.1:\n\n"
"- GO-2026-0001 (CVE-2026-1): Cross-site scripting in html/template\n" "- GO-2026-0001 (CVE-2026-1): Cross-site scripting in html/template\n"
"- GO-2026-0002 (CVE-2026-2): Denial of service in net/http\n\n" "- GO-2026-0002 (GHSA-2222, CVE-2026-2): Denial of service in net/http\n\n"
"### 5. Add a thing." "### 5. Add a thing."
) )
) )