Skip to content

Commit 1ddaf25

Browse files
committed
security: require immutable GitHub source revisions
Signed-off-by: Vitor Mattos <vitor@php.rio>
1 parent f82f686 commit 1ddaf25

1 file changed

Lines changed: 20 additions & 2 deletions

File tree

‎scripts/sync_upstream.py‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import json
1010
from dataclasses import dataclass
1111
from pathlib import Path
12+
from urllib.parse import urlparse
1213
from urllib.request import Request, urlopen
1314

1415

@@ -43,8 +44,7 @@ def load_sources(manifest_path: Path) -> list[Source]:
4344

4445
if len(digest) != 64 or any(char not in "0123456789abcdef" for char in digest):
4546
raise ValueError(f"sources[{index}].sha256 must be 64 lowercase hex characters")
46-
if "/refs/heads/" in url or url.endswith(("/main", "/master")):
47-
raise ValueError(f"sources[{index}].url must be pinned to an immutable commit")
47+
_validate_immutable_url(url, f"sources[{index}].url")
4848

4949
sources.append(
5050
Source(
@@ -91,6 +91,24 @@ def check(sources: list[Source], root: Path) -> None:
9191
raise ValueError("generated templates are out of date: " + ", ".join(drift))
9292

9393

94+
def _validate_immutable_url(url: str, path: str) -> None:
95+
parsed = urlparse(url)
96+
if parsed.scheme != "https":
97+
raise ValueError(f"{path} must use https")
98+
99+
if parsed.hostname == "raw.githubusercontent.com":
100+
parts = [part for part in parsed.path.split("/") if part]
101+
if len(parts) < 4:
102+
raise ValueError(f"{path} is not a valid raw GitHub file URL")
103+
revision = parts[2]
104+
if len(revision) != 40 or any(
105+
char not in "0123456789abcdefABCDEF" for char in revision
106+
):
107+
raise ValueError(f"{path} must pin a 40-character Git commit SHA")
108+
elif "/refs/heads/" in parsed.path:
109+
raise ValueError(f"{path} must not reference a mutable branch")
110+
111+
94112
def _safe_destination(root: Path, destination: Path) -> Path:
95113
if destination.is_absolute() or ".." in destination.parts:
96114
raise ValueError(f"unsafe destination: {destination}")

0 commit comments

Comments
 (0)