On November 5, Alpine Linux published an urgent advisory: a password authentication bypass in OpenSSH, tracked as CVE-2026-123158, CVSS 8.1 High. An attacker who can reach your SSH port can log in without knowing any password — send a 257–1024 byte password, and musl's crypt() returns "*", matching the stored "*" field of an account that has no password set. OpenSSH shipped 10.6p2 the same day as a critical workaround. By November 6, watchTowr reported active exploitation attempts, driven by automated botnets and cryptominers.

The catch: a CVE list cannot tell you whether your images are exposed. The bypass only fires when five conditions hold at once — Alpine with musl, OpenSSH without PAM, password authentication on, a * account, and a $5$/$6$ salt donor somewhere on the box. A scanner that checks the CVE database answers a different question. I built alpinegate to answer the one that matters: which of my images actually run this combination?

What it does

alpinegate checks the five live conditions from the image itself, with zero network and no database wait, and returns one verdict per target: EXPOSED, AT-RISK, SAFE, or UNKNOWN (UNKNOWN is never SAFE).

alpinegate scan fixtures/exposed fixtures/patched 

Targets can be a rootfs directory, a .tar / .tar.gz, a docker-save tarball, or a local Docker image through the daemon socket (read-only). There is also check for a single target and playbook, which prints the first-hour stopgap: close password logins, upgrade and restart the daemon, then check whether you were already attacked. The tool never attempts an SSH login, ships no exploit code, and never edits a config or restarts a daemon — the playbook only prints steps.

The demo below runs the real CLI against real fixtures — an Alpine 3.18.9 minirootfs with the real vulnerable openssh 9.3_p2-r3 and the real fixed 9.3_p2-r4:

alpinegate demo

And the scan output, captured from the real CLI:

$ alpinegate scan fixtures/exposed fixtures/patched TARGET ALPINE OPENSSH PAM PASSAUTH `*` ACCOUNTS DONOR VERDICT ---------------- ------ --------- --- -------- ------------ ----- ------- fixtures/exposed 3.18.9 9.3_p2-r3 no yes root $6$ EXPOSED fixtures/patched 3.18.9 9.3_p2-r4 no yes root $6$ SAFE fixtures/exposed: all five conditions hold: Alpine 3.18, openssh 9.3_p2-r3 < fixed 9.3_p2-r4, no PAM, password auth on, * accounts (root) + $6$ salt donor fixtures/patched: openssh 9.3_p2-r4 is at or above the fixed version 9.3_p2-r4 for Alpine 3.18 exit: 1 

Two details worth noting. The fixed-version map is taken from the Alpine advisory itself, per branch from 3.16 to edge, with the source URL and check date recorded in the code — a wrong map here would mean a false SAFE, the worst possible failure, so every branch value is pinned by a unit test. And the tool only ever records the $5$/$6$ hash-type prefix, never a hash — an end-to-end test asserts the real fixture hash is absent from CLI output.

Use it as a CI gate

Scan your images before they ship, and fail the build on EXPOSED:

- run: node dist/cli.js scan ./image.tar --fail-on exposed 

Exit codes are 0 (no EXPOSED), 1 (EXPOSED found, or AT-RISK with --fail-on at-risk), 2 (usage error).

Where rivals beat us

I compared against the closest tools before building, and alpinegate loses on their home turf:

  • Trivy and Grype beat alpinegate on breadth: they cover thousands of CVEs across every package in an image, plus IaC, Kubernetes, secrets, and licenses. alpinegate covers exactly one exposure shape. If you want a full CVE list, use Trivy or Grype.
  • ssh-audit beats alpinegate on SSH policy depth: it audits configuration and policy in detail. It does not detect Alpine/musl, does not check /etc/shadow, and does not inventory a fleet.
  • Lynis / OpenSCAP beat alpinegate on host hardening breadth: full audits across the whole host. They have no sshole-specific condition check and no container inventory.

The wedge: Trivy and Grype are database-driven, so a same-day CVE may not be in their database yet. On the day this CVE landed, a condition checker was not blind in the same way. alpinegate checks the five live conditions from the image itself, with zero network and no database wait, and adds an image inventory plus a first-hour playbook that a CVE list does not give you.

Honest limits

  • Conditions, not exploitability. EXPOSED means the bypass shape is present; it does not prove anyone logged in. Check /var/log/messages for Accepted password entries for that.
  • UNKNOWN is not SAFE. If the tool cannot read /etc/shadow or the package database, it says UNKNOWN — treat it as "go look", not as a pass.
  • Global sshd_config context only. Match blocks are not parsed yet; the README tells you to verify with sshd -T. Match-aware parsing is on the roadmap.
  • Static images cannot tell you the daemon state. Whether sshd is actually running is reported as unknown for image targets; a live-host scan checks /proc.
  • No registry pull, no Kubernetes inventory, no GitHub Action wrapper yet. Those are roadmap items, listed honestly in the README.
  • The fixed-version map is dated. It was checked against the Alpine advisory on 2026-11-06. If Alpine publishes new backports, the map needs a re-check — the code records the check date so you can see it.

Install

Install from source for now (no published npm package yet):

git clone https://github.com/techdevfun/alpinegate.git cd alpinegate npm install npm run build node dist/cli.js --help 

Node 20 or later. Zero runtime dependencies. TypeScript, strict mode. The test suite is 35 tests (25 unit, 10 end-to-end) plus a live smoke test, and CI runs it on Node 20 and 22.

Sources

CVE-2026-123158, CVSS 3.1 8.1 (High). Researcher: Özgün Kültekin (Wakefield); write-up at sshhole.com. Alpine advisory 2026-11-05; OpenSSH 10.6p2 released 2026-11-05. watchTowr active exploitation attempts reported 2026-11-06.

The code is MIT licensed at github.com/techdevfun/alpinegate. If you run Alpine images anywhere, scan them before the weekend — the botnets already started.