Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ sequenceDiagram

`scripts/ci/` ships with 100% statement/branch coverage and 100% docstrings.
CI installs Python tools only with `pip install --require-hashes`. Contract
tests pin workflow structure and governance prose so drift fails closed.
tests pin workflow structure and governance prose so drift fails closed. The
trusted `uv` exporter is downloaded from the literal GitHub Releases URL for
`uv` 0.12.1; `releases.astral.sh` is not the network sink.

## Related durable documents

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109).
- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context.
- Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367).
- Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics.
Expand Down
40 changes: 32 additions & 8 deletions docs/doctoring/trusted-uv-lock-materialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ The implementation therefore:
absence;
3. installs one process-wide urllib opener with an empty proxy map and a redirect
handler that rejects every redirect before urllib creates a target request;
4. downloads one fixed official Astral `uv` archive from a literal HTTPS URL and
accepts a response only when its parsed origin remains HTTPS,
`releases.astral.sh`, and the absent or explicit default port 443; malformed
or nondefault ports fail closed;
4. downloads one fixed official `uv` archive from the literal GitHub Releases
HTTPS URL and accepts a response only when its parsed origin remains HTTPS on
`github.com`, `release-assets.githubusercontent.com`, or
`objects.githubusercontent.com` with the absent or explicit default port 443;
malformed or nondefault ports, userinfo, and any other host fail closed. The
opener may follow exactly one hop from `github.com` onto those two GitHub
release-asset hosts. `releases.astral.sh` is no longer the network sink
because that vanity host now returns HTTP 403 for the pinned 0.12.1 archive
(ContextualWisdomLab/.github#1109) while the GitHub Releases asset keeps the
same SHA-256 digest;
5. verifies the bounded archive with a pinned SHA-256 digest before extraction;
6. accepts only the expected regular-file tar member within explicit size bounds;
7. writes the executable with mode `0755` and verifies that it reports the exact
Expand Down Expand Up @@ -104,10 +110,12 @@ Regression coverage must prove:
- base-revision-only reads and rejection of unsafe revision/path shapes;
- an absent sibling project is skipped, but an inventoried project blob that
cannot be read propagates a fatal error before uv starts;
- the download opener is cached, disables ambient proxies, and rejects redirects
before following them;
- fixed HTTPS scheme and hostname validation, acceptance only of an absent or
explicit port 443, rejection of malformed and nondefault ports, bounded reads,
- the download opener is cached, disables ambient proxies, and follows only one
`github.com` → GitHub release-asset CDN hop before rejecting every other
redirect;
- fixed HTTPS scheme and hostname validation for GitHub Releases plus the two
official asset hosts, acceptance only of an absent or explicit port 443,
rejection of userinfo, malformed ports, and nondefault ports, bounded reads,
archive digest, member type, member size, executable size, executable mode,
and exact version;
- frozen, offline, cacheless, noninteractive exporter arguments;
Expand Down Expand Up @@ -171,6 +179,9 @@ accepted by the coverage sandbox.

## References

Astral Software, Inc. (n.d.). *Installation*. uv documentation. Retrieved
August 18, 2026, from https://docs.astral.sh/uv/getting-started/installation/

Astral Software, Inc. (n.d.). *Exporting a lockfile*. uv documentation. Retrieved
August 4, 2026, from https://docs.astral.sh/uv/concepts/projects/export/

Expand All @@ -184,9 +195,16 @@ Berners-Lee, T., Fielding, R., & Masinter, L. (2005). *Uniform Resource Identifi
(URI): Generic syntax* (STD 66; RFC 3986). Internet Engineering Task Force.
https://doi.org/10.17487/RFC3986

Fielding, R. (Ed.), Nottingham, M. (Ed.), & Reschke, J. (Ed.). (2022). *HTTP
semantics* (RFC 9110). Internet Engineering Task Force.
https://doi.org/10.17487/RFC9110

GitHub. (n.d.). *actions/checkout*. GitHub. Retrieved August 5, 2026, from
https://github.com/actions/checkout

GitHub, Inc. (n.d.). *About releases*. GitHub Docs. Retrieved August 18, 2026,
from https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases

GitHub, Inc. (n.d.). *Events that trigger workflows*. GitHub Docs. Retrieved
August 5, 2026, from
https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows
Expand All @@ -197,6 +215,12 @@ Supply-chain Levels for Software Artifacts. (2025). *SLSA specification
Supply-chain Levels for Software Artifacts. (2025). *Provenance (version 1.2)*.
https://slsa.dev/spec/v1.2/provenance

MITRE. (2026a). *CWE-601: URL redirection to untrusted site ('open redirect')*.
https://cwe.mitre.org/data/definitions/601.html

MITRE. (2026b). *CWE-918: Server-side request forgery (SSRF)*.
https://cwe.mitre.org/data/definitions/918.html

Supply-chain Levels for Software Artifacts. (2025). *Source: Requirements for
producing source (version 1.2)*.
https://slsa.dev/spec/v1.2/source-requirements
101 changes: 74 additions & 27 deletions scripts/ci/materialize_base_python_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,17 @@
UV_EXPORT_TIMEOUT_SECONDS = 120
TRUSTED_UV_VERSION = "0.12.1"
TRUSTED_UV_ARCHIVE_URL = (
"https://releases.astral.sh/github/uv/releases/download/0.12.1/"
"https://github.com/astral-sh/uv/releases/download/0.12.1/"
"uv-x86_64-unknown-linux-gnu.tar.gz"
)
TRUSTED_UV_RELEASE_HOST = "github.com"
TRUSTED_UV_ASSET_HOSTS = frozenset(
{
"release-assets.githubusercontent.com",
"objects.githubusercontent.com",
}
)
TRUSTED_UV_FINAL_HOSTS = frozenset({TRUSTED_UV_RELEASE_HOST, *TRUSTED_UV_ASSET_HOSTS})
TRUSTED_UV_ARCHIVE_SHA256 = (
"90b2f223fb69d19db49e117da601f64978593417988530aa733d456141b4bcbb"
)
Expand All @@ -50,10 +58,51 @@
TRUSTED_UV_DOWNLOAD_MAX_BYTES = 64 * 1024 * 1024
TRUSTED_UV_BINARY_MAX_BYTES = 64 * 1024 * 1024
TRUSTED_UV_VERSION_TIMEOUT_SECONDS = 10
TRUSTED_UV_ORIGIN_ERROR = (
"trusted uv archive redirected outside the fixed GitHub release HTTPS origin"
)


def _https_default_port(parsed: urllib.parse.ParseResult) -> bool:
"""Return whether one parsed URL uses the implicit or explicit HTTPS port."""
try:
return parsed.port in (None, 443)
except ValueError:
return False


def _is_trusted_uv_https_host(
url: str,
allowed_hosts: frozenset[str],
) -> bool:
"""Return whether ``url`` is HTTPS, default-port, and host-allowlisted."""
parsed = urllib.parse.urlparse(url)
return (
parsed.scheme == "https"
and parsed.hostname in allowed_hosts
and parsed.username is None
and parsed.password is None
and _https_default_port(parsed)
)


class _RejectTrustedUvRedirects(urllib.request.HTTPRedirectHandler):
"""Reject every redirect before urllib issues a request to its target."""
def _is_trusted_uv_release_request(url: str) -> bool:
"""Return whether the current request is still the GitHub Releases origin."""
return _is_trusted_uv_https_host(url, frozenset({TRUSTED_UV_RELEASE_HOST}))


def _is_trusted_uv_asset_location(url: str) -> bool:
"""Return whether the next hop is an official GitHub release-asset host."""
return _is_trusted_uv_https_host(url, TRUSTED_UV_ASSET_HOSTS)


def _is_trusted_uv_final_origin(url: str) -> bool:
"""Return whether the completed response stayed on a trusted HTTPS origin."""
return _is_trusted_uv_https_host(url, TRUSTED_UV_FINAL_HOSTS)


class _TrustedUvReleaseAssetRedirects(urllib.request.HTTPRedirectHandler):
"""Follow one GitHub Releases hop onto the official asset CDN only."""

def redirect_request(
self,
Expand All @@ -63,18 +112,31 @@ def redirect_request(
message: str,
headers: Any,
new_url: str,
) -> None:
"""Fail closed for all redirect status codes and target locations."""
del request, response, code, message, headers, new_url
raise RuntimeError("trusted uv archive redirects are forbidden")
) -> urllib.request.Request:
"""Allow github.com → GitHub asset CDN and reject every other hop."""
if not _is_trusted_uv_release_request(request.full_url) or not (
_is_trusted_uv_asset_location(new_url)
):
raise RuntimeError(TRUSTED_UV_ORIGIN_ERROR)
followed = super().redirect_request(
request,
response,
code,
message,
headers,
new_url,
)
if followed is None:
raise RuntimeError(TRUSTED_UV_ORIGIN_ERROR)
return followed


@functools.cache
def _install_trusted_uv_url_opener() -> None:
"""Install one process-wide no-proxy, no-redirect opener for the fixed URL."""
"""Install one process-wide no-proxy opener for the fixed GitHub URL."""
opener = urllib.request.build_opener(
urllib.request.ProxyHandler({}),
_RejectTrustedUvRedirects(),
_TrustedUvReleaseAssetRedirects(),
)
urllib.request.install_opener(opener)

Expand Down Expand Up @@ -226,27 +288,12 @@ def _download_trusted_uv_archive() -> bytes:
# prove that neither user data nor repository content selects a scheme,
# host, path, query, fragment, method, or request header.
with urllib.request.urlopen( # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected # nosec B310
"https://releases.astral.sh/github/uv/releases/download/0.12.1/"
"https://github.com/astral-sh/uv/releases/download/0.12.1/"
"uv-x86_64-unknown-linux-gnu.tar.gz",
timeout=TRUSTED_UV_DOWNLOAD_TIMEOUT_SECONDS,
) as response:
final_url = urllib.parse.urlparse(response.geturl())
try:
final_port = final_url.port
except ValueError as exc:
raise RuntimeError(
"trusted uv archive redirected outside the fixed "
"releases.astral.sh HTTPS origin"
) from exc
if (
(final_url.scheme, final_url.hostname)
!= ("https", "releases.astral.sh")
or final_port not in (None, 443)
):
raise RuntimeError(
"trusted uv archive redirected outside the fixed "
"releases.astral.sh HTTPS origin"
)
if not _is_trusted_uv_final_origin(response.geturl()):
raise RuntimeError(TRUSTED_UV_ORIGIN_ERROR)
payload = bytearray()
while len(payload) <= TRUSTED_UV_DOWNLOAD_MAX_BYTES:
chunk = response.read(
Expand Down
42 changes: 40 additions & 2 deletions tests/test_materialize_base_python_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,19 +525,57 @@ def _trusted_uv_archive(
def test_download_trusted_uv_archive_accepts_fixed_https_origin(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The downloader returns bounded bytes from the fixed Astral HTTPS origin."""
"""The downloader returns bounded bytes from the fixed GitHub HTTPS origin."""
payload = b"archive"
response = FakeHttpResponse(materializer.TRUSTED_UV_ARCHIVE_URL, payload)
monkeypatch.setattr(materializer.urllib.request, "urlopen", lambda *_a, **_k: response)

assert materializer._download_trusted_uv_archive() == payload


def test_download_trusted_uv_archive_accepts_github_release_asset_origin(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The official GitHub release-asset CDN remains a valid final HTTPS origin."""
payload = b"archive"
response = FakeHttpResponse(
"https://release-assets.githubusercontent.com/"
"github-production-release-asset/699532645/archive",
payload,
)
monkeypatch.setattr(materializer.urllib.request, "urlopen", lambda *_a, **_k: response)

assert materializer._download_trusted_uv_archive() == payload


def test_download_trusted_uv_archive_accepts_legacy_objects_asset_origin(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The previous GitHub release-asset hostname remains a valid final origin."""
payload = b"archive"
response = FakeHttpResponse(
"https://objects.githubusercontent.com/github-production-release-asset/1/file",
payload,
)
monkeypatch.setattr(materializer.urllib.request, "urlopen", lambda *_a, **_k: response)

assert materializer._download_trusted_uv_archive() == payload


@pytest.mark.parametrize(
"unsafe_url",
[
"https://example.invalid/uv.tar.gz",
"https://[email protected]/astral-sh/uv/releases/download/0.12.1/uv.tar.gz",
"https://:[email protected]/astral-sh/uv/releases/download/0.12.1/uv.tar.gz",
],
)
def test_download_trusted_uv_archive_rejects_unsafe_redirect(
monkeypatch: pytest.MonkeyPatch,
unsafe_url: str,
) -> None:
"""A redirect away from the fixed HTTPS release host fails closed."""
response = FakeHttpResponse("https://example.invalid/uv.tar.gz")
response = FakeHttpResponse(unsafe_url)
monkeypatch.setattr(materializer.urllib.request, "urlopen", lambda *_a, **_k: response)

with pytest.raises(RuntimeError, match="redirected outside"):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_trusted_uv_download_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
_REPO_ROOT = Path(__file__).resolve().parents[1]
_MATERIALIZER = _REPO_ROOT / "scripts" / "ci" / "materialize_base_python_requirements.py"
_EXPECTED_URL = (
"https://releases.astral.sh/github/uv/releases/download/0.12.1/"
"https://github.com/astral-sh/uv/releases/download/0.12.1/"
"uv-x86_64-unknown-linux-gnu.tar.gz"
)
_SEMGREP_DYNAMIC_URL_RULE = (
Expand Down
22 changes: 15 additions & 7 deletions tests/test_uv_redirect_and_coverage_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@
@pytest.mark.parametrize(
"unsafe_url",
[
"https://releases.astral.sh:444/github/uv/releases/download/0.12.1/uv.tar.gz",
"https://releases.astral.sh:not-a-port/github/uv/releases/download/0.12.1/uv.tar.gz",
"https://github.com:444/astral-sh/uv/releases/download/0.12.1/uv.tar.gz",
"https://github.com:not-a-port/astral-sh/uv/releases/download/0.12.1/uv.tar.gz",
"https://release-assets.githubusercontent.com:444/github-production-release-asset/1/file",
],
)
def test_trusted_uv_download_rejects_nondefault_or_malformed_ports(
monkeypatch: pytest.MonkeyPatch,
unsafe_url: str,
) -> None:
"""The pinned Astral host cannot redirect to another or malformed service port."""
"""The pinned GitHub release origin cannot land on another or malformed port."""

response = FakeHttpResponse(unsafe_url)
monkeypatch.setattr(
Expand All @@ -38,14 +39,21 @@ def test_trusted_uv_download_rejects_nondefault_or_malformed_ports(
materializer._download_trusted_uv_archive()


@pytest.mark.parametrize(
"trusted_url",
[
"https://github.com:443/astral-sh/uv/releases/download/0.12.1/uv-x86_64-unknown-linux-gnu.tar.gz",
"https://release-assets.githubusercontent.com:443/github-production-release-asset/1/file",
"https://objects.githubusercontent.com:443/github-production-release-asset/1/file",
],
)
def test_trusted_uv_download_accepts_explicit_default_https_port(
monkeypatch: pytest.MonkeyPatch,
trusted_url: str,
) -> None:
"""An explicit port 443 still denotes the fixed trusted HTTPS origin."""
"""An explicit port 443 still denotes a fixed trusted HTTPS origin."""

response = FakeHttpResponse(
"https://releases.astral.sh:443/github/uv/releases/download/0.12.1/uv.tar.gz"
)
response = FakeHttpResponse(trusted_url)
monkeypatch.setattr(
materializer.urllib.request,
"urlopen",
Expand Down
Loading
Loading