Skip to content

v0.10.0 "Verified Releases & Safe Gates"

Choose a tag to compare

@github-actions github-actions released this 01 Nov 14:40
· 178 commits to main since this release
134d302

Every release is now built from its tag with hash-locked tools and carries signed build provenance, and no output or CI gate treats an input OpenPulse did not fully read as clean. This release changes outputs and gates:

  • --fail-on (CLI and Action) now fails on a repository it could not read.
  • openpulse attest emits evidence contract 2.1.0, where runs with no repository say NOT_APPLICABLE coverage.
  • A token file with two non-empty lines is refused.

Webhooks connect only to the address that passed the SSRF check. Verdicts and the frozen evaluation are unchanged. Read the upgrade notes before upgrading CI gates or contract consumers.

Upgrade notes

  • openpulse attest emits evidence contract 2.1.0, and runs with no repository say NOT_APPLICABLE coverage (#109, v0.10.0 item V3). Contracts from --watchlist, --sbom, --spdx, --lockfile, --manifest or --images runs said inspection_coverage.status: COMPLETE although no repository was scanned; they now say NOT_APPLICABLE. A consumer validating against the 2.0 schema must switch to schemas/evidence-contract/v2.1/schema.json, and one that switches on the status must accept the new value. Runs with --github-repo are unchanged. The Action's inspection-coverage output can be NOT_APPLICABLE, which raises no warning. Hash and every other value are unchanged.
  • --github-token-file accepts exactly one non-empty line, holding a visible-ASCII token (#138, v0.10.0 item V5). A leading blank line is now skipped instead of rejected. A file with two non-empty lines, which used the first and ignored the rest, is now refused, because it's ambiguous which line is the token. A token with a space, a control character or a non-ASCII character is refused with a clear message, instead of failing later inside the HTTP client. No message contains the token. The read goes through the shared bounded reader (core.bounded_io), so its size error now reads "is N bytes (limit 4096)".
  • --fail-on now fails on FAILED repository coverage (#101, v0.10.0 item V2). check --fail-on, attest --fail-on and the GitHub Action's fail-on exited 0 when a --github-repo scan FAILED, so a CI gate could pass on a repository OpenPulse could not read. They now exit 1 (after writing every output, for attest), as --strict already did, and say why. PARTIAL coverage is unchanged: it fails only --fail-on-partial. Without a gating flag, FAILED coverage stays report-only.
  • Webhook delivery ignores HTTP(S)_PROXY (finding M5, v0.10.0 item V4, #242). A proxy resolves the webhook host itself, which would undo the DNS pinning below, so --webhook delivery now connects directly. A host that can only reach the webhook through a proxy can no longer deliver.

Security

  • DNS-pinned webhook transport (finding M5, v0.10.0 item V4, #242). The webhook SSRF check resolved the host, then httpx connected by hostname and resolved it again, so a resolver that answered with a public address first and 127.0.0.1 or 169.254.169.254 second (DNS rebinding) steered delivery past the check. The host is now resolved once, every returned address is checked, and the connection goes to a validated IP while TLS SNI, certificate verification and the Host header stay on the original hostname (a custom httpcore network backend under httpx's transport; no new dependency). IPv4-mapped (::ffff:a.b.c.d), 6to4 (2002::/16) and NAT64 (64:ff9b::/96, 64:ff9b:1::/48) addresses are unwrapped first, and a refusal names the embedded IPv4 and its wrapping. The rest of the policy is unchanged: https only unless --webhook-allow-http, no redirects, capped response bodies, and no URL path in output.

Added

  • A warning when the GitHub token file is readable by other users (#139, v0.10.0 item V5). On POSIX, if the file's mode grants group or other access (mode & 0o077), check, digest, warnings, attest and discover print warning: GitHub token file <path> is readable by other users; run chmod 600 <path> to stderr. The run continues; the warning prints the path, never the token. The README example now runs chmod 600.
  • Attested releases (#111, v0.10.0 item V1). A v* tag runs .github/workflows/release.yml.
    • Build: scripts/build_release.py builds the sdist and wheel from the tagged tree. It uses setuptools' own PEP 517 hooks with the hash-locked setuptools from .github/ci-tools.lock, without build isolation, so no build tool is fetched at build time.
    • Refused builds: a tag that differs from the package version, and a locked setuptools below [build-system].requires.
    • Checks before publishing: the wheel is smoke-tested in a clean venv from outside the checkout, and the deterministic SBOM is regenerated twice and compared.
    • Provenance: every file gets Sigstore-signed SLSA build provenance (actions/attest-build-provenance); verify with gh attestation verify <file> --repo IkarusAILabs/OpenPulse.
    • On the GitHub release: the sdist, wheel, sbom.cdx.json and SHA256SUMS, attached without replacing files that are already there.
    • Permissions: each job gets only what it needs: id-token only for the job that signs, contents: write only for the job that attaches.
    • Not done: nothing is published to PyPI; that waits on owner decision 7.

Fixed

  • Digest, warnings and the webhook body now say when a repository was not fully read (#102, v0.10.0 item V3). With a FAILED or PARTIAL --github-repo scan, openpulse digest and openpulse warnings used to drop the inspection result. Their body read "Nothing upcoming or newly-effective within the warning window", with no hint that the repository was never read, and check --digest --webhook posted the same. All three now carry > ⚠️ Repository inspection coverage: <status> — <reasons>. Results are not a clean bill of health. in the rendered body (and coverage_note in --output json), so it reaches the webhook too. Output is unchanged when coverage is COMPLETE or no repository is inspected.
  • The CI tools lock now satisfies the build floor. Dependabot raised [build-system].requires to setuptools>=84.2.1, while .github/ci-tools.lock stayed at 84.1.0. The lock now pins 84.2.1, with the hashes PyPI publishes, and a test fails if the two drift apart again.