CVE-2026-105221: gist RubyGem VERIFY_NONE leaks GitHub tokens
gist RubyGem before 6.1.0 sets OpenSSL VERIFY_NONE on HTTPS to GitHub. An on-path attacker can present any certificate, read the OAuth token the CLI sends, and then read or change the victim's gists. Labbed 6.0.0 against 6.1.0 on loopback. No public PoC existed.
- Name
- CVE-2026-105221: gist RubyGem VERIFY_NONE leaks GitHub tokens
- Type
- N-day analysis
- CVE
- CVE-2026-105221
- CVE Risk
- high
- Disclosure Status
- public
- Vendor
- defunkt
- Affected
- gist RubyGem 4.0.0 through 6.0.0. Lab pin 6.0.0. Fixed in 6.1.0 (07ccc1a). On-path MITM of the gist CLI.
- Published
- 05 Oct 2026
- Updated
- 05 Oct 2026
- Tags
- n-day, gist, ruby, tls, mitm, cwe-295
gist hung up on github with the cert check off
The proof of concept is on GitHub: abraxas/cve-2026-105221-gist-tls (loopback client; @abraxas_null). The lab stack is lab/: Dockerfile, docker-compose.yml, run.sh, poc.py. Authorized lab only. It talks to loopback.
This is CVE-2026-105221 in the gist RubyGem. defunkt. CWE-295. 7.4 High (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N). VulnCheck's 4.0 score is 9.1. On-path MITM of the CLI. Patched in 6.1.0 (07ccc1a). Siyang Wu found it. Issue 373 / PR 374. I labbed the public CVE. There was no public PoC.
gist is the old command-line client for gist.github.com. You gist --login, it stores an OAuth token in ~/.gist, then every upload and list call hits the GitHub API over HTTPS. Encryption was on. The peer check was not.
What an attacker can do
The attacker sits on the path and takes the GitHub token.
- Present any TLS certificate. gist 6.0.0 sets
verify_mode = OpenSSL::SSL::VERIFY_NONE. A self-signed cert withCN=mitm.invalidis enough. The CLI keeps talking. - Read
Authorization: token .... Upload, list, delete, and login all go throughhttp_connection. The token in~/.gistrides that socket in the clear to whoever terminated TLS. - Read and edit the victim's gists. That token is a GitHub OAuth credential with the
gistscope. Private gists. Updates. Deletes. - Catch a login in flight. Device-flow and username/password login use the same unverified TLS. The attacker can take the fresh token before it ever hits disk.
They need a network position: hostile Wi-Fi, a proxy, a box in the middle. This is not unauthenticated remote RCE against a server. It is the gist CLI trusting whoever answered.
6.1.0 sets VERIFY_PEER. The same bogus cert dies with certificate verify failed (self-signed certificate) and the token never leaves the client.
How I found it
The CVE text names http_connection in lib/gist.rb. I pinned gem 6.0.0 next to 6.1.0 and read the one-line flip.
lib/gist.rb on 6.0.0:
if uri.scheme == "https"
connection.use_ssl = true
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
end6.1.0 is VERIFY_PEER. ConradIrwin's note on the PR: Ruby's certificate story was bad when this was written, and it had not been revisited.
I did not intercept real api.github.com. The lab is a fake GitHub Enterprise HTTPS stub on loopback with a self-signed cert. GITHUB_URL=https://127.0.0.1:8443 puts gist in GHE mode, so it POSTs /api/v3/gists. A lab token CVE-2026-105221-WITNESS sits in the matching ~/.gist.* file. gist 6.0.0 completes TLS. The stub captures Authorization: token CVE-2026-105221-WITNESS and writes the witness. gist 6.1.0 against the same stub fails TLS. Capture count on 6.1.0 stays zero.
SUCCESS CVE-2026-105221 vuln-tls=ok token-captured=1 patched-tls=fail witness-written=yes CVE-2026-105221-WITNESSWrong turns already recorded: none. First run.sh printed SUCCESS. A reverse shell. Theatre. The oracle is the captured lab token on 6.0.0 and the self-signed TLS error on 6.1.0.
VERIFY_NONE is a peer, not a lock
The gem still spoke TLS. Wireshark on the wire would see ciphertext. The person who terminated that TLS, with any cert they minted, would see the token.
Upgrade to 6.1.0 or later.
I am @abraxas_null. Site abraxaslabs.tech. GitHub abraxas. Mail abraxas.null@proton.me.