diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 97d3b10..91243d8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -72,6 +72,8 @@ permissions: env: # ESP-IDF v5.5.1, pinned by digest; same image as build.yml IDF_IMAGE: espressif/idf:v5.5.1@sha256:dfa2d076c796769c07c155eba6c672b9f395aec943b2ba3701b73379b5f9e884 + # gitleaks v8.28.0 (secret-scan), pinned by digest + GITLEAKS_IMAGE: zricethezav/gitleaks:v8.28.0@sha256:cdbb7c955abce02001a9f6c9f602fb195b7fadc1e812065883f695d1eeaba854 ESP_IDF_SBOM_VERSION: 1.4.0 ACTIONLINT_VERSION: 1.7.12 ACTIONLINT_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 @@ -358,6 +360,14 @@ jobs: with: # The base commit must be present for gitleaks to diff against it. fetch-depth: 0 + - name: Pull gitleaks image (retries transient registry errors) + run: | + for attempt in 1 2 3 4; do + docker pull "$GITLEAKS_IMAGE" && exit 0 + echo "Pull failed (attempt $attempt), retrying..." + sleep $((attempt * 15)) + done + exit 1 # Range: base..head for a pull request, before..after for a push; a new branch or a # force-push has no usable "before", so only the pushed commit is scanned. Context # values arrive via env, never interpolated into the script. @@ -376,5 +386,5 @@ jobs: echo "Scanning commit range: $RANGE" docker run --rm -v "$PWD:/repo" \ -e GIT_CONFIG_COUNT=1 -e GIT_CONFIG_KEY_0=safe.directory -e GIT_CONFIG_VALUE_0='*' \ - zricethezav/gitleaks:v8.28.0@sha256:cdbb7c955abce02001a9f6c9f602fb195b7fadc1e812065883f695d1eeaba854 \ + "$GITLEAKS_IMAGE" \ git /repo --redact --no-banner --log-opts="$RANGE" diff --git a/test/guards/test_supply_chain.py b/test/guards/test_supply_chain.py index 7c55006..b7d3102 100644 --- a/test/guards/test_supply_chain.py +++ b/test/guards/test_supply_chain.py @@ -73,6 +73,19 @@ def test_image_variables_and_known_images_pinned(self): with self.subTest(workflow=name, image=ref): self.assertRegex(ref, r"@sha256:[0-9a-f]{64}$") + def test_run_images_pulled_with_retries(self): + # A registry hiccup (Docker Hub auth timeout) must not fail the gate: `docker run` + # takes its image from an *_IMAGE variable that a retry loop pulled first. + for name, text in repo.workflows().items(): + code = re.sub(r"(?m)\s#\s.*$", "", text).replace("\\\n", " ") + for cmd in re.findall(r"docker run\b[^\n]*", code): + with self.subTest(workflow=name, run=cmd[:60]): + self.assertNotRegex(cmd, r"\s[\w.-]+/[\w.-]+:[\w.-]+@sha256:", + "run images through an *_IMAGE variable") + for var in re.findall(r'"\$(\w*IMAGE\w*)"', cmd): + self.assertRegex(code, r'for attempt in [^\n]*\n\s*docker pull "\$' + var + r'" && exit 0', + f"pull ${var} with retries before running it") + def test_workflows_default_to_read_only_token(self): for name, text in repo.workflows().items(): with self.subTest(workflow=name):