Global Security Issue
-
Internet encryption is currently having a nuclear meltdown.
Cryptographers are calling it "bigger than Heartbleed."
A global Certificate Authority just leaked 8 master private keys that allow anyone to impersonate any server on Earth (Google, banks, government sites).
-
We are monitoring that also. The impact is a bit limited and only depends on apps which call out to other servers from a cloudron perspective. So n8n with its outbound calling nodes is a higher risk, but many other apps don't depend on 3rd party services as much which could get MITMd.
Make sure to update your browsers and phone and such though!
-
-
-
@joseph its back up now.
-
This security issue began way back in April 2025 and has been there to exploit since then till Oct 17th, 2026, just a few days ago.
-
https://eliastaylor.com/noexploit/ is an interesting read. I have to admit despite all the frenzy, I haven't seen any code exploit yet. Do you guys happen to have testing something which shows the issue? The way it was publicized, it was as if I can run a simple curl command and run random commands remotely.
-
https://eliastaylor.com/noexploit/ is an interesting read. I have to admit despite all the frenzy, I haven't seen any code exploit yet. Do you guys happen to have testing something which shows the issue? The way it was publicized, it was as if I can run a simple curl command and run random commands remotely.
The two posts below provide emudhra_audit.py, a checker for the October 2026 eMudhra incident. Eight intermediate CA keys in that incident were generated with private values below 256, so anyone can recover them (Mozilla Bugzilla 2083449). Rather than relying on certificate names, the script does three things:
It mathematically recovers any EC key with a tiny private value.
It matches the sixteen compromised certificates by fingerprint.
It flags any trust anchor from which a forged chain could be built.Clients such as curl, OpenSSL, Python, Go and Node do not check revocation by default. They stay exposed until emSign ECC Root CA - G3 is removed from your bundles. Run uv run --python 3.13 --with cryptography emudhra_audit.py --self-test first, then run it on the host. Add --docker to cover every Cloudron app container, and use --host yoursite for your own endpoints. If anything is reported, update ca-certificates and your images, then rescan until it comes back clean.
-- removed some AI code --
-
#!/usr/bin/env python3 """ emudhra_audit.py - audit trust stores, certificate files, containers and live TLS endpoints for exposure to the October 2026 eMudhra weak-key incident (Mozilla Bugzilla 2083449). What it actually checks (instead of just matching names): 1. Weak EC keys, mathematically. Every EC certificate it sees (P-256, P-384, P-521) is tested with baby-step giant-step for a private scalar d where d or n-d is below 2^bits (default 2^24, the eMudhra keys were below 2^8). A hit means the private key has been recovered, and it is printed. This finds the eight eMudhra keys without knowing their names, and finds any future key with the same failure mode. 2. Known-bad certificates by SHA-256: the eight compromised intermediates and the eight Sectigo cross-certificates over the same keys. 3. Trust anchors that can complete a path to the compromised keys (emSign ECC Root CA - G3, emSign Root TLS CA - G3, emSign Root CS CA - G3, and the Sectigo "AAA Certificate Services" cross-signer). An attacker supplies the intermediate in the handshake, so any client that trusts one of these anchors and does not consult revocation data is exposed. 4. Optional proof of a path: given the compromised intermediates as files (or --fetch-ct to download them from crt.sh), it verifies which anchors in each store cryptographically signed them. 5. Live endpoints: fetches the chain a server presents, runs the checks above, and warns when the chain sits under the emSign ECC G3 hierarchy that browsers are removing. Nothing is silently skipped: unreadable files and unparseable PEM blocks are reported. Needs Python 3.11+ and the "cryptography" package; Python 3.13+ is needed to see the full chain a server presents (older versions inspect the leaf only). Tested with Python 3.13 and cryptography 50. Easiest: uv run --python 3.13 --with cryptography emudhra_audit.py Exit status: 0 clean, 1 warnings, 2 critical findings, 3 errors only. """ from __future__ import annotations import argparse import base64 import hashlib import json import math import os import re import shutil import socket import ssl import subprocess import sys import urllib.request import warnings from dataclasses import asdict, dataclass, field from pathlib import Path from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.x509.oid import NameOID # Old roots with non-positive serials are still real roots; do not let a warning hide output. warnings.filterwarnings("ignore", category=UserWarning, module="cryptography") warnings.filterwarnings("ignore", message=".*serial number.*") # -------------------------------------------------------------------------- # Incident data (source: Mozilla Bugzilla 2083449, comments 0 and 2) # -------------------------------------------------------------------------- KNOWN_BAD_SHA256 = { # Issued by emSign Root TLS CA - G3 / emSign Root CS CA - G3 "E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838": "EM EV TLS CA - G3B", "98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B": "EM OV TLS CA - G3B", "42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493": "EM DV TLS CA - G3B", "432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3": "EM EV TLS CA - G3C", "CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F": "EM OV TLS CA - G3C", "0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19": "EM DV TLS CA - G3C", "9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4": "EM OV CS CA - G3B", "4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1": "EM OV CS CA - G3C", # Sectigo (AAA Certificate Services) cross-certificates over the same keys "579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512": "EM EV TLS CA - G3B (Sectigo cross)", "5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699": "EM OV TLS CA - G3B (Sectigo cross)", "45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE": "EM DV TLS CA - G3B (Sectigo cross)", "FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F": "EM EV TLS CA - G3C (Sectigo cross)", "7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489": "EM OV TLS CA - G3C (Sectigo cross)", "62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72": "EM DV TLS CA - G3C (Sectigo cross)", "A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8": "EM OV CS CA - G3B (Sectigo cross)", "84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD": "EM OV CS CA - G3C (Sectigo cross)", } # Anchors from which a path to the compromised keys can be built. EXPOSING_ANCHORS = { "emSign ECC Root CA - G3": ("CRITICAL", "root removed by Mozilla (NSS) and Chrome for this incident"), "emSign Root TLS CA - G3": ("CRITICAL", "directly signed the six compromised TLS intermediates"), "emSign Root CS CA - G3": ("CRITICAL", "directly signed the two compromised code signing intermediates"), "AAA Certificate Services": ("WARN", "cross-signed all eight compromised keys; the cross-certificates are " "revoked, so you are exposed only if your clients ignore revocation"), } # Issuers whose leaf certificates live under emSign ECC Root CA - G3 (Bugzilla 2083449 comment 30). G3_HIERARCHY_ISSUERS = { "emSign Root TLS CA - G3", "InCommon Intermediate CA - OVG3C", "InCommon Intermediate CA - DVG3C", "EM EV TLS CA - G3A-1", "EM OV TLS CA - G3A-1", "emSign ECC SSL CA - G3", "emSign ECC EV SSL CA - G3", "emSign TLS CA - G3", } | {v for v in KNOWN_BAD_SHA256.values() if "cross" not in v} STORE_PATHS = [ "/etc/ssl/certs/ca-certificates.crt", # Debian, Ubuntu, Cloudron host, Alpine "/etc/pki/tls/certs/ca-bundle.crt", # Fedora, RHEL, Bazzite "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", # Fedora, RHEL "/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt", "/etc/ssl/cert.pem", # Alpine, BSD, macOS "/etc/ssl/ca-bundle.pem", # openSUSE "/var/lib/ca-certificates/ca-bundle.pem", # openSUSE "/etc/ca-certificates/extracted/tls-ca-bundle.pem", # Arch ] ENV_VARS = ["SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE", "NODE_EXTRA_CA_CERTS", "GIT_SSL_CAINFO"] CERT_SUFFIXES = {".pem", ".crt", ".cer", ".der", ".cert", ".ca-bundle"} SEVERITY_ORDER = {"CRITICAL": 3, "WARN": 2, "INFO": 1, "ERROR": 0} # -------------------------------------------------------------------------- # Elliptic curve arithmetic for the weak-key search # -------------------------------------------------------------------------- CURVE_PARAMS = { "secp256r1": (2**256 - 2**224 + 2**192 + 2**96 - 1, 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551, ec.SECP256R1), "secp384r1": (2**384 - 2**128 - 2**96 + 2**32 - 1, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973, ec.SECP384R1), "secp521r1": (2**521 - 1, 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409, ec.SECP521R1), } class Curve: """Affine short-Weierstrass arithmetic with a = -3 (all NIST prime curves).""" def __init__(self, name: str): self.name = name self.p, self.n, self.cls = CURVE_PARAMS[name] self.a = self.p - 3 self.G = self.pub_of(1) self._tables: dict[int, dict[int, int]] = {} # Self-test: 2G and (n-1)G from the library must match our arithmetic, # which validates p, a and n before any result is trusted. if self.add(self.G, self.G) != self.pub_of(2) or self.pub_of(self.n - 1) != self.neg(self.G): raise RuntimeError(f"curve self-test failed for {name}") def pub_of(self, d: int): nums = ec.derive_private_key(d, self.cls()).public_key().public_numbers() return (nums.x, nums.y) def neg(self, P): return None if P is None else (P[0], (-P[1]) % self.p) def add(self, P, Q): if P is None: return Q if Q is None: return P p = self.p if P[0] == Q[0]: if (P[1] + Q[1]) % p == 0: return None lam = (3 * P[0] * P[0] + self.a) * pow(2 * P[1], -1, p) % p else: lam = (Q[1] - P[1]) * pow(Q[0] - P[0], -1, p) % p x = (lam * lam - P[0] - Q[0]) % p return (x, (lam * (P[0] - x) - P[1]) % p) def baby_table(self, m: int) -> dict[int, int]: if m not in self._tables: table, P = {}, None for j in range(1, m + 1): P = self.add(P, self.G) table.setdefault(P[0], j) self._tables[m] = table return self._tables[m] def small_dlog(self, Q, bits: int) -> int | None: """Return d with d*G == Q if d or n-d is below roughly 2^bits, else None.""" m = 1 << ((bits + 1) // 2) table = self.baby_table(m) giant = self.neg(self.pub_of(m)) # -(m*G) for target, sign in ((Q, 1), (self.neg(Q), -1)): R = target for i in range(m + 1): if R is None: cands = [i * m] elif R[0] in table: j = table[R[0]] cands = [i * m + j, i * m - j] else: R = self.add(R, giant) continue for c in cands: d = (sign * c) % self.n if d and self.pub_of(d) == Q: return d R = self.add(R, giant) return None _CURVES: dict[str, Curve] = {} def curve(name: str) -> Curve | None: if name not in CURVE_PARAMS: return None if name not in _CURVES: _CURVES[name] = Curve(name) return _CURVES[name] # -------------------------------------------------------------------------- # Certificate loading (reports problems instead of hiding them) # -------------------------------------------------------------------------- PEM_RE = re.compile(rb"-----BEGIN (TRUSTED )?CERTIFICATE-----(.+?)-----END (?:TRUSTED )?CERTIFICATE-----", re.S) def first_der_tlv(data: bytes) -> bytes: """Slice the first DER element (strips OpenSSL 'TRUSTED CERTIFICATE' aux data).""" if len(data) < 2 or data[0] != 0x30: raise ValueError("not a DER SEQUENCE") length, hdr = data[1], 2 if length & 0x80: nbytes = length & 0x7F length, hdr = int.from_bytes(data[2:2 + nbytes], "big"), 2 + nbytes return data[:hdr + length] def load_certs(blob: bytes) -> tuple[list[x509.Certificate], list[str]]: certs, problems = [], [] blocks = PEM_RE.findall(blob) if not blocks: try: return [x509.load_der_x509_certificate(first_der_tlv(blob))], [] except Exception: return [], [] # not a certificate file at all; caller decides whether that matters for idx, (trusted, body) in enumerate(blocks, 1): try: der = base64.b64decode(b"".join(body.split())) certs.append(x509.load_der_x509_certificate(first_der_tlv(der) if trusted else der)) except Exception as exc: # noqa: BLE001 - every failure is reported problems.append(f"PEM block {idx}: {type(exc).__name__}: {exc}") return certs, problems def cn(name: x509.Name) -> str: attrs = name.get_attributes_for_oid(NameOID.COMMON_NAME) return str(attrs[0].value) if attrs else name.rfc4514_string() def sha256_fp(cert: x509.Certificate) -> str: return cert.fingerprint(hashes.SHA256()).hex().upper() def spki_sha256(cert: x509.Certificate) -> str: spki = cert.public_key().public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo) return base64.b64encode(hashlib.sha256(spki).digest()).decode() # -------------------------------------------------------------------------- # Findings # -------------------------------------------------------------------------- @dataclass class Finding: severity: str source: str message: str subject: str = "" sha256: str = "" detail: dict = field(default_factory=dict) class Audit: def __init__(self, bits: int, verbose: bool): self.bits, self.verbose = bits, verbose self.findings: list[Finding] = [] self.stats = {"sources": 0, "certificates": 0, "ec_keys_tested": 0} self._dlog_cache: dict[str, int | None] = {} self.stores: dict[str, list[x509.Certificate]] = {} def add(self, *a, **kw): self.findings.append(Finding(*a, **kw)) def weak_key(self, cert: x509.Certificate) -> int | None: key = cert.public_key() if not isinstance(key, ec.EllipticCurvePublicKey): return None c = curve(key.curve.name) if c is None: return None nums = key.public_numbers() cache_key = f"{key.curve.name}:{nums.x:x}" if cache_key not in self._dlog_cache: self.stats["ec_keys_tested"] += 1 self._dlog_cache[cache_key] = c.small_dlog((nums.x, nums.y), self.bits) return self._dlog_cache[cache_key] def check_cert(self, cert: x509.Certificate, source: str, role: str): self.stats["certificates"] += 1 fp, subj = sha256_fp(cert), cn(cert.subject) d = self.weak_key(cert) if d is not None: n = curve(cert.public_key().curve.name).n shown = str(d) if d < n // 2 else f"n - {n - d}" self.add("CRITICAL", source, f"private key RECOVERED (d = {shown}); anyone can sign with this key", subj, fp, {"curve": cert.public_key().curve.name, "private_scalar_hex": format(d, "x"), "spki_sha256_b64": spki_sha256(cert), "is_ca": is_ca(cert)}) if fp in KNOWN_BAD_SHA256: self.add("CRITICAL", source, f"known compromised certificate present: {KNOWN_BAD_SHA256[fp]}", subj, fp) if role == "store" and subj in EXPOSING_ANCHORS and cert.subject == cert.issuer: sev, why = EXPOSING_ANCHORS[subj] self.add(sev, source, f"trust anchor present: {subj} ({why})", subj, fp, {"serial": format(cert.serial_number, "x")}) elif role == "store" and subj in EXPOSING_ANCHORS: sev, why = EXPOSING_ANCHORS[subj] self.add(sev, source, f"certificate for {subj} present in store (not self-signed; {why})", subj, fp) elif self.verbose and re.search(r"emsign|emudhra", cert.subject.rfc4514_string(), re.I): self.add("INFO", source, "other eMudhra/emSign certificate (not implicated by this incident)", subj, fp) # ---- sources ---- def scan_blob(self, blob: bytes, source: str, role: str = "store", quiet_nonpem: bool = False): certs, problems = load_certs(blob) if not certs and not problems: if not quiet_nonpem: self.add("ERROR", source, "no certificates found in this file") return self.stats["sources"] += 1 for p in problems: self.add("ERROR", source, f"could not parse {p}") for c in certs: self.check_cert(c, source, role) if role == "store": self.stores[source] = certs def scan_path(self, path: Path, role: str = "store"): try: if path.is_dir(): for f in sorted(path.rglob("*")): if f.is_file() and (f.suffix.lower() in CERT_SUFFIXES or f.suffix == "" or f.suffix == ".0"): self.scan_path_file(f, role, quiet_nonpem=True) else: self.scan_path_file(path, role) except OSError as exc: self.add("ERROR", str(path), f"unreadable: {exc}") def scan_path_file(self, f: Path, role: str, quiet_nonpem: bool = False): try: if f.stat().st_size > 50_000_000: return self.scan_blob(f.read_bytes(), str(f), role, quiet_nonpem) except OSError as exc: self.add("ERROR", str(f), f"unreadable: {exc}") def scan_container(self, engine: str, name: str): found = False for p in STORE_PATHS: r = subprocess.run([engine, "exec", name, "cat", p], capture_output=True, timeout=60) if r.returncode == 0 and r.stdout: found = True self.scan_blob(r.stdout, f"{engine}:{name}:{p}", "store") if not found: self.add("ERROR", f"{engine}:{name}", "no known trust bundle path readable in container") def scan_host(self, target: str, timeout: float): m = re.fullmatch(r"\[?([^\[\]]+?)\]?(?::(\d+))?", target) host, port = (m.group(1), int(m.group(2) or 443)) if m else (target, 443) src = f"tls://{host}:{port}" ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx.check_hostname, ctx.verify_mode = False, ssl.CERT_NONE try: with socket.create_connection((host, port), timeout=timeout) as raw, \ ctx.wrap_socket(raw, server_hostname=host) as s: chain = s.get_unverified_chain() if hasattr(s, "get_unverified_chain") else None ders = [c if isinstance(c, bytes) else c.public_bytes(ssl.ENCODING_DER) for c in chain] if chain \ else [s.getpeercert(binary_form=True)] except (OSError, ssl.SSLError) as exc: self.add("ERROR", src, f"connection failed: {exc}") return self.stats["sources"] += 1 certs = [x509.load_der_x509_certificate(d) for d in ders] if chain is None: self.add("INFO", src, "Python < 3.13: only the leaf certificate could be inspected") for c in certs: self.check_cert(c, src, "chain") issuers = {cn(c.issuer) for c in certs} | {cn(c.subject) for c in certs[1:]} hit = sorted(issuers & G3_HIERARCHY_ISSUERS) if hit: self.add("WARN", src, "served chain sits under emSign ECC Root CA - G3 (" + ", ".join(hit) + "); replace this certificate, Chrome removes the root on 27 October 2026", cn(certs[0].subject), sha256_fp(certs[0])) try: vctx = ssl.create_default_context() with socket.create_connection((host, port), timeout=timeout) as raw, \ vctx.wrap_socket(raw, server_hostname=host): pass self.add("INFO", src, "chain is accepted by this machine's default Python/OpenSSL trust store") except ssl.SSLCertVerificationError as exc: self.add("INFO", src, f"chain is rejected by this machine's default trust store: {exc.verify_message}") except (OSError, ssl.SSLError): pass def path_check(self, intermediates: list[x509.Certificate]): """Cryptographically confirm which store anchors signed the compromised intermediates.""" for store, anchors in self.stores.items(): for inter in intermediates: for anchor in anchors: if anchor.subject != inter.issuer: continue try: inter.verify_directly_issued_by(anchor) except Exception: continue self.add("CRITICAL", store, f"anchor '{cn(anchor.subject)}' validly signs compromised '{cn(inter.subject)}': " "a forged chain verifies against this store unless revocation is enforced", cn(anchor.subject), sha256_fp(anchor), {"intermediate_sha256": sha256_fp(inter)}) def is_ca(cert: x509.Certificate) -> bool: try: return cert.extensions.get_extension_for_class(x509.BasicConstraints).value.ca except x509.ExtensionNotFound: return False # -------------------------------------------------------------------------- # Discovery helpers # -------------------------------------------------------------------------- def default_store_paths() -> list[Path]: cands = list(STORE_PATHS) dvp = ssl.get_default_verify_paths() cands += [x for x in (dvp.cafile, dvp.openssl_cafile) if x] cands += [os.environ[v] for v in ENV_VARS if os.environ.get(v)] try: import certifi # type: ignore cands.append(certifi.where()) except ImportError: pass seen, out = set(), [] for c in cands: p = Path(c) if p.is_file() and (rp := p.resolve()) not in seen: seen.add(rp) out.append(p) return out def fetch_ct(cache: Path) -> list[x509.Certificate]: cache.mkdir(parents=True, exist_ok=True) out = [] for fp, label in KNOWN_BAD_SHA256.items(): f = cache / f"{fp}.pem" if not f.exists(): req = urllib.request.Request(f"https://crt.sh/?d={fp}", headers={"User-Agent": "emudhra_audit"}) try: with urllib.request.urlopen(req, timeout=60) as r: f.write_bytes(r.read()) except OSError as exc: print(f"[ERROR] could not fetch {label} from crt.sh: {exc}", file=sys.stderr) continue certs, _ = load_certs(f.read_bytes()) if certs and sha256_fp(certs[0]) == fp: out.append(certs[0]) else: print(f"[ERROR] {f} does not contain the expected certificate {fp}", file=sys.stderr) return out def self_test(bits: int) -> int: print(f"Self-test: generating keys with tiny private scalars and recovering them (bits={bits})") ok = True for name in CURVE_PARAMS: c = curve(name) for d in (4, 155, 237, (1 << (bits - 1)) + 12345, c.n - 56): got = c.small_dlog(c.pub_of(d), bits) ok &= got == d print(f" {name:10} d={d if d < c.n // 2 else 'n-' + str(c.n - d):>12} recovered={got == d}") strong = ec.generate_private_key(c.cls()).public_key().public_numbers() miss = c.small_dlog((strong.x, strong.y), bits) is None ok &= miss print(f" {name:10} random key correctly NOT recovered={miss}") print("SELF-TEST PASSED" if ok else "SELF-TEST FAILED") return 0 if ok else 2 # -------------------------------------------------------------------------- # CLI # -------------------------------------------------------------------------- def main() -> int: ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0], formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("paths", nargs="*", help="bundle files or directories to scan (default: discover system stores)") ap.add_argument("--host", action="append", default=[], metavar="HOST[:PORT]", help="inspect a live TLS endpoint") ap.add_argument("--docker", nargs="*", metavar="CONTAINER", help="scan trust bundles inside containers (all running if none named)") ap.add_argument("--engine", default="docker", help="container engine binary (docker or podman)") ap.add_argument("--intermediates", nargs="*", default=[], metavar="FILE", help="compromised intermediates to path-check against stores") ap.add_argument("--fetch-ct", action="store_true", help="download the 16 compromised certificates from crt.sh for the path check") ap.add_argument("--bits", type=int, default=24, help="weak-key search bound, 2^bits (default 24; 32 is thorough but slower)") ap.add_argument("--timeout", type=float, default=10.0) ap.add_argument("--json", action="store_true", help="machine-readable output") ap.add_argument("-v", "--verbose", action="store_true", help="also list unrelated eMudhra/emSign certificates") ap.add_argument("--self-test", action="store_true", help="prove the weak-key detector works, then exit") args = ap.parse_args() if args.self_test: return self_test(args.bits) audit = Audit(args.bits, args.verbose) explicit = bool(args.paths or args.host or args.docker is not None) for p in args.paths or ([] if explicit else default_store_paths()): audit.scan_path(Path(p)) if args.docker is not None: if not shutil.which(args.engine): audit.add("ERROR", args.engine, "container engine not found") else: names = args.docker if not names: r = subprocess.run([args.engine, "ps", "--format", "{{.Names}}"], capture_output=True, text=True) if r.returncode != 0: audit.add("ERROR", args.engine, f"could not list containers: {r.stderr.strip()}") names = r.stdout.split() if r.returncode == 0 and not names: audit.add("ERROR", args.engine, "no running containers found") for n in names: audit.scan_container(args.engine, n) for h in args.host: audit.scan_host(h, args.timeout) inters: list[x509.Certificate] = [] for f in args.intermediates: certs, _ = load_certs(Path(f).read_bytes()) inters += certs if args.fetch_ct: inters += fetch_ct(Path.home() / ".cache" / "emudhra_audit") for c in inters: audit.check_cert(c, "supplied-intermediates", "chain") if inters: audit.path_check(inters) findings = sorted(audit.findings, key=lambda f: -SEVERITY_ORDER[f.severity]) if args.json: print(json.dumps({"stats": audit.stats, "findings": [asdict(f) for f in findings]}, indent=2)) else: for f in findings: print(f"[{f.severity}] {f.source}\n {f.message}") if f.subject: print(f" subject: {f.subject}") if f.sha256: print(f" sha256: {f.sha256}") for k, v in f.detail.items(): print(f" {k}: {v}") s = audit.stats print(f"\nScanned {s['sources']} sources, {s['certificates']} certificates, " f"{s['ec_keys_tested']} distinct EC keys tested for d < 2^{args.bits} (and n - d).") if not s["sources"]: print("Nothing was scanned. Pass a bundle path, --host or --docker.") sev = {f.severity for f in findings} if not audit.stats["sources"]: return 3 if "CRITICAL" in sev: return 2 if "WARN" in sev: return 1 return 3 if "ERROR" in sev else 0 if __name__ == "__main__": sys.exit(main()) -
https://eliastaylor.com/noexploit/ is an interesting read. I have to admit despite all the frenzy, I haven't seen any code exploit yet. Do you guys happen to have testing something which shows the issue? The way it was publicized, it was as if I can run a simple curl command and run random commands remotely.
@girish
Weird blog to be honest. Only two posts with the first one telling us LLMs will hack everything on the internet and the second one ranting about how LLMs didn't find an easy hack but deceived us.
I am not skilled enough of doing any testing myself but I am thankful for your overtime hours and acting as fast as you did. If it happens to be not as bad as everyone feared, we can happily move on.As for me, I took it as an urgent reason to finally switch a legacy website. Working under pressure gets things done faster.

Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login
