Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/renovate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ jobs:
LOG_LEVEL: info
RENOVATE_REPOSITORIES: ${{ github.repository }}
RENOVATE_DRY_RUN: ${{ inputs.dry-run || 'false' }}
RENOVATE_ALLOWED_COMMANDS: '["^bash scripts/renovate/[a-zA-Z0-9_-]+\\.sh$"]'
4 changes: 2 additions & 2 deletions images/sandbox/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ RUN apt-get update \
# ---------------------------------------------------------------------------
# tirith — terminal security scanner for PreToolUse hooks.
# Pinned version + sha256 checksum for supply chain safety.
# To update: bump TIRITH_VERSION and TIRITH_SHA256_{AMD64,ARM64} from the checksums.txt
# in the GitHub release: https://github.com/sheeki03/tirith/releases
# Renovate bumps TIRITH_VERSION; postUpgradeTasks runs
# scripts/renovate/update-tirith-checksums.sh to refresh the SHA256 ARGs.
ARG TIRITH_VERSION=0.3.1
ARG TIRITH_SHA256_AMD64=571e6a300e4c444293476537a322666069e561c7f05283d6650f5b8ef83db3ac
ARG TIRITH_SHA256_ARM64=0462fe5083b4c72c45a8de918d5413e21d17aa8077aa7dbe53c0876b112847bb
Expand Down
32 changes: 29 additions & 3 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,24 @@
"groupName": "cloudflare-workers"
},
{
"description": "Disable automerge for tirith — Renovate bumps the version but SHA256 checksums need manual refresh, so the PR always needs human intervention",
"description": "After cosign version bump, verify the new binary's sigstore signature and update COSIGN_SHA256",
Comment thread
rh-hemartin marked this conversation as resolved.
Comment thread
rh-hemartin marked this conversation as resolved.
"matchPackageNames": ["sigstore/cosign"],
"automerge": false,
"postUpgradeTasks": {
"commands": ["bash scripts/renovate/update-cosign-checksum.sh"],
"fileFilters": ["scripts/renovate/update-tirith-checksums.sh"],
"executionMode": "branch"
}
},
{
"description": "After tirith version bump, fetch and update SHA256 checksums from the release",
Comment thread
rh-hemartin marked this conversation as resolved.
"matchPackageNames": ["sheeki03/tirith"],
"automerge": false
"automerge": false,
Comment thread
rh-hemartin marked this conversation as resolved.
"postUpgradeTasks": {
"commands": ["bash scripts/renovate/update-tirith-checksums.sh"],
"fileFilters": ["images/sandbox/Containerfile"],
"executionMode": "branch"
}
}
],
"postUpdateOptions": ["gomodTidy"],
Expand Down Expand Up @@ -89,14 +104,25 @@
},
{
"customType": "regex",
"description": "Track tirith CLI version pin in the sandbox image. TIRITH_SHA256_{AMD64,ARM64} must be refreshed manually from the release checksums.txt at https://github.com/sheeki03/tirith/releases — the image build fails on checksum mismatch until they are.",
"description": "Track tirith CLI version pin in the sandbox image. SHA256 checksums are updated automatically by postUpgradeTasks (scripts/renovate/update-tirith-checksums.sh).",
"managerFilePatterns": ["/^images/sandbox/Containerfile$/"],
"matchStrings": [
"ARG TIRITH_VERSION=(?<currentValue>\\d+\\.\\d+\\.\\d+)"
],
"depNameTemplate": "sheeki03/tirith",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
Comment thread
rh-hemartin marked this conversation as resolved.
"description": "Track cosign version pin in update-tirith-checksums.sh. COSIGN_SHA256 is updated automatically by postUpgradeTasks (scripts/renovate/update-cosign-checksum.sh).",
"managerFilePatterns": ["/^scripts/renovate/update-tirith-checksums\\.sh$/"],
"matchStrings": [
"COSIGN_VERSION=(?<currentValue>\\d+\\.\\d+\\.\\d+)"
],
"depNameTemplate": "sigstore/cosign",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
}
]
}
83 changes: 83 additions & 0 deletions scripts/renovate/update-cosign-checksum.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Called by Renovate postUpgradeTasks after a cosign version bump.
# Downloads the new cosign binary, verifies its sigstore bundle signature
# using the old (currently installed) cosign binary, then updates
# COSIGN_SHA256 in update-tirith-checksums.sh.
set -euo pipefail

SCRIPT="scripts/renovate/update-tirith-checksums.sh"

# --- Read old and new versions ---
OLD_VERSION=$(git show HEAD:"${SCRIPT}" | grep -oP '^COSIGN_VERSION=\K\S+' || true)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] OLD_VERSION extraction via git show HEAD assumes an unconfirmed Renovate commit ordering for executionMode: branch

OLD_VERSION/OLD_SHA256 are read via git show HEAD:"${SCRIPT}" while NEW_VERSION is read from the working tree. This only produces a correct diff if Renovate applies the COSIGN_VERSION bump to the working tree but has NOT yet committed it to HEAD by the time postUpgradeTasks runs. The cosign packageRule in renovate.json now uses executionMode: "branch" (changed in this same PR specifically to work around the previously-flagged 'update'-mode same-file collision, renovatebot/renovate#42263). I could not confirm from Renovate's public docs whether 'branch' mode commits the manager's version bump before or after invoking postUpgradeTasks. If the bump is committed first, git show HEAD would already return the NEW version, OLD_VERSION would equal NEW_VERSION, and the script would print "cosign version unchanged (...)" and exit 0 without verifying the new binary or refreshing COSIGN_SHA256 — a silent no-op that looks successful in CI logs.

Suggestion: confirm Renovate's actual commit ordering for executionMode: branch against a real cosign version bump before relying on this, or make the script resilient to either ordering (e.g. derive OLD_VERSION from the parent commit git show HEAD~1:... or add an explicit log/guard that treats an unexpected 'unchanged' result as suspicious when the Renovate PR title indicates a bump).

if [[ -z "${OLD_VERSION}" ]]; then
echo "error: could not extract COSIGN_VERSION from committed ${SCRIPT}" >&2
exit 1
fi
if [[ ! "${OLD_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "error: OLD_VERSION is not a valid semver: ${OLD_VERSION}" >&2
exit 1
fi

OLD_SHA256=$(git show HEAD:"${SCRIPT}" | grep -oP '^COSIGN_SHA256=\K\S+' || true)
if [[ -z "${OLD_SHA256}" ]]; then
echo "error: could not extract COSIGN_SHA256 from committed ${SCRIPT}" >&2
exit 1
fi
if [[ ! "${OLD_SHA256}" =~ ^[0-9a-f]{64}$ ]]; then
echo "error: OLD_SHA256 is not a valid sha256 hex digest: ${OLD_SHA256}" >&2
exit 1
fi

NEW_VERSION=$(grep -oP '^COSIGN_VERSION=\K\S+' "${SCRIPT}" || true)
if [[ -z "${NEW_VERSION}" ]]; then
echo "error: could not extract COSIGN_VERSION from working-tree ${SCRIPT}" >&2
exit 1
fi
if [[ ! "${NEW_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "error: NEW_VERSION is not a valid semver: ${NEW_VERSION}" >&2
exit 1
fi

if [[ "${OLD_VERSION}" == "${NEW_VERSION}" ]]; then
echo "cosign version unchanged (${OLD_VERSION}), nothing to do"
exit 0
fi

echo "cosign: ${OLD_VERSION} -> ${NEW_VERSION}"

WORKDIR="$(mktemp -d)"
trap 'rm -rf "${WORKDIR}"' EXIT

BASE_URL="https://github.com/sigstore/cosign/releases/download"

# --- Bootstrap the old cosign binary to use as verifier ---
OLD_BINARY="${WORKDIR}/cosign-old"
curl -fsSL "${BASE_URL}/v${OLD_VERSION}/cosign-linux-amd64" -o "${OLD_BINARY}"
echo "${OLD_SHA256} ${OLD_BINARY}" | sha256sum -c -
chmod +x "${OLD_BINARY}"

# --- Download new cosign binary and its sigstore bundle ---
NEW_BINARY="${WORKDIR}/cosign-linux-amd64"
curl -fsSL "${BASE_URL}/v${NEW_VERSION}/cosign-linux-amd64" -o "${NEW_BINARY}"
curl -fsSL "${BASE_URL}/v${NEW_VERSION}/cosign-linux-amd64.sigstore.json" -o "${NEW_BINARY}.sigstore.json"

# --- Verify the new binary's signature using the old cosign ---
"${OLD_BINARY}" verify-blob \
--bundle "${NEW_BINARY}.sigstore.json" \
--certificate-identity "keyless@projectsigstore.iam.gserviceaccount.com" \
--certificate-oidc-issuer "https://accounts.google.com" \
"${NEW_BINARY}"

echo "cosign signature verified for v${NEW_VERSION}"

# --- Compute and update the SHA256 ---
NEW_SHA256=$(sha256sum "${NEW_BINARY}" | awk '{print $1}')

if [[ ! "${NEW_SHA256}" =~ ^[0-9a-f]{64}$ ]]; then
echo "error: computed checksum is not a valid sha256 hex digest: ${NEW_SHA256}" >&2
exit 1
fi

sed -i "s/^COSIGN_SHA256=.*/COSIGN_SHA256=${NEW_SHA256}/" "${SCRIPT}"

echo "updated COSIGN_SHA256 to ${NEW_SHA256}"
77 changes: 77 additions & 0 deletions scripts/renovate/update-tirith-checksums.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
# Called by Renovate postUpgradeTasks after a tirith version bump.
Comment thread
rh-hemartin marked this conversation as resolved.
Comment thread
rh-hemartin marked this conversation as resolved.
# Fetches the new release's checksums.txt, verifies its cosign signature
# against the Sigstore transparency log, and patches the Containerfile
# so the SHA256 ARGs match the bumped version.
set -euo pipefail

# --- Retrieve the new Tirith version ---
FILE="images/sandbox/Containerfile"
VERSION=$(grep -oP 'ARG TIRITH_VERSION=\K\S+' "${FILE}" || true)
if [[ -z "${VERSION}" ]]; then
echo "Tried to retrieve Tirith version from ${FILE}, couldn't do it. Exiting."
Comment thread
waynesun09 marked this conversation as resolved.
exit 1
fi
if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "error: VERSION is not a valid semver: ${VERSION}" >&2
exit 1
fi

BASE_URL="https://github.com/sheeki03/tirith/releases/download/v${VERSION}"
Comment thread
rh-hemartin marked this conversation as resolved.

# --- Bootstrap cosign if not already available ---
# Pinned version + SHA256 so this script is self-contained inside the
# Renovate container, which does not ship cosign.
COSIGN_VERSION=3.1.2
Comment thread
rh-hemartin marked this conversation as resolved.
COSIGN_SHA256=f7622ed3cf22e55e1ae6377c080979ff77a22da9981c11df222a2e444991e7cf
Comment thread
rh-hemartin marked this conversation as resolved.

WORKDIR="$(mktemp -d)"
trap 'rm -rf "${WORKDIR}"' EXIT

if command -v cosign &>/dev/null; then
Comment thread
rh-hemartin marked this conversation as resolved.
COSIGN=cosign
else
COSIGN="${WORKDIR}/cosign"
curl -fsSL "https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64" \
-o "${COSIGN}"
echo "${COSIGN_SHA256} ${COSIGN}" | sha256sum -c -
chmod +x "${COSIGN}"
fi

# --- Fetch checksums and cosign verification artifacts ---
for f in checksums.txt checksums.txt.sig checksums.txt.pem; do
if ! curl -fsSL "${BASE_URL}/${f}" -o "${WORKDIR}/${f}"; then
echo "error: failed to fetch ${BASE_URL}/${f}" >&2
exit 1
fi
done

# --- Verify the cosign signature on checksums.txt ---
# Pin the certificate SAN to tirith's .github/workflows/ path (not the whole
# repo), matching the upstream install.sh scope.
"${COSIGN}" verify-blob \
--certificate "${WORKDIR}/checksums.txt.pem" \
--signature "${WORKDIR}/checksums.txt.sig" \
--certificate-identity-regexp "^https://github\\.com/sheeki03/tirith/\\.github/workflows/" \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] cosign certificate-identity-regexp for tirith accepts any .github/workflows/ file, not just release.yml

Independently verified by decoding the real checksums.txt.pem for tirith v0.3.1: the certificate SAN is exactly https://github.com/sheeki03/tirith/.github/workflows/release.yml@refs/tags/v0.3.1. The --certificate-identity-regexp here is anchored only at the start (^https://github\.com/sheeki03/tirith/\.github/workflows/), so it would accept a Fulcio cert minted by ANY workflow under that path, not just release.yml — confirmed via the GitHub API that sheeki03/tirith also has ci.yml, bench.yml, fuzz.yml, and threatdb.yml. None of those currently declare id-token: write (also confirmed), so this isn't exploitable today, but the trust boundary is looser than it needs to be: if any workflow in that upstream, third-party repo is ever granted OIDC signing permission for any reason, it could mint a signature this script would accept as a valid tirith "release". This is distinct from the earlier resolved thread on this line (which made the regex match upstream install.sh's .github/workflows/ prefix) — that fix matched upstream's scope, but upstream's scope itself doesn't pin to the specific release workflow.

Suggestion: anchor the regex to the exact release workflow and version being fetched, e.g. --certificate-identity-regexp "^https://github\\.com/sheeki03/tirith/\\.github/workflows/release\\.yml@refs/tags/v${VERSION}$", so a compromised or newly-added non-release workflow with id-token: write can't forge a valid signature.

--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"${WORKDIR}/checksums.txt"

echo "cosign signature verified for tirith v${VERSION} checksums.txt"

# --- Extract per-architecture SHA256 hashes ---
AMD64=$(grep -E '^[0-9a-f]{64} tirith-x86_64-unknown-linux-gnu\.tar\.gz$' "${WORKDIR}/checksums.txt" | awk '{print $1}' || true)
ARM64=$(grep -E '^[0-9a-f]{64} tirith-aarch64-unknown-linux-gnu\.tar\.gz$' "${WORKDIR}/checksums.txt" | awk '{print $1}' || true)

if [[ ! "${AMD64}" =~ ^[0-9a-f]{64}$ ]]; then
echo "error: amd64 checksum is not a valid sha256 hex digest: ${AMD64}" >&2
exit 1
fi
if [[ ! "${ARM64}" =~ ^[0-9a-f]{64}$ ]]; then
echo "error: arm64 checksum is not a valid sha256 hex digest: ${ARM64}" >&2
exit 1
fi

Comment thread
rh-hemartin marked this conversation as resolved.
sed -i "s/^ARG TIRITH_SHA256_AMD64=.*/ARG TIRITH_SHA256_AMD64=${AMD64}/" "${FILE}"
sed -i "s/^ARG TIRITH_SHA256_ARM64=.*/ARG TIRITH_SHA256_ARM64=${ARM64}/" "${FILE}"

echo "updated tirith checksums to v${VERSION}: amd64=${AMD64} arm64=${ARM64}"
Loading