Skip to content

fix(ci): verify the KSail archive before installing it in the validate jobΒ #2841

Description

@devantler

πŸ€– Generated by the Agentic Engineer

Evidence

ci.yaml's βš™οΈ Setup KSail step downloads the KSail release archive from a version-pinned URL, then extracts it and sudo installs it β€” with no integrity check:

curl -fsSL "https://github.com/devantler-tech/ksail/releases/download/v${KSAIL_VERSION}/ksail_${KSAIL_VERSION}_linux_amd64.tar.gz" -o /tmp/ksail.tar.gz
tar -xzf /tmp/ksail.tar.gz -C /tmp
sudo install /tmp/ksail /usr/local/bin/ksail

A version-pinned URL is not an integrity check: it names which asset to fetch, not what bytes to expect. A replaced release asset would be installed and executed by the workflow.

Raised by CodeRabbit against the identical step newly added in #2830, where it has been fixed. This issue covers the pre-existing instance in ci.yaml, deliberately left out of that PR so a supply-chain change does not ride along inside a PR about Kubescape SARIF publishing.

Affected audience and impact

Every pull_request and merge_group run of ci.yaml that hits the validate job. merge_group runs gate merges to main, so the binary runs on a trusted path.

Expected behaviour

The archive is verified before it is extracted or installed, and a verification that cannot be performed fails the step rather than being skipped.

Acceptance criteria

  • The validate job verifies ksail_${KSAIL_VERSION}_linux_amd64.tar.gz against the release's published ksail_${KSAIL_VERSION}_checksums.txt before tar -xzf.
  • A missing checksum entry fails the step with an actionable message β€” it must not silently skip verification. Note grep exits 1 under set -euo pipefail, so the lookup needs || true or the step dies before the message prints.
  • A checksum mismatch fails the step and names the expected and actual digests.
  • Verified three ways before merge: real archive passes, a tampered archive fails, a missing entry fails closed.

The implementation in #2830's validate-main.yaml kubescape-baseline job is a working reference β€” this is the same block.

Deliberately NOT a pinned literal SHA

KSAIL_VERSION is Renovate-managed (datasource=github-releases, group ksail). Renovate bumps the version but cannot update an opaque digest beside it, so a pinned SHA breaks CI on every bump until hand-edited β€” a control that reliably breaks the routine path is one that gets removed.

Stated limit: archive and checksum list share an origin, so this detects a corrupted, truncated or individually-substituted asset, not a fully compromised release.

Stronger follow-up (measured, not speculative)

KSail publishes an attestation for the exact asset digest, but it is an https://in-toto.io/attestation/release/v0.2 predicate and gh attestation verify returns no attestations found for it β€” with the default predicate and with that one passed explicitly. So attestation-based verification is not a drop-in today; it needs its own investigation.

Size

Small β€” one run: block, plus the three-way verification.

Activity

  1. moved this to πŸ“₯ Backlog in 🌊 Project Boardon Jul 28, 2026
  2. coderabbitai commented on Jul 28, 2026

    @coderabbitai
    Contributor
    πŸ”— Related PRs

    #2319 - feat(security): cosign-verify the infrastructure Flux OCI artifact [merged]
    #2502 - feat(ci): validate embedded JSON blobs in ConfigMaps [merged]
    #2538 - fix(ci): cache kubeconform schemas in Validate Manifests job [merged]
    #2625 - fix(deploy): synchronize production GHCR pull auth [merged]
    #2633 - fix(deploy): verify incoming image on Talos nodes [merged]


    πŸ“ Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    πŸ§ͺ Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    πŸ’¬ Have feedback or questions? Drop into our discord!

  3. self-assigned this
    on Aug 10, 2026
  4. moved this from πŸ“₯ Backlog to πŸ‘€ In Review in 🌊 Project Boardon Aug 10, 2026
  5. devantler commented on Aug 10, 2026

    @devantler
    ContributorAuthor

    πŸ€– Generated by the Agentic Engineer

    Correction to this issue's own premise, found while fixing it (#3073).

    The body says "merge_group runs gate merges to main, so the binary runs on a trusted path."
    That is wrong for this job. ci.yaml's validate job is gated on
    github.event_name == 'pull_request', so it never runs for merge_group β€” the workflow carries that
    trigger, but this job does not accept it. CodeRabbit caught the same thing in my PR comment, where I
    had repeated the claim.

    The exposure is still real, just different: validate runs on fork PRs, and the archive is
    sudo installed and executed on the runner. The fix stands on that.

    The merge-gating claim belongs to the deploy paths, which install the same archive with no check
    at all β€” now tracked as #3075 (and #3076 for talosctl, which has no check anywhere). Those are the
    higher-severity instances of this class; this one was the least privileged of four.

    Generalisable: verify a threat statement against the job condition, not the workflow's on:
    list. A workflow-level trigger says nothing about which jobs actually accept it.

  6. moved this from πŸ‘€ In Review to βœ… Done in 🌊 Project Boardon Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Fields

No fields configured for Security.

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions