Skip to content

Commit 5105dd0

Browse files
security(supply-chain): verify access-module integrity via pinned commit SHA (F-019)
External access modules are cloned at runtime (and in CI) and imported directly, so a compromised upstream repo = RCE (CTO-4866, CVSS 8.1). clone_repo now treats a 40-char SHA after '#' as an integrity pin: after cloning it checks out that commit and asserts repo.head.commit.hexsha matches, aborting on mismatch. Unpinned modules log a warning to prompt migrating config.json git_urls to '<url>#<sha>'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1c78618 commit 5105dd0

1 file changed

Lines changed: 30 additions & 4 deletions

File tree

scripts/clone_access_modules.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import logging
44
import os
5+
import re
56
import shutil
67
import sys
78
import time
@@ -92,20 +93,25 @@ def safe_access_module_path(name):
9293

9394
def clone_repo(formatted_git_arg, retry_limit):
9495
""" Clone a single repo """
95-
url, target_branch = get_repo_url_and_branch(formatted_git_arg)
96+
url, target_ref = get_repo_url_and_branch(formatted_git_arg)
9697

9798
folder_name = url.split("/").pop()[:-4]
9899
# F-023: folder_name comes from the (untrusted) git URL — sanitize before use
99100
target_folder_path = safe_access_module_path(folder_name)
100101

102+
# F-019: if the ref after '#' is a full 40-char commit SHA, treat it as an
103+
# integrity pin (verified below) rather than a branch to clone.
104+
pinned_sha = target_ref if (target_ref and re.fullmatch(r"[0-9a-fA-F]{40}", target_ref)) else None
105+
101106
retry_exception = None
107+
repo = None
102108
for clone_attempt in range(1, retry_limit + 1):
103109
try:
104110
logger.info("Cloning Repo")
105-
if target_branch:
106-
Repo.clone_from(url, target_folder_path, branch=target_branch)
111+
if target_ref and not pinned_sha:
112+
repo = Repo.clone_from(url, target_folder_path, branch=target_ref)
107113
else:
108-
Repo.clone_from(url, target_folder_path)
114+
repo = Repo.clone_from(url, target_folder_path)
109115
except (GitCommandError, Exception) as exception:
110116
sleep_time = 10 * clone_attempt
111117
logger.error(
@@ -128,6 +134,26 @@ def clone_repo(formatted_git_arg, retry_limit):
128134
logger.exception("Max retry count reached while cloning repo")
129135
raise retry_exception
130136

137+
# F-019: integrity verification. External modules are cloned at runtime and
138+
# imported directly, so a compromised upstream = RCE. If a commit SHA was
139+
# pinned, check it out and assert HEAD matches; abort on mismatch. Otherwise
140+
# warn that the module could not be integrity-verified.
141+
if pinned_sha:
142+
repo.git.checkout(pinned_sha)
143+
actual_sha = repo.head.commit.hexsha
144+
if actual_sha.lower() != pinned_sha.lower():
145+
raise Exception(
146+
f"Integrity check failed for {url}: expected commit "
147+
f"{pinned_sha}, got {actual_sha}"
148+
)
149+
logger.info("Verified access module %s at pinned commit %s", url, pinned_sha)
150+
else:
151+
logger.warning(
152+
"Access module %s is not pinned to a commit SHA; runtime integrity "
153+
"cannot be verified (F-019). Pin via '<git-url>#<40-char-commit-sha>'.",
154+
url,
155+
)
156+
131157
return target_folder_path
132158

133159

0 commit comments

Comments
 (0)