Skip to content

feat(tcfeed): resolve the tarball hash alongside the version pin - #134

Merged
ralyodio merged 1 commit into
masterfrom
worktree-tcfeed-integrity
Aug 14, 2026
Merged

ralyodio merged 1 commit into
masterfrom
worktree-tcfeed-integrity

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Pairs with sh1pt's threatcrush-scan pack change, which adds a threatcrushIntegrity input the workflow checks before installing anything. This fills it.

The two have to land together: without this, rendering the new pack stops with the pack has an input tcfeed cannot fill — the guard doing its job.

Why a hash and not just the pin

A version pin says which release to fetch. It does not say the bytes are the ones that release was published with, and the party answering "which version" is the party serving the bytes.

That's the line GlassOnTin drew on Haven#532 when they asked for "exact version + integrity hash" rather than accepting the pin as the answer. The exact version shipped a while back; the hash is the half that was still missing.

How it fails

Deliberately in two different directions:

spec on failure why
resolved by tcfeed throw the fallback is an unverified install in a stranger's repo — the thing being fixed
set via TCFEED_SPEC return empty a tarball or local build has no dist.integrity; refusing to render would break a legitimate use. The pack treats empty as "no check" and warns in the job log

Anything that isn't an SRI hash is treated as no hash. A half-read line pinned into somebody's workflow fails their build closed on every run — worse than not pinning one.

Verification

Against the updated pack:

  • resolved — spec 0.11.0, hash sha512-EKcaxsgi…hwWfQ==, matching npm view dist.integrity exactly
  • rendered — want='sha512-EKcaxsgi…' present in the workflow, 0 placeholders left
  • TCFEED_SPEC=./some-local-build.tgz — renders want='', does not throw
  • strict — a spec with no registry entry throws rather than shipping an unverified install

🤖 Generated with Claude Code

The pack gained a threatcrushIntegrity input, which the workflow checks
before it installs anything. This fills it. Without this the render stops
with "the pack has an input tcfeed cannot fill", which is the guard doing
its job — but it means the two halves have to land together.

resolveIntegrity reads the registry's own dist.integrity for whatever
spec resolveSpec settled on, and is cached per spec for the same reason
the version is: one answer per run, so every request in a batch pins the
same bytes.

It fails in two different directions on purpose:

  spec this resolved itself   throw. The fallback is an unverified
                              install in a stranger's repository, which
                              is the thing being fixed.

  spec the caller chose       return empty. TCFEED_SPEC pointing at a
                              tarball or a local build has no
                              dist.integrity to read, and refusing to
                              render would break a legitimate use. The
                              pack treats empty as "no check" and warns
                              in the job log.

Anything that is not an SRI hash is treated as no hash. A half-read line
pinned into somebody's workflow fails their build closed on every run,
which is worse than not pinning one.

Verified against the updated pack:

  resolved     spec 0.11.0, hash sha512-EKcaxsgiydi7…hwWfQ==, matching
               npm view dist.integrity exactly
  rendered     want='sha512-EKcaxsgi…' in the workflow, 0 placeholders left
  TCFEED_SPEC  ./some-local-build.tgz renders want='' and does not throw
  strict       a spec with no registry entry throws rather than shipping
               an unverified install

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

67 finding(s)

HIGH/CRITICAL: 11 | MEDIUM: 55 | LOW: 1

Severity Rule Location
HIGH secret-aws-access-key prd/0003-detect-hardcoded-secrets-before-they-are-committed-or-served.md:126
HIGH sh-eval-expansion .githooks/pre-commit:26
HIGH sh-remote-script-execution apps/web/public/install.sh:272
HIGH sh-remote-script-execution apps/web/public/install.sh:320
HIGH secret-generic-credential modules/spend-guard/config/example.conf.toml:13
HIGH secret-generic-credential modules/spend-guard/README.md:84
HIGH secret-generic-credential PRD.md:268
HIGH tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:121
HIGH tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:122
HIGH sh-remote-script-execution scripts/smoke-test.sh:46
HIGH sh-remote-script-execution scripts/smoke-test.sh:47
MEDIUM insecure-temp-file .githooks/commit-msg:16
MEDIUM insecure-temp-file .githooks/post-commit:20
MEDIUM js-shell-exec-interpolation apps/cli/src/commands/init.ts:70
MEDIUM js-shell-exec-interpolation apps/cli/src/commands/init.ts:79
MEDIUM js-shell-exec-interpolation apps/cli/src/commands/service.ts:88
MEDIUM js-shell-exec-interpolation apps/cli/src/commands/service.ts:111
MEDIUM sql-template-interpolation apps/cli/src/core/state.ts:121
MEDIUM sql-template-interpolation apps/cli/src/core/state.ts:125
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:31
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:33
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:34
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:35
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:36
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:43
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:49
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:56
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:63
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:82
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:84
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:85
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:93
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:98
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:105
MEDIUM js-shell-exec-interpolation apps/cli/src/daemon/firewall/adapters.ts:112
MEDIUM js-shell-exec-interpolation apps/cli/src/index.ts:419
MEDIUM js-unescaped-html-sink apps/web/src/app/about/page.tsx:180
MEDIUM js-unescaped-html-sink apps/web/src/app/about/page.tsx:184
MEDIUM js-open-redirect apps/web/src/app/auth/login/page.tsx:50
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:125
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:153
MEDIUM js-unescaped-html-sink apps/web/src/app/blog/[slug]/page.tsx:157
MEDIUM js-unescaped-html-sink apps/web/src/app/get-whitepaper/page.tsx:346
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:211
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:215
MEDIUM js-unescaped-html-sink apps/web/src/app/layout.tsx:219
MEDIUM js-unescaped-html-sink apps/web/src/app/page.tsx:120
MEDIUM js-unescaped-html-sink apps/web/src/app/store/[slug]/page.tsx:107
MEDIUM js-open-redirect apps/web/src/components/funding/FundingClient.tsx:97
MEDIUM manifest-install-lifecycle-script package.json:24

…and 17 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit f096537 into master Aug 14, 2026
11 checks passed
@ralyodio
ralyodio deleted the worktree-tcfeed-integrity branch August 14, 2026 01:27
ralyodio added a commit that referenced this pull request Aug 14, 2026
…in (#135)

The integrity check shipped in #134 and sh1pt#959. The request body never
mentioned it, so the strongest supply-chain answer we have was invisible
to the people who asked for it.

That is not a cosmetic gap. Every decline on this workflow so far has
been about the install, not the scanner: SonarCloud on githubactions:S8543,
CodeRabbit scoring a request Moderate for handing an unpinned scanner a
write-scoped job, and Haven's maintainer declining with

  whoever can publish that package can run code in this repository's CI
  from that point on, forever, without a further PR

and naming the remedy exactly — "pinning to an exact version + integrity
hash would address that specific objection". We now do both halves and
were still describing only the first.

The paragraph now says what actually happens: the tarball is downloaded,
hashed, checked against a value committed in the workflow file, and not
installed on a mismatch. It also gives the reader the command to check
that value against the registry themselves, because a claim a reviewer
can verify in one line is worth more than one they have to take on trust
— which is the whole argument the paragraph is making.

Does not touch the 45 requests already open. Their workflow files can be
brought up to the current pack with `check --fix`; their bodies are
prose in somebody else's notification feed and are left alone.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant