8000 trust store: declare ca-certificates on ruby, typst, atuin, gws, skopeo; document where the store comes from by bryan-minimal · Pull Request #957 · gominimal/pkgs · GitHub
[go: up one dir, main page]

Skip to content

trust store: declare ca-certificates on ruby, typst, atuin, gws, skopeo; document where the store comes from - #957

Merged
bryan-minimal merged 4 commits into
mainfrom
trust-store-runtime-deps
Oct 22, 2026
Merged

bryan-minimal merged 4 commits into
mainfrom
trust-store-runtime-deps

Conversation

@bryan-minimal
@bryan-minimal bryan-minimal commented Oct 20, 2026 •
Copy link
Copy Markdown
Member

Follow-up to CVE-2026-111943 (eMudhra). This PR is about which environments can see /etc/ssl/certs at all. The bundle fixes themselves are in #948 and the other SECURITY PRs.

When a runtime has no trust store

The semantics, read from minimal fbf8505f6:

  • Injection. ca-certificates carries attrs.needed_for_internet. Transitives::new_with_seenset (crates/graph/src/transitives.rs:144-154) adds every such package to the closure of any spec with needs.internet. The check runs at every level of the recursion, so the store also reaches anything that has a needs.internet package (curl, wget, go, rust, uv, bun, …) in its runtime closure, and any build whose build deps include one.

  • Not hashed. The spec hash covers only declared build and runtime edges plus the names of needs (crates/graph/src/spec_hasher.rs:154-186, 332-340). A new bundle therefore rebuilds nothing that gets the store by injection; today a bump rehashes 10 packages.

  • The planner HACK only builds. ExecPlan::with_toplevels (crates/graph/src/planner.rs:321-329) adds needed_for_internet packages to the build plan's top levels. Nothing mounts the plan's top levels. What gets mounted is the closure:

    • session rootfs: crates/minimald/src/env.rs:489, 547-551;
    • OCI image: crates/op/src/oci_image.rs:45;
    • standalone tests: crates/op/src/standalone_test.rs:38-45;
    • build sandbox: crates/op/src/specs.rs:108.

    None of these consult the HACK.

So a runtime has no /etc/ssl/certs whenever nothing in its environment's closure declares needs.internet. Examples:

  • a session or task with packages = ["python"], or ["base", "ruby"]. base does not bring the store;
  • an OCI image of a python, ruby or jdk app;
  • a standalone test of such a package. These run network-isolated in that case, so only offline store checks are affected.

Sessions that include curl or git, like the repo's default tasks, already have the store. Session network access comes from the session plan (with_plan(args.network)), not from needs, so a session can be online with no store.

Today 124 of 621 packages have ca-certificates in their runtime closure. With this PR, 130 do.

Blast radius, measured

Measured with MINIMAL_SCIENCE_MODE=1 mip --no-fetch dump --packages --arch amd64, diffing spec hashes against main. "Per future bundle bump" is the rehash count when ca-certificates itself changes after the option lands.

option rehash now per future bundle bump
main (injection only) 0 10
openssl → ca-certificates 559 / 621 559, and a build cycle: ca-certificates build-depends on openssl
all leaf runtimes (python, ruby, node, node-lts, jdk-21/25/27, deno, typst, atuin, gws, chromium-*, gcloud) 559 559, driven by python (in the toolchain closure)
python alone / perl alone 559 / 559 559
node-lts + jdk-21/25/27 (deferred, see below) 37 –
this PR: ruby, typst, atuin, gws, skopeo 11 21

The 11 rehashed by this PR are atuin, bun, dev-language-servers, gws, hunk, opencode, plannotator, ruby, ruby-lsp, skopeo and typst. bun and plannotator come in through ruby. They are also rebuilt by #951; this PR does not touch their files.

What changes

  • ruby, typst, atuin, gws, skopeo: add ca-certificates to runtime_deps. Each one reads the system store:

    • ruby uses its openssl ext with OPENSSLDIR=/etc/ssl;
    • typst and atuin use native-tls, linking openssl;
    • gws uses reqwest rustls-tls-native-roots, checked in the 0.22.5 Cargo.toml;
    • skopeo uses Go crypto/x509.

    atuin also loses a duplicated comment in the same hunk.

  • ruby: new standalone test trust_store. Every live per-root file under /etc/ssl/certs must verify through OpenSSL::X509::Store#set_default_paths alone, and a store with no paths must verify none of them (the control). It runs offline.

  • AGENTS.md: new "Trust store" section. It covers where the store comes from and how it reaches an environment (injected vs declared, and that the HACK only builds it). It also gives the rules: why python, perl and openssl don't take the edge, don't use needs.internet to get the store, don't embed a store, the two denylists as they are today (and that they should converge), and the steps when a CA is distrusted.

Not changed here, on purpose

Merge order and caveats

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Enhancements

    • Improved TLS certificate verification for Atuin, GWS, Ruby, Skopeo, and Typst by providing a standard system trust store at /etc/ssl/certs.
    • Ruby now verifies the availability and validity of trusted certificate roots during its build checks.
  • Documentation

    • Added guidance on trust-store configuration, runtime declarations, planner behavior, and certificate denylist updates.

…ere the store comes from

ruby, typst, atuin, gws and skopeo verify TLS against /etc/ssl/certs but
had no ca-certificates in their runtime closure; they only saw a store when
something else in the environment (curl, git, a needs.internet package)
brought it in. Add the edge, plus a ruby standalone test that every live
root verifies through OpenSSL's default paths.

AGENTS.md
8000
 gains a "Trust store" section: how the store reaches an
environment (needed_for_internet injection vs a declared edge), that the
planner's top-level HACK only builds it, why python/perl/openssl do not
take the edge, and the no-embedded-store / one-denylist rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bryan-minimal
bryan-minimal requested a review from a team as a code owner October 20, 2026 23:03
@coderabbitai
coderabbitai Bot commented Oct 20, 2026 •
Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

  • Run on-demand review

This review includes 6 billable files and costs up to $1.50.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 25 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 3 included reviews currently available. Your 49 included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: gominimal/pkgs/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 50e7c594-0d2d-4a17-bcf4-da51e15e143e
📥 Commits

Reviewing files that changed from the base of the PR and between d27f7c5 and c98d29f.

📒 Files selected for processing: 6
  • AGENTS.md
  • packages/atuin/build.ncl
  • packages/gws/build.ncl
  • packages/ruby/build.ncl
  • packages/skopeo/build.ncl
  • packages/typst/build.ncl

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: gominimal/pkgs/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 1721614f-ad71-4ea4-beb9-87b140fbdc5c
📥 Commits

Reviewing files that changed from the base of the PR and between 949648c and d27f7c5.

📒 Files selected for processing: 6
  • AGENTS.md
  • packages/atuin/build.ncl
  • packages/gws/build.ncl
  • packages/ruby/build.ncl
  • packages/skopeo/build.ncl
  • packages/typst/build.ncl

Included review availability: This review used your included allowance. 2 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.

📝 Walkthrough

The changes document system trust-store behavior and add ca-certificates as a runtime dependency for Atuin, gws, Ruby, Skopeo, and Typst. A Ruby test checks certificate roots and OpenSSL default-path validation.

Changes: System trust-store support

Layer / File(s) Summary
Trust-store declaration rules
AGENTS.md
Documents /etc/ssl/certs, planner behavior, runtime dependency rules, and guidance for CA denylist maintenance.
Runtime dependencies and Ruby validation
packages/atuin/build.ncl, packages/gws/build.ncl, packages/ruby/build.ncl, packages/skopeo/build.ncl, packages/typst/build.ncl
Adds ca-certificates to the runtime dependencies of the five packages. Adds a Ruby test for certificate roots and OpenSSL default-path validation.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: norrietaylor, jtnkminimal

Merge Risk: ⚪ Minimal · up to d27f7

No actionable merge-blocking issue was found. The prerequisite certificate-bundle change is present; run the Ruby standalone test as part of normal validation.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title accurately identifies the main change: adding the ca-certificates runtime dependency to five packages and documenting the trust store source. It is specific and related to the changeset, alt…
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title accurately identifies the main change: adding the ca-certificates runtime dependency to five packages and documenting the trust store source. It is specific and related to the changeset, although it is somewhat long.

Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0 files. (6 skipped: 6 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@mitodrummer mitodrummer left a comment
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.

Approving with nits. Verified: gws 0.22.5 resolves reqwest rustls-tls-native-roots (rustls-native-certs + openssl-probe, no webpki-roots in its lockfile); typst 0.15.1's system-downloader links the system openssl and we don't enable vendor-openssl; atuin already carried openssl as a runtime dep; ruby links our openssl, built with --openssldir=/etc/ssl. The ruby trust_store test reads correctly: the 100-root floor prevents a vacuous pass, the m%" string leaves Ruby's #{} alone, and a malformed cert raises rather than passes.

Should fix (doc only)

  • AGENTS.md "One denylist": main currently has two lists, packages/ca-certificates/distrust.txt and cve-2026-111943-blocked-sha256.txt (the JDK patch reads the second through its own import). Following the rule as written at the next incident would append to one file and leave the JDKs unpatched. Describe both files as they are and say they should converge, or drop the sentence until they do.

Nits

  • PR body is stale: #948 and #949 merged this morning, so "merge after #948" is satisfied and the test no longer fails against main.
  • The roots.size < 100 guard counts all roots; a wrong sandbox clock could empty live and the test would verify nothing. Guard live.size instead.
  • AGENTS.md "so OpenSSL, curl, git, Python's ssl, Ruby, Go and rustls-native-certs all look for roots there": Go's crypto/x509 and openssl-probe check /etc/ssl/certs on their own, not because of openssldir.
  • In each runtime_deps, ca-certificates is placed ahead of glibc, which every touched file lists first; the import sits in the build-tool import block rather than the runtime-dep block.

bryan-minimal and others added 2 commits October 21, 2026 14:03
Review follow-ups (mitodrummer):
- AGENTS.md: replace "One denylist" with the two lists as they are on
  main: distrust.txt (ca-certificates build gate, the Chromium policy
  checker, pkgmgr truststores) and cve-2026-111943-blocked-sha256.txt via
  cve-2026-111943.ncl (the JDK patch, which needs all 48 certificate
  fingerprints). They should converge (gominimal/pkgmgr-rs#1020); until
  then an incident lands in both.
- AGENTS.md: Go's crypto/x509 and openssl-probe probe /etc/ssl/certs on
  their own; only OpenSSL and what links it go through openssldir.
- ruby trust_store test: the 100-root floor counts live roots, so a wrong
  sandbox clock cannot make the test verify nothing.
- atuin, gws, ruby, skopeo, typst: ca-certificates after glibc in
  runtime_deps, and its import in the runtime-dep import block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bryan-minimal
Copy link
Copy Markdown
Member Author

Thanks @mitodrummer. Addressed in 6acc40e, plus a merge of main (c98d29f).

Should fix (doc)

  • "One denylist". Replaced with "Denylists: two today, meant to be one". It describes both files as they are on main:

    It says they should converge (gominimal/pkgmgr-rs#1020) and that until then an incident has to land in both. The "when a CA is distrusted" step says the same. The section no longer claims one list.

Nits

Spec-hash impact of the reorder (MINIMAL_SCIENCE_MODE=1 mip dump --packages, amd64 and arm64):

  • Order is hashed. Compared with the previous head, the reorder changed the hashes of the same 11 packages the PR already rehashes (atuin, bun, dev-language-servers, gws, hunk, opencode, plannotator, ruby, ruby-lsp, skopeo, typst).
  • Compared with main (a19e4dc4), the rehash set is still exactly those 11 on both arches. So the reorder adds no rebuilds beyond what the PR already causes. It does invalidate the previous head's build.

minimal build for c98d29f is queued behind a world rebuild on the build-bot. I'll add the result when it lands.

🤖 Generated with Claude Code

@bryan-minimal
bryan-minimal added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 2900803 Oct 22, 2026
12 checks passed
@bryan-minimal
bryan-minimal deleted the trust-store-runtime-deps branch October 22, 2026 00:18
bryan-minimal added a commit that referenced this pull request Oct 22, 2026
…um 155.0.8059.84)

One conflicted file:

- packages/ruby/build.ncl: both sides added a runtime dep after glibc and a
  standalone test after `eval`. Kept both. runtime_deps is glibc,
  ca-certificates (main's #957 placement, "after glibc", with its comment),
  then this branch's glibc-locale-c-utf8. Tests keep this branch's
  utf8_default_external and main's trust_store, in that order; the import
  block already had both imports from the auto-merge.

Everything else merged cleanly. chromedriver-bin and the ruby-3.x siblings
are brought in line with main in the commits that follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bryan-minimal added a commit that referenced this pull request Oct 22, 2026
…in's trust store and build cleanup

- chromedriver-bin: 155.0.8059.39 -> 155.0.8059.84, matching main's
  chromium-headless-shell-bin (#894). The version is no longer a literal:
  it is chromium-headless-shell-bin.attrs.upstream_version, the same shape
  as glibc-locale-c-utf8 and rust-arm-embedded. A browser bump now changes
  the driver's URLs, so the driver's build fails on its sha256s until they
  move in the same change, instead of shipping a driver that refuses the
  browser. matches_browser therefore checks the driver binary, the browser
  binary and the browser package's declared version against each other.
  CfT zips: linux64 4fe24330..., linux-arm64 738853f0...; both downloads'
  MD5s match the GCS object x-goog-hash, and both binaries carry the
  155.0.8059.84 version string and the BSD-3-Clause LICENSE.chromedriver.
- ruby-3.2/3.3/3.4: the drop-in siblings of `ruby` take what main gave it.
  From #957: ca-certificates in runtime_deps (after glibc) plus the
  trust_store test, per AGENTS.md "Declare the store when you read it".
  From #875: delete mkmf.log / gem_make.out after install, since they
  record gcc's random temp names and break reproducibility.

Co-Authored-By: Claude Opus 5.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.

2 participants

0