Pin the website publication toolchain
Some checks failed
Dependency Audit / dependency-audit (push) Failing after 15s
Security Audit / security-audit (push) Failing after 13s

This commit is contained in:
2026-07-22 22:20:09 +02:00
parent dc46bda224
commit b6e9a9bd81
2 changed files with 106 additions and 4 deletions

View File

@@ -11,6 +11,7 @@ from pathlib import Path
import re
import secrets
import shlex
import shutil
import stat
import subprocess
import sys
@@ -107,6 +108,12 @@ def publish_catalog_candidate(
steps: list[CatalogPublishStep] = []
notes: list[str] = []
blockers: list[str] = []
resolved_npm = npm
if build_web:
try:
resolved_npm = _trusted_npm_command(npm)
except (OSError, RuntimeError) as exc:
blockers.append(f"website build command is not trusted: {exc}")
if not candidate_catalog.exists():
blockers.append(f"candidate catalog is missing: {candidate_catalog}")
@@ -399,9 +406,18 @@ def publish_catalog_candidate(
CatalogPublishStep(
id="build-web",
title="Build website",
detail="Run the website build after copying catalog files.",
detail=(
"Run the website build after copying catalog files with "
f"{resolved_npm}."
),
command=shlex.join(
[npm, "--prefix", str(resolved_web_root), "run", "build"]
[
resolved_npm,
"--prefix",
str(resolved_web_root),
"run",
"build",
]
),
mutating=True,
status="planned" if not apply else "blocked" if blockers else "pending",
@@ -559,7 +575,7 @@ def publish_catalog_candidate(
]
if build_web:
run_checked(
[npm, "--prefix", str(resolved_web_root), "run", "build"],
[resolved_npm, "--prefix", str(resolved_web_root), "run", "build"],
cwd=resolved_web_root,
)
completed_steps = [
@@ -2009,10 +2025,14 @@ def git_success(path: Path, *args: str) -> bool:
def run_checked(args: list[str], *, cwd: Path) -> None:
if not args or not Path(args[0]).is_absolute():
raise RuntimeError("publication build command must be an absolute path")
environment = _sanitized_git_environment()
environment["PATH"] = f"{Path(args[0]).parent}:/usr/bin:/bin"
result = subprocess.run(
args,
cwd=cwd,
env=_sanitized_git_environment(),
env=environment,
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -2030,3 +2050,34 @@ def run_checked(args: list[str], *, cwd: Path) -> None:
output=result.stdout,
stderr=result.stderr,
)
def _trusted_npm_command(value: str) -> str:
candidate = Path(value)
if not candidate.is_absolute():
if candidate.name != value:
raise RuntimeError(
"npm executable must be an absolute path or the bare name npm"
)
located = shutil.which(value, path="/usr/bin:/bin")
if not located:
raise RuntimeError("npm executable is unavailable on the fixed system path")
candidate = Path(located)
candidate = Path(os.path.abspath(candidate))
permitted_owners = {0, os.geteuid()}
issue = _trusted_runtime_executable_issue(
candidate,
permitted_owners=permitted_owners,
label="npm executable",
)
if issue:
raise RuntimeError(issue)
node = candidate.parent / "node"
issue = _trusted_runtime_executable_issue(
node,
permitted_owners=permitted_owners,
label="Node.js executable",
)
if issue:
raise RuntimeError(issue)
return str(candidate)