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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ people's projects) and drives tools that install software, so:
- It's signed with the hardened runtime, so injected `DYLD_*` libraries are ignored, and the projects
folder must be inside your home folder (not `~/Library`).
- **The dashboard** escapes everything it shows and only links advisory references that use `http(s)`.
- Registry and OSV requests must be `https://`. Notifications pass package names to `osascript` as
- Registry and OSV requests must be `https://`, and don't trust eMudhra's (emSign) CA roots, whose
intermediate keys turned out to be guessable in October 2026, or Sectigo's AAA root, which cross-signed them. Notifications pass package names to `osascript` as
arguments, never as script source.
- The Socket.dev token lives only in the macOS Keychain.

Expand Down
53 changes: 51 additions & 2 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""
import calendar
import concurrent.futures
import functools
import getpass
import hashlib
import json
Expand All @@ -17,6 +18,7 @@
import shlex
import shutil
import socket
import ssl
import subprocess
import tarfile
import tempfile
Expand Down Expand Up @@ -167,14 +169,61 @@ def loads(text, default):
return default


# eMudhra's (emSign) roots are left out of what the registry, OSV and Socket requests trust: in October 2026 the keys of
# 8 of their intermediate CAs turned out to be guessable. Browsers block those intermediates, but macOS's
# /etc/ssl/cert.pem still trusts the roots above them, and Sectigo's AAA root, which cross-signed twins of them.
# Dropped by SHA-256, or if the root names eMudhra at all.
DISTRUSTED_ROOTS = {
"40f6af0346a99aa1cd1d555a4e9cce62c7f9634603ee406615833dc8c8d00367", # emSign Root CA - G1
"86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b", # emSign ECC Root CA - G3
"125609aa301da0a249b97a8239cb6a34216f44dcac9f3954b14292f2e8c8608f", # emSign Root CA - C1
"bc4d809b15189d78db3e1d8cf4f9726a795da1643ca5f1358e1ddb0edc0d7eb3", # emSign ECC Root CA - C3
# Sectigo's legacy AAA root: it cross-signed twins of the 8 compromised intermediates. Browsers and certifi
# stopped trusting it for websites in 2025, but macOS's /etc/ssl/cert.pem still does.
"d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef4", # AAA Certificate Services (Comodo)
}
PEM_RE = re.compile(r"-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----", re.DOTALL)


def distrusted(pem):
der = ssl.PEM_cert_to_DER_cert(pem)
return hashlib.sha256(der).hexdigest() in DISTRUSTED_ROOTS or b"eMudhra" in der


def ca_bundle():
"""The roots Python trusts by default, as PEM: $SSL_CERT_FILE, else its default file, else its default folder."""
paths = ssl.get_default_verify_paths()
cafile = os.environ.get(paths.openssl_cafile_env) or paths.cafile
if cafile:
files = [cafile]
elif paths.capath and os.path.isdir(paths.capath):
files = [os.path.join(paths.capath, n) for n in sorted(os.listdir(paths.capath))]
else:
files = []
out = []
for f in files:
try:
with open(f, encoding="utf-8", errors="replace") as fh: # labels in comments can be non-ASCII
out.append(fh.read())
except (OSError, ValueError):
pass
return "\n".join(out)


@functools.lru_cache(maxsize=None)
def tls_context():
pems = list(dict.fromkeys(p for p in PEM_RE.findall(ca_bundle()) if not distrusted(p))) # a folder repeats them
return ssl.create_default_context(cadata="\n".join(pems)) if pems else ssl.create_default_context()


def http_json(url, body=None, timeout=30, headers=None):
if not url.startswith("https://"): # registries and OSV only: never file: or plain http
raise ValueError("not an https URL: " + url[:80])
req = urllib.request.Request( # noqa: S310 - https enforced above
url, data=json.dumps(body).encode() if body is not None else None,
headers={"User-Agent": "pkgdash/1.0 (local dashboard)",
"Content-Type": "application/json", **(headers or {})})
with urllib.request.urlopen(req, timeout=timeout) as r: # noqa: S310
with urllib.request.urlopen(req, timeout=timeout, context=tls_context()) as r: # noqa: S310
return json.loads(r.read())


Expand Down Expand Up @@ -595,7 +644,7 @@ def socket_api(token, path, body=None, timeout=30):
"https://api.socket.dev/v0/" + path, data=json.dumps(body).encode() if body is not None else None,
headers={"Authorization": "Bearer " + token, "User-Agent": "pkgdash/1.0 (local dashboard)",
"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=timeout) as r: # noqa: S310 - fixed https://api.socket.dev/ prefix
with urllib.request.urlopen(req, timeout=timeout, context=tls_context()) as r: # noqa: S310 - fixed https://api.socket.dev/ prefix
return r.read().decode()


Expand Down
36 changes: 36 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import os
import re
import shutil
import ssl
import subprocess
import sys
import tempfile
Expand Down Expand Up @@ -318,6 +319,41 @@ def test_advisory_ids_are_validated(self):
self.assertEqual(self.req("GET", "/api/advisory?id=" + urllib.request.quote(bad), token=False)[0], 400, bad)


class Trust(unittest.TestCase):
"""Requests to registries, OSV and Socket trust the default roots minus eMudhra's."""

def test_context_has_roots_but_no_emudhra(self):
server.tls_context.cache_clear()
ctx = server.tls_context()
names = [str(c.get("subject")) for c in ctx.get_ca_certs()]
self.assertGreater(len(names), 100)
self.assertEqual([n for n in names if "eMudhra" in n or "emSign" in n or "AAA Certificate" in n], [])
self.assertTrue(ctx.check_hostname)

def test_only_emudhra_roots_are_dropped(self):
pems = server.PEM_RE.findall(server.ca_bundle())
dropped = [p for p in pems if server.distrusted(p)]
if not dropped:
self.skipTest("this machine's default roots have no eMudhra root")
for pem in dropped:
der = ssl.PEM_cert_to_DER_cert(pem)
self.assertTrue(b"emSign" in der or b"AAA Certificate" in der)
self.assertGreater(len(pems) - len(dropped), 100)

def test_ssl_cert_file_is_honoured_and_filtered(self):
pems = server.PEM_RE.findall(server.ca_bundle())
bundle = os.path.join(UNIT_HOME, "corp.pem")
with open(bundle, "w") as f:
f.write("\n".join([next(p for p in pems if not server.distrusted(p))] + [p for p in pems if server.distrusted(p)]))
os.environ["SSL_CERT_FILE"] = bundle
server.tls_context.cache_clear()
try:
self.assertEqual(len(server.tls_context().get_ca_certs()), 1)
finally:
del os.environ["SSL_CERT_FILE"]
server.tls_context.cache_clear()


class Demo(unittest.TestCase):
def test_demo_serves_only_made_up_data(self):
home = tempfile.mkdtemp(prefix="pkgdash-demo-test-")
Expand Down
Loading