Skip to content
38 changes: 36 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ jobs:
- name: Lint YAML
if: always()
run: |
pip install --quiet --require-hashes --only-binary :all: -r scripts/ci-yamllint-requirements.txt
# Bounded retries + a per-attempt timeout so a transient PyPI/index
# blip retries instead of reding the Lint job (guards the fleet
# failure-rate metric — see test/workflows/ci/install-resilience.bats,
# issue #935).
pip install --quiet --retries 3 --timeout 30 --require-hashes --only-binary :all: -r scripts/ci-yamllint-requirements.txt
yamllint -c <(cat <<'YAMLLINTRC'
extends: default
rules:
Expand Down Expand Up @@ -107,6 +111,17 @@ jobs:
node-version: '20'

- name: Run AgentShield
# npm's default of 2 fetch retries is thin for the npx package download.
# Bump it via npm's native fetch-retries config so a transient npm-registry
# blip retries instead of reding the job (guards the fleet failure-rate
# metric — see test/workflows/ci/install-resilience.bats, issue #935).
# An env var (not a shell loop) so a genuine high-severity finding still
# fails on the first run instead of being retried.
env:
npm_config_fetch_retries: '3'
npm_config_fetch_retry_mintimeout: '2000'
npm_config_fetch_retry_maxtimeout: '30000'
Comment thread
don-petry marked this conversation as resolved.
Resolved
npm_config_fetch_timeout: '60000'
run: |
npx --ignore-scripts ecc-agentshield@1.4.0 scan --path . --format json --min-severity high

Expand All @@ -125,7 +140,26 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release download v8.30.1 --repo gitleaks/gitleaks --pattern 'gitleaks_8.30.1_linux_x64.tar.gz' --output /tmp/gitleaks.tar.gz
set -euo pipefail
# `gh release download` pulls from GitHub Releases (redirects to
# objects.githubusercontent.com) and has no native retry — the same
# transient-blip failure mode #894 fixed for the actionlint curl. Wrap
# in a bounded retry loop with backoff so a transient error retries
# instead of reding the Secret scan job (guards the fleet failure-rate
# metric — see test/workflows/ci/install-resilience.bats, issue #935).
downloaded=0
for attempt in 1 2 3; do
if timeout 30 gh release download v8.30.1 --repo gitleaks/gitleaks --pattern 'gitleaks_8.30.1_linux_x64.tar.gz' --output /tmp/gitleaks.tar.gz --clobber; then
downloaded=1
break
fi
echo "gitleaks download attempt ${attempt} failed" >&2
[ "${attempt}" -lt 3 ] && sleep $((attempt * 5))
done
if [ "${downloaded}" -ne 1 ]; then
echo "gitleaks download failed after 3 attempts" >&2
exit 1
fi
echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz" | sha256sum -c
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
sudo mv /tmp/gitleaks /usr/local/bin/gitleaks
Expand Down
66 changes: 66 additions & 0 deletions test/workflows/ci/install-resilience.bats
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,33 @@

load 'helpers/setup'

# Emit the `run:` step block whose text contains the given marker substring.
# Steps in ci.yml start at a 6-space "- " (the `steps:` child indent); every
# deeper-indented line belongs to that step. Isolating a step lets each test
# assert resilience on the one fetch it cares about without matching flags that
# happen to appear in a neighbouring step.
step_block_with() {
local marker="$1"
local block="" out="" line=""
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line%$'\r'}"
if [[ "$line" == " - "* ]]; then
if [[ "$block" == " - "* ]]; then
[[ "$block" == *"$marker"* ]] && out="$block"
fi
block="$line"$'\n'
else
if [[ -n "$block" ]]; then
block+="$line"$'\n'
fi
fi
done < "$TT_WORKFLOW"
if [[ "$block" == " - "* ]]; then
[[ "$block" == *"$marker"* ]] && out="$block"
fi
printf '%s' "$out"
}
Comment thread
don-petry marked this conversation as resolved.
Resolved

@test "install: the ci workflow exists" {
[ -f "$TT_WORKFLOW" ]
}
Expand Down Expand Up @@ -50,3 +77,42 @@ load 'helpers/setup'
[[ "$cmd" == *"--retry-all-errors"* ]]
done
}

@test "install: the yamllint pip install carries a bounded retry budget" {
# pip fetches wheels from PyPI/the configured index; a transient index blip
# would otherwise red the Lint job. An explicit --retries pins the budget so a
# future edit can't drop resilience and reintroduce a bare install.
local block
block="$(step_block_with 'pip install')"
[ -n "$block" ]
local pip_cmd
pip_cmd="$(printf '%s' "$block" | grep -E '^[[:space:]]+pip install' | head -1)"
[ -n "$pip_cmd" ]
[[ "$pip_cmd" =~ --retries[[:space:]=][1-9] ]]
}

@test "install: the AgentShield npx fetch retries transient registry errors" {
# `npx ecc-agentshield` downloads the package from the npm registry before
# scanning; npm's default of 2 fetch retries is thin. Bump it via npm's native
# fetch-retries config (env var, NOT a shell loop) so a transient registry
# error retries while a genuine high-severity finding still fails on the first
# run instead of being retried three times.
local block
block="$(step_block_with 'ecc-agentshield')"
[ -n "$block" ]
[[ "$block" =~ npm_config_fetch_retries:[[:space:]]*\'?[1-9] ]]
[[ "$block" =~ npm_config_fetch_timeout:[[:space:]]*\'?[1-9] ]]
}

@test "install: the gitleaks release download retries on transient failure" {
# `gh release download` pulls gitleaks from GitHub Releases (which redirects to
# objects.githubusercontent.com) and has no native retry — the exact failure
# mode #894 fixed for the actionlint curl. Wrap it in a bounded retry loop with
# backoff so a transient blip retries instead of reding the Secret scan job.
local block
block="$(step_block_with 'gh release download')"
[ -n "$block" ]
[[ "$block" == *"for attempt in"* ]]
[[ "$block" == *"sleep"* ]]
[[ "$block" == *"timeout"* ]]
}
Loading