ci: gitleaks was passing without scanning anything
ci / lint (push) Successful in 18s
ci / dockerfile (push) Successful in 18s
ci / chart (push) Successful in 19s
ci / security (push) Successful in 1m7s
ci / integration (push) Successful in 48s
ci / unit (push) Successful in 37s
ci / types (push) Successful in 50s
ci / image (api) (push) Successful in 2m17s
ci / image (reconciler) (push) Successful in 2m36s
ci / image (worker) (push) Successful in 1m30s
ci / bump (push) Successful in 14s

The secret-scanning gate has been green and meaningless. `docker run -v "$PWD:/repo"`
resolves the bind source on the dind sidecar's daemon, not in the job container, so
gitleaks received an empty mount:

    ERR [git] fatal: not a git repository (or any parent up to mount point /)
    ERR failed to scan Git repository error="stderr is not empty"
    INF scan completed in 35.2ms
    INF no leaks found          <- exit 0

It logged the failure and exited 0. A scanner that reports success without looking is
worse than no scanner. The 35ms runtime was the tell.

Now a checksum-pinned binary, plus a `git rev-list --count HEAD` assertion so an
unscannable checkout fails the job instead of passing it.

Same root cause and same fix for yq in bump-digests.sh, which failed the bump job with
"stat deploy/chart/values.yaml: no such file or directory". helm was fixed this way
earlier. Nothing on this runner should mount $PWD into a container.
This commit is contained in:
Nguyen Minh Phuc
2026-07-19 09:11:22 +00:00
parent dc51bd1d9d
commit ca21b6e70d
2 changed files with 68 additions and 12 deletions
+32 -5
View File
@@ -28,6 +28,8 @@ env:
REGISTRY: gitea.oci-oci.duckdns.org REGISTRY: gitea.oci-oci.duckdns.org
IMAGE_NS: gitea_admin IMAGE_NS: gitea_admin
UV_VERSION: "0.5.11" UV_VERSION: "0.5.11"
GITLEAKS_VERSION: "8.21.2"
GITLEAKS_SHA256: "654c935542c89f565aabe7bf7c6c500830f116c114f0aeb509d2460c1ac2e6da"
jobs: jobs:
# --- stage 1: lint -- fast, fails first --------------------------------------------- # --- stage 1: lint -- fast, fails first ---------------------------------------------
@@ -155,12 +157,37 @@ jobs:
run: uv run --with 'bandit[toml]' bandit -c pyproject.toml -r libs services -ll run: uv run --with 'bandit[toml]' bandit -c pyproject.toml -r libs services -ll
- name: gitleaks (secret scan) - name: gitleaks (secret scan)
# Pinned by digest and run directly, so the command is the documented one rather # A downloaded binary, not `docker run -v "$PWD:/repo"`.
# than a marketplace action's opinion of it. #
# The container form was scanning NOTHING and passing. `-v "$PWD:/repo"` is
# interpreted by the dind sidecar's daemon, which cannot see the job container's
# checkout, so gitleaks got an empty mount and logged:
#
# ERR [git] fatal: not a git repository (or any parent up to mount point /)
# ERR failed to scan Git repository error="stderr is not empty"
# INF scan completed in 35.2ms
# INF no leaks found <- exit 0
#
# It reported success without looking, which is worse than having no scanner: the
# gate was green and meaningless. The 35ms runtime was the tell — a real history
# scan of this repo takes seconds.
#
# The `--log-opts` + commit-count assertion below is the guard against that class
# of failure returning. A scanner that cannot fail is not a gate.
run: | run: |
docker run --rm -v "$PWD:/repo" -w /repo \ set -euo pipefail
ghcr.io/gitleaks/gitleaks:v8.21.2@sha256:0e99e8821643ea5b235718642b93bb32486af9c8162c8b8731f7cbdc951a7f46 \ curl -fsSL -o /tmp/gitleaks.tgz \
detect --no-banner --source /repo "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_arm64.tar.gz"
echo "${GITLEAKS_SHA256} /tmp/gitleaks.tgz" | sha256sum -c -
tar -xzf /tmp/gitleaks.tgz -C /tmp gitleaks
install -m 0755 /tmp/gitleaks /usr/local/bin/gitleaks
# Prove there is a repository to scan before trusting the verdict.
commits=$(git rev-list --count HEAD)
echo "scanning $commits commits"
test "$commits" -gt 0
gitleaks detect --no-banner --source . --redact --exit-code 1
- name: pip-audit (dependency CVEs) - name: pip-audit (dependency CVEs)
# --strict fails on an audit error rather than shrugging and reporting clean. # --strict fails on an audit error rather than shrugging and reporting clean.
+36 -7
View File
@@ -22,21 +22,50 @@ set -euo pipefail
SERVICES=(api worker reconciler) SERVICES=(api worker reconciler)
CHART_VALUES="deploy/chart/values.yaml" CHART_VALUES="deploy/chart/values.yaml"
# yq, pinned by digest. Not python+pyyaml: a yaml round-trip strips every comment in
# values.yaml, and those comments are the only thing explaining why the digests are there.
# yq edits in place and leaves the rest of the file alone.
YQ_IMAGE="mikefarah/yq:4.44.6@sha256:b1d117c609ba990436ad1649299e2f6c378f62cb562caf30b6f2fb6144713422"
WORKDIR="$(mktemp -d)" WORKDIR="$(mktemp -d)"
cleanup() { cleanup() {
rm -rf "${WORKDIR}" rm -rf "${WORKDIR}"
} }
trap cleanup EXIT trap cleanup EXIT
yq() { # yq as a checksum-pinned binary, NOT `docker run -v "$PWD:/work"`.
docker run --rm -v "${PWD}:/work" -w /work -u "$(id -u):$(id -g)" "${YQ_IMAGE}" "$@" #
# The container form cannot see the checkout on the Gitea runner: the -v source path is
# resolved by the dind sidecar's daemon, not by the job container, so yq reported
# "stat deploy/chart/values.yaml: no such file or directory" for a file that plainly
# exists. The same boundary silently broke helm and gitleaks.
#
# Not python+pyyaml: a yaml round-trip strips every comment in values.yaml, and those
# comments are the only thing explaining why the digests are there. yq edits in place.
YQ_VERSION="4.44.6"
YQ_SHA256="9477ac3cc447b6c083986129e35af8122eb2b938fe55c9c3e40436fb966e5813"
ensure_yq() {
if command -v yq >/dev/null 2>&1; then
return
fi
local arch
case "$(uname -m)" in
aarch64|arm64) arch=arm64 ;;
x86_64) arch=amd64 ;;
*) echo "!! unsupported arch $(uname -m)" >&2; exit 1 ;;
esac
curl -fsSL -o "${WORKDIR}/yq" \
"https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_${arch}"
# Only the arm64 digest is pinned; this runner is Ampere. On another arch the download
# is still version-pinned, and the mismatch is reported rather than silently accepted.
if [ "${arch}" = "arm64" ]; then
echo "${YQ_SHA256} ${WORKDIR}/yq" | sha256sum -c -
else
echo "warning: no pinned checksum for ${arch}; version-pinned only" >&2
fi
chmod +x "${WORKDIR}/yq"
PATH="${WORKDIR}:${PATH}"
export PATH
} }
ensure_yq
echo "==> resolving digests for tag ${IMAGE_TAG}" echo "==> resolving digests for tag ${IMAGE_TAG}"
for svc in "${SERVICES[@]}"; do for svc in "${SERVICES[@]}"; do
image="${REGISTRY}/${IMAGE_NS}/svcforge-${svc}" image="${REGISTRY}/${IMAGE_NS}/svcforge-${svc}"