diff --git a/utils/tests/verify_action_build/test_security.py b/utils/tests/verify_action_build/test_security.py index 0916653fc..e8094def1 100644 --- a/utils/tests/verify_action_build/test_security.py +++ b/utils/tests/verify_action_build/test_security.py @@ -1283,6 +1283,42 @@ def test_calculate_sha256_function_name_recognized(self): content = f"const sha = {func_name}(downloadPath)\n" assert self._has_verification(content) is True, func_name + def test_validate_checksum_function_name_recognized(self): + # astral-sh/setup-uv@v8.2.0 shape: ``src/download/download-version.ts`` + # downloads via ``tc.downloadTool`` then calls ``validateChecksum(...)``, + # whose implementation was extracted into a sibling module + # (``./checksum/checksum``). The call name is the in-file evidence the + # scanner must accept; without it, #910 false-flagged the download as + # unverified. + for func_name in ( + "validateChecksum", + "validateFileCheckSum", + "validateHash", + "validateDigest", + "validateSHA256", + ): + content = ( + "const downloadPath = await tc.downloadTool(url)\n" + f"await {func_name}(checksum, downloadPath, arch, platform, version)\n" + ) + assert self._has_verification(content) is True, func_name + + def test_validate_checksum_real_setup_uv_snippet_recognized(self): + # Faithful trim of the real download → validate sequence so the + # regression is anchored to the actual source, not just the bare name. + content = """\ +import * as tc from "@actions/tool-cache"; +import { validateChecksum } from "./checksum/checksum"; + +const downloadPath = await tc.downloadTool( + downloadUrl, + undefined, + githubToken, +); +await validateChecksum(checksum, downloadPath, arch, platform, version); +""" + assert self._has_verification(content) is True + def test_verify_hash_function_recognized(self): # Whether named ``verifyHash`` or referenced inline. for snippet in ( diff --git a/utils/verify_action_build/security.py b/utils/verify_action_build/security.py index 29a334f8a..2f07b5d6d 100644 --- a/utils/verify_action_build/security.py +++ b/utils/verify_action_build/security.py @@ -1207,6 +1207,19 @@ def analyze_action_metadata( re.compile(r"\bcalculate(?:SHA[\d]+|Checksum|Digest)\b", re.IGNORECASE), re.compile(r"\bverifyHash\b"), re.compile(r"\bcomputeChecksum\b"), + # ``validate*`` checksum helpers — the call site counts as verification + # even when the helper's implementation lives in a sibling module. + # astral-sh/setup-uv's ``src/download/download-version.ts`` downloads via + # ``tc.downloadTool`` then immediately calls ``validateChecksum(checksum, + # downloadPath, …)`` (imported from ``./checksum/checksum``), which SHA-256s + # the artifact against a provided checksum or the built-in KNOWN_CHECKSUMS + # table. v8.2.0 extracted that validation into the sibling module, moving + # the ``createHash`` token out of this file and tripping the same-file + # heuristic — the call name is the evidence that survives the refactor. + re.compile( + r"\bvalidate(?:Checksum|Hash|Digest|SHA\d*|FileChecksum)\b", + re.IGNORECASE, + ), ] _JS_SOURCE_EXTENSIONS = (".ts", ".js", ".mjs", ".cjs")