') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); feat(installer): offline Sigstore bundle — verify --offline, no live Rekor (#584) by shujaatTracebloc · Pull Request #599 · tracebloc/client · GitHub
Skip to content

feat(installer): offline Sigstore bundle — verify --offline, no live Rekor (#584) - #599

Merged
shujaatTracebloc merged 2 commits into
developfrom
fix/584-offline-sigstore-bundle
Aug 5, 2026
Merged

feat(installer): offline Sigstore bundle — verify --offline, no live Rekor (#584)#599
shujaatTracebloc merged 2 commits into
developfrom
fix/584-offline-sigstore-bundle

Conversation

@shujaatTracebloc

@shujaatTraceblocshujaatTracebloc commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What & why

Closes#584 (child 3/4 of #578, network resilience). This is the item that actually fixes the field Windows install failure on a TLS-inspecting corporate network.

Keyless cosign verification needs sigstore's transparency log (Rekor) at verify time, and our short-lived keyless cert is expired by install time — so a network that blocks or TLS-inspects sigstore fails verification even for a valid signature. #583's CA wiring doesn't cover this on Windows/macOS (Go ignores SSL_CERT_FILE there), and skipping the tlog was proven not to verify the expired cert. The fix is an offline Sigstore bundle: it carries the Rekor inclusion proof (SET), so the cert's validity at signing is established with no live call.

Changes

Signingrelease-helm-chart.yaml

  • cosign sign-blob now also emits manifest.sha256.bundle and publishes it as a release asset, alongside the existing .sig/.cert.

Verifyinstall.sh + install.ps1

  • Prefer cosign verify-blob --bundle <bundle> --certificate-identity-regexp … --certificate-oidc-issuer … --offline — a full check (signature + cert identity + tlog inclusion) with no live Rekor.
  • Fallback: if the bundle 404s (older release) or doesn't verify, fall through to the existing online .sig/.cert keyless path. That path does the same full keyless verification, just needing live Rekor — so it's a fallback, never a security downgrade.
  • PS: extracted Invoke-CosignVerifyBlob so the fail-closed sentinel (nonzero $LASTEXITCODE seed) + stderr suppression (Installer must never expose tracebloc internals or user PII in logs/output #576) back both paths from one place.

Tests

  • bats: bootstrap prefers --bundle --offline when a bundle is published (and does not touch the sig/cert path); falls back to sig/cert when no bundle exists (older release); cosign-failure still fails closed (unchanged).
  • Pester:Confirm-ManifestSignature verifies --bundle --offline first with a sig/cert fallback; Invoke-CosignVerifyBlob is fail-closed (sentinel + stderr suppression).
  • Full Pester 449/0/9; guards + manifest --check clean. install.sh/install.ps1 are the bootstrap trust root (not manifested), so no manifest change.

⚠️ Sequencing (cross-repo-ish, but all in this repo)

The installer prefers the bundle but a new release must be cut after merge so the manifest.sha256.bundle asset actually exists. Until then — and for all existing releases — the fallback (online sig/cert) path runs, so nothing regresses. Once a release ships the bundle, sigstore-blocked networks (incl. the field Windows case) verify fully offline with no env var or user action.

Notes


Note

Medium Risk
Changes the bootstrap trust root and release signing assets; behavior is backward-compatible via fallback, but a bad bundle rollout could affect installs until the next release ships bundles correctly.

Overview
Adds offline manifest verification so installs succeed on networks that block or TLS-inspect Sigstore/Rekor, and so expired short-lived keyless certs can still be validated using the bundle’s embedded signing-time proof.

The release workflow now emits and publishes manifest.sha256.bundle alongside the existing .sig/.cert when signing manifest.sha256.

install.sh and install.ps1 try cosign verify-blob --bundle … --offline first (same identity pins as before). If the bundle is missing (older releases) or verification fails, they fall back to the existing online .sig/.cert path—same keyless checks, not a weaker mode. On Windows, Invoke-CosignVerifyBlob centralizes fail-closed $LASTEXITCODE seeding and stderr suppression for both paths.

Tests (bats + Pester) cover bundle-first success, sig/cert fallback, older releases without a bundle, and fail-closed when both paths fail.

Reviewed by Cursor Bugbot for commit 5669847. Bugbot is set up for automated code reviews on this repo. Configure here.

@shujaatTraceblocshujaatTracebloc self-assigned this Aug 5, 2026
…Rekor (#584)
Child 3/4 of #578. Keyless cosign verification needs sigstore's Rekor at verify time,
and our short-lived keyless cert is expired by install time — so a network that blocks
or TLS-inspects sigstore fails verification even for a valid signature (the class behind
the field Windows failure on a TLS-inspecting corporate network that #583's CA wiring
does NOT cover, since Go ignores SSL_CERT_FILE on Windows/macOS). Skipping the tlog was
proven not to verify the expired cert; the offline bundle carries the Rekor inclusion
proof (SET) so the cert's validity at signing can be established without any live call.
- Signing (release-helm-chart.yaml): cosign sign-blob now also emits
manifest.sha256.bundle and publishes it as a release asset, alongside the .sig/.cert.
- Verify (install.sh + install.ps1): prefer `verify-blob --bundle <bundle> --offline`
(full check — signature + cert identity + tlog inclusion — with NO live Rekor). Falls
through to the existing online .sig/.cert keyless path for releases cut before the
bundle existed, or if the bundle doesn't verify — the SAME full check, just needing
live Rekor, so it's a fallback, never a downgrade.
- PS: extracted Invoke-CosignVerifyBlob so the fail-closed sentinel (nonzero
$LASTEXITCODE seed) + stderr suppression (#576) back BOTH paths from one place.
Tests: bootstrap prefers --bundle --offline when a bundle is published and does NOT hit
the sig/cert path; falls back to sig/cert when no bundle (older release); fail-closed on
a bad signature unchanged. Pester + bats green.
SEQUENCING: needs a NEW release cut after merge so the bundle asset exists; until then
the fallback (online) path runs. Existing releases keep working via the fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@shujaatTracebloc
shujaatTraceblocforce-pushed the fix/584-offline-sigstore-bundle branch from 014b359 to 664041bCompareAugust 5, 2026 07:09
@shujaatTracebloc
shujaatTracebloc marked this pull request as ready for review August 5, 2026 07:10
@shujaatTraceblocshujaatTracebloc added the e2e Run the full E2E last-mile journey (create_cluster → CLI → cluster info → data validate) label Aug 5, 2026

@saadqbalsaadqbal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice, careful PR — offline bundle is the right fix and the fallback is genuinely a fallback, not a downgrade. Approving.

One gap worth a follow-up (non-blocking): the bundle-verifies-then-fails path isn't tested — see inline. Minor nit: on Windows, folding the try/catch into Invoke-CosignVerifyBlob means a cosign that can't launch now surfaces the generic "couldn't confirm authentic" message instead of the old "couldn't run the verification step" — same fail-closed, just a slightly misleading message for a broken binary.

Comment threadscripts/install.sh
saadqbal
saadqbal previously approved these changes Aug 5, 2026
…ail-closed (reviewer)
saadqbal: the bundle-present-but-verify-fails -> sig/cert fallback branch was never
exercised (both bundle tests forced cosign exit 0; the fail-closed test had no bundle),
so a regression there would stay green. Add:
- bats: a fallback case (bundle published, cosign REJECTS the --bundle verify but ACCEPTS
sig/cert -> the sig/cert path runs and the install proceeds) and a both-fail case
(bundle present, every cosign verify fails -> fail closed, privileged step never runs).
- Pester: behavioural (not source-text) equivalents driving Confirm-ManifestSignature —
bundle-fails falls through to sig/cert (2 verify calls, no throw); both-fail throws the
authenticity error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@shujaatTracebloc
shujaatTracebloc merged commit e19f176 into developAug 5, 2026
37 checks passed
@shujaatTracebloc
shujaatTracebloc deleted the fix/584-offline-sigstore-bundle branch August 5, 2026 08:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

e2eRun the full E2E last-mile journey (create_cluster → CLI → cluster info → data validate)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Network resilience (3/4): offline Sigstore bundle — verify --offline, no live Rekor (cross-repo: signing + release)

3 participants

@shujaatTracebloc@saadqbal@LukasWodka