From fa3840cf6db9fa3adf9363967b2a9d6a8df5890b Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Fri, 8 May 2026 02:43:47 +0000 Subject: [PATCH] scripts: harden github_blob_to_raw against substring URL spoofing CodeQL flagged scripts/notebook_to_python.py:33's `if "github.com" in url and "/blob/" in url` as py/incomplete-url-substring-sanitization: "github.com" can sit anywhere in the URL, so an attacker-controlled URL like https://attacker.example.com/github.com/blob/x would be rewritten to a raw.githubusercontent.com URL and fetched as if it were a real GitHub blob. Switch to urllib.parse.urlparse and require parsed.netloc == "github.com" exactly, then rewrite via a proper urlunparse on the parsed components (path is replaced with first /blob/ -> / only). Query strings and fragments now round-trip correctly too, which was an incidental bug in the old string-replace path. Closes the high-severity CodeQL alert on PR head 08235625. --- scripts/notebook_to_python.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/notebook_to_python.py b/scripts/notebook_to_python.py index 7bfd54a99b..86f3239b72 100644 --- a/scripts/notebook_to_python.py +++ b/scripts/notebook_to_python.py @@ -29,11 +29,19 @@ def needs_fstring(cmd: str) -> bool: def github_blob_to_raw(url: str) -> str: """Convert GitHub blob URL to raw URL.""" - # https://github.com/user/repo/blob/branch/path -> https://raw.githubusercontent.com/user/repo/branch/path - if "github.com" in url and "/blob/" in url: - url = url.replace("github.com", "raw.githubusercontent.com") - url = url.replace("/blob/", "/") - return url + # https://github.com/user/repo/blob/branch/path + # -> https://raw.githubusercontent.com/user/repo/branch/path + # Compare the parsed host exactly (not as a substring) so a URL + # like https://attacker.example.com/github.com/blob/... does NOT + # get rewritten to a github raw URL. Closes CodeQL alert + # py/incomplete-url-substring-sanitization. + parsed = urllib.parse.urlparse(url) + if parsed.netloc != "github.com" or "/blob/" not in parsed.path: + return url + new_path = parsed.path.replace("/blob/", "/", 1) + return urllib.parse.urlunparse( + parsed._replace(netloc = "raw.githubusercontent.com", path = new_path) + ) def download_notebook(url: str) -> tuple[str, str]: