8000 SECURITY: httpie: hash-lock the dependency closure with certifi 2026.10.19 (CVE-2026-111943) by bryan-minimal · Pull Request #952 · gominimal/pkgs · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions packages/httpie/build.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@ let uv = import "../uv/build.ncl" in
# common cases but not a drop-in: different flag surface, no sessions, no
# plugins. Both can coexist.)
#
# `needs` is set because pip resolves the dependency tree from PyPI at build
# time. That is the trade for not vendoring 12 packages: the build reaches the
# network, and the lockfile is upstream's own metadata rather than ours.
# The dependency closure is pinned in requirements.lock (version + sha256 for
# every distribution), which build.sh installs with --require-hashes. It used to
# be resolved from PyPI at build time with no pins, so the shipped tree, and
# certifi's CA bundle in particular, was whatever PyPI served that day. The lock
# also holds certifi at or above 2026.10.19, the first release without emSign ECC
# Root CA - G3 (CVE-2026-111943).
#
# `needs` is still set: pip fetches the locked wheels, and httpie's build
# backend, from PyPI during the build.
let version = "3.2.4" in

{
name = "httpie",
build_deps = [
{ file = "build.sh" } | Local,
{ file = "requirements.lock" } | Local,
{
url = "https://github.com/httpie/httpie/archive/refs/tags/%{version}.tar.gz",
sha256 = "b185cd8d81325f97c773582e50f1c5e047c2d8575b53d676469c9daf2a52f341",
Expand Down Expand Up @@ -86,5 +93,41 @@ let version = "3.2.4" in
["/bin/bash", "-c", "/bin/http --help >/dev/null"],
],
} | Test,

# CVE-2026-111943 regression: the CA bundle requests verifies against must
# not trust emSign ECC Root CA - G3 (distrusted by Mozilla, bug 2083449).
# Checked by SHA-256 fingerprint, offline. FAILS with certifi <= 2026.07.22.
certifi_distrusts_emsign_ecc_g3 =
{
class = 'Standalone,
test_deps = [base],
cmds = [
[
"/bin/bash",
"-c",
m%"
cat > poc.py <<'PYEOF'
import hashlib, re, ssl, sys
import requests.certs

G3 = '86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b'
pem = open(requests.certs.where()).read()
fps = {hashlib.sha256(ssl.PEM_cert_to_DER_cert(c)).hexdigest()
for c in re.findall(r'-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----',
pem, re.S)}
failures = []
if len(fps) < 100:
failures.append('certifi bundle has only %d roots' % len(fps))
if G3 in fps:
failures.append('certifi trusts emSign ECC Root CA - G3')
for f in failures:
print('FAIL: ' + f, file=sys.stderr)
sys.exit(1 if failures else 0)
PYEOF
python3 poc.py
"%
],
],
} | Test,
},
} | BuildSpec
42 changes: 36 additions & 6 deletions packages/httpie/build.sh
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
#!/bin/sh
set -ex

# Exactly diffoscope's invocation — let pip resolve and install the dependency
# tree into this package's own site-packages. Deliberately NOT
# --no-build-isolation: httpie's build backend is not present in the sandbox,
# so suppressing isolation makes pip fail while preparing the distribution
# metadata rather than letting it fetch the backend it needs.
pip3 install --root "$OUTPUT_DIR" .
# Install httpie's dependency closure from requirements.lock: every distribution
# pinned to one version and its sha256, wheels only, no resolver. Before this,
# build.sh let pip resolve the tree from PyPI at build time, so the shipped
# dependencies (certifi's CA bundle among them) were whatever PyPI served that
# day: not reproducible, and the CA bundle could not be held at a fixed release.
# See requirements.lock for the certifi security floor (CVE-2026-111943) and for
# how to regenerate it.
pip3 install --root "$OUTPUT_DIR" --require-hashes --no-deps --only-binary :all: -r requirements.lock

# httpie itself, from the pinned source tarball, with --no-deps so nothing
# outside the lock can enter. Deliberately NOT --no-build-isolation: httpie's
# build backend is not present in the sandbox, so suppressing isolation makes pip
# fail while preparing the distribution metadata rather than letting it fetch the
# backend it needs. The backend only builds httpie's own wheel; it does not ship.
pip3 install --root "$OUTPUT_DIR" --no-deps .

# Fail closed: a pip that "succeeded" without installing the console scripts
# would otherwise ship a package whose only symptom is a missing command.
if [ ! -x "$OUTPUT_DIR/usr/bin/http" ]; then
echo "httpie: pip did not install the 'http' entrypoint" >&2
exit 1
fi

# With --no-deps, a dependency missing from the lock would install cleanly and
# break at import time. `pip check` over the installed tree (plus the python
# runtime dep's own pip, which satisfies httpie's `pip` requirement) turns that
# into a build failure.
SITE="$(ls -d "$OUTPUT_DIR"/usr/lib/python3.*/site-packages)"
PYTHONPATH="$SITE" python3 -m pip check

# CVE-2026-111943: requests verifies TLS against certifi's bundle, so that
# bundle must not trust emSign ECC Root CA - G3 (distrusted by Mozilla after
# eMudhra's intermediate key compromise, bug 2083449). The needle is one line of
# that root's base64 PEM body (its public key).
CACERT="$SITE/certifi/cacert.pem"
[ -s "$CACERT" ] || {
echo "httpie: no certifi CA bundle at $CACERT" >&2
exit 1
}
if grep -q 'djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0' "$CACERT"; then
echo "httpie: certifi still trusts emSign ECC Root CA - G3 (CVE-2026-111943)" >&2
exit 1
fi
Loading
Loading
0