Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package sshamble for openSUSE:Factory 
checked in at 2025-09-22 16:39:33
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/sshamble (Old)
 and      /work/SRC/openSUSE:Factory/.sshamble.new.27445 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "sshamble"

Mon Sep 22 16:39:33 2025 rev:3 rq:1306288 version:0.3.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/sshamble/sshamble.changes        2024-10-04 
17:11:12.334494808 +0200
+++ /work/SRC/openSUSE:Factory/.sshamble.new.27445/sshamble.changes     
2025-09-22 16:40:17.010933473 +0200
@@ -1,0 +2,31 @@
+Fri Sep 12 17:48:01 UTC 2025 - Martin Hauke <[email protected]>
+
+- Update to version 0.3.3
+  This release improves interact handling and introduces two new
+  command-line options:
+  * --one-session-only This boolean option instructs sshamble to
+    skip additional checks after the first successful session for
+    that target. This is helpful for devices where many methods
+    bypass authentication.
+  * --session-poke This string option defines what bytes to send
+    into new sessions to elicit responses. This field can be plain
+    ascii or escaped hex strings. For exampleshow
+    version\r\n\x0a\x0d will send a show version command followed
+    by two CRLFs. Please take into account your local shell
+    handling when using this feature (place the argument in
+    single-quotes, etc.).
+  * Clarify that all output is JSONL (NDJSON) and update
+    dependencies.
+  * Update analyzers.
+- Update to version 0.2.1
+  * Dependency updates.
+- Update to version 0.2.0
+  * Switch to https://github.com/runZeroInc/excrypto for forked
+    dependencies.
+  * Automatic badkeys.info blocklist lookups.
+  * Additional authentication bypass methods.
+  * Wider algorithm and host key support.
+  * Experimental blind exec vuln checks.
+  * Target filtering with --skip-versions.
+
+-------------------------------------------------------------------

Old:
----
  sshamble-0.0.5.tar.gz

New:
----
  sshamble-0.3.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ sshamble.spec ++++++
--- /var/tmp/diff_new_pack.sipA0R/_old  2025-09-22 16:40:18.206983726 +0200
+++ /var/tmp/diff_new_pack.sipA0R/_new  2025-09-22 16:40:18.210983894 +0200
@@ -1,8 +1,8 @@
 #
 # spec file for package sshamble
 #
-# Copyright (c) 2024 SUSE LLC
-# Copyright (c) 2024, Martin Hauke <[email protected]>
+# Copyright (c) 2025 SUSE LLC and contributors
+# Copyright (c) 2024-2025, Martin Hauke <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
 
 
 Name:           sshamble
-Version:        0.0.5
+Version:        0.3.3
 Release:        0
 Summary:        Security testing toolset for SSH
 License:        BSD-2-Clause
@@ -55,6 +55,6 @@
 
 %files
 %license LICENSE.md
-%doc README.md README.crypto.md SECURITY.md
+%doc README.md SECURITY.md
 %{_bindir}/sshamble
 

++++++ sshamble-0.0.5.tar.gz -> sshamble-0.3.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/README.crypto.md 
new/sshamble-0.3.3/README.crypto.md
--- old/sshamble-0.0.5/README.crypto.md 2024-09-30 18:50:03.000000000 +0200
+++ new/sshamble-0.3.3/README.crypto.md 1970-01-01 01:00:00.000000000 +0100
@@ -1,19 +0,0 @@
-# SSHamble Patches for x/crypto/ssh
-
-This repository includes a fork of the Go x/crypto package.
-
-To maintain this fork, first rediff against upstream:
-
-$ ./crypto.rediff.sh
-
-This creates a file named crypto.patch.
-
-Now resync with upstream using:
-
-$ ./crypto.resync.sh
-
-This applies the patch on top of upstream.
-
-Review the changes, fix conflicts, and commit the results in ./crypto
-
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/README.md new/sshamble-0.3.3/README.md
--- old/sshamble-0.0.5/README.md        2024-09-30 18:50:03.000000000 +0200
+++ new/sshamble-0.3.3/README.md        2025-08-19 06:31:06.000000000 +0200
@@ -20,7 +20,7 @@
 Binaries are available from the [releases 
page](https://github.com/runZeroInc/sshamble/releases).
 
 
-To build SSHamble from source, ensure that you have a recent version of Go 
(1.22.6+) installed.
+To build SSHamble from source, ensure that you have a recent version of Go 
(1.24+) installed.
 
 You can use Go to install a binary into the `bin` directory in your GOPATH.
 
@@ -42,16 +42,15 @@
 $ ./sshamble -h
 ```
 
-To enable experimental [badkeys](https://badkeys.info) support, run the 
generator first:
+To enable [badkeys](https://badkeys.info) support, run `sshamble 
badkeys-update` first, then scan.
 ```shell
 $ git clone https://github.com/runZeroInc/sshamble
 $ cd sshamble
 $ go generate ./...
 $ go build -o sshamble
-$ ./sshamble -h
+$ ./sshamble badkeys-update
 ```
 
-
 ## Usage
 
 ```console
@@ -72,19 +71,20 @@
 
 Start a network scan using:
 
-$ ./sshamble scan -o results.json 192.168.0.0/24
+$ ./sshamble scan -o results.jsonl 192.168.0.0/24
 
 Analyze the results using:
 
-$ ./sshamble analyze -o results-directory results.json
+$ ./sshamble analyze -o results-directory results.jsonl
 
 Usage:
   sshamble [command]
 
 Available Commands:
-  analyze     Analyzes a scan JSON output file and buckets results
-  help        Help about any command
-  scan        Enumerates a set of targets for SSH capabilities and exposures
+  analyze        Analyzes a scan JSON output file and buckets results
+  badkeys-update Updates the badkeys.info blocklist cache.
+  help           Help about any command
+  scan           Enumerates a set of targets for SSH capabilities and exposures
 
 Flags:
   -h, --help   help for sshamble
@@ -96,15 +96,14 @@
 
 ```console
 $ ./sshamble scan -h
-
 Enumerates a set of targets for SSH capabilities and exposures
 
 Usage:
-  sshamble scan [-p 22] [-u root,admin] [-o scan.json] [-l scan.log] 
[--log-level trace] 192.168.0.0/24 ... [flags]
+  sshamble scan [-p 22] [-u root,admin] [-o scan.jsonl] [-l scan.log] 
[--log-level trace] 192.168.0.0/24 ... [flags]
 
 Flags:
-      --categories string                     The list of categories to 
include. (default "bypass,gssapi,keyboard,password,pubkey,userenum,vuln")
-      --checks string                         The list of checks to run. 
Non-default 
("userenum-none-timing,userenum-password-timing,userenum-pubkey-timing") 
(default 
"gssapi-any,keyboard-any,keyboard-empty,keyboard-null,keyboard-user,password-any,password-change-empty,password-change-null,password-empty,password-null,password-user,pubkey-any,pubkey-bulkhalf,pubkey-hunt,pubkey-user,skip-auth,skip-auth-method-empty,skip-auth-method-null,skip-auth-none,skip-auth-pubkeyany,skip-auth-success,skip-ssh-userauth,vuln-generic-env,vuln-gogs-env,vuln-ruckus-password-escape,vuln-softserve-env,vuln-tcp-forward")
+      --categories string                     The list of categories to 
include. (default 
"bypass,gssapi,hostkey,keyboard,password,pubkey,userenum,vuln")
+      --checks string                         The list of checks to run. 
Non-default 
("userenum-none-timing,userenum-password-timing,userenum-pubkey-timing,vuln-exec-skip-auth,vuln-exec-skip-userauth")
 (default 
"badkeys-blocklist,gssapi-any,keyboard-any,keyboard-empty,keyboard-null,keyboard-user,password-any,password-change-empty,password-change-null,password-empty,password-null,password-user,pubkey-any,pubkey-bulkhalf,pubkey-hunt,pubkey-user,skip-auth,skip-auth-method-empty,skip-auth-method-null,skip-auth-none,skip-auth-pubkeyany,skip-auth-success,skip-ssh-userauth,vuln-generic-env,vuln-gogs-env,vuln-ruckus-password-escape,vuln-softserve-env,vuln-tcp-forward")
       --client-version string                 The client version string to 
send (default "OpenSSH_9.8p1")
       --config string                         config file (default is 
$HOME/.sshamble.json)
   -h, --help                                  help for scan
@@ -114,6 +113,7 @@
   -l, --log string                            The file to write logs to 
(default is stderr) (default "-")
   -L, --log-level string                      The log level to write 
(trace,debug,info,warn,error) (default "info")
   -m, --max-connections uint                  The maximum number of concurrent 
connections (default 5000)
+      --one-session-only                      Only open one session per target
   -o, --output string                         The destination file for JSON 
output (default "stdout")
       --password string                       An optional password to try for 
authentication
       --password-file string                  An optional file with clear-text 
passwords to try for authentication
@@ -125,8 +125,10 @@
       --pubkey-hunt-conn-limit uint           The number of public keys to 
test in each connection (default 250000)
       --pubkey-hunt-file string               The optional file containing 
public keys to hunt
       --retries uint                          The retry count for subsequent 
failed connections after an initial success (default 2)
+      --session-poke string                   A byte sequence sent to sessions 
to elicit further responses (hex or ascii) (default "\\x0a\\x0d\\r\\n")
+      --skip-versions string                  A regular expression of SSH 
versions to skip (ex: '(?i)openssh|dropbear)'
       --timeout uint                          The number of seconds to wait 
for a target to respond (default 5)
-      --userenum-max-per-session-count uint   The maximum number of 
authentication attempts per session (default 1023)
+      --userenum-max-per-session-count uint   The maximum number of 
authentication atempts per session (default 1023)
       --userenum-test-count uint              The number of tests to apply 
during username enumeration (default 2500)
   -u, --users string                          The list of usernames to test on 
each target (comma-separated) (default "root")
 ```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/auth/auth.go 
new/sshamble-0.3.3/auth/auth.go
--- old/sshamble-0.0.5/auth/auth.go     2024-09-30 18:50:03.000000000 +0200
+++ new/sshamble-0.3.3/auth/auth.go     2025-08-19 06:31:06.000000000 +0200
@@ -97,6 +97,8 @@
        defer conn.Close()
 
        res.Stage = "connect"
+       options.Logger.Tracef("%s connection established %v", addr, 
conn.RemoteAddr())
+
        if options.StopStage == res.Stage {
                return res
        }
@@ -141,6 +143,8 @@
        res.HostKeys[uac.HostKeyType] = 
base64.StdEncoding.EncodeToString(uac.HostKey)
        res.KexInit = &uac.ServerKexInit
        res.Stage = "kex"
+       options.Logger.Tracef("%s kex completed", addr)
+
        if options.StopStage == res.Stage {
                return res
        }
@@ -150,6 +154,8 @@
 
        exts := make(map[string][]byte)
        if !options.SkipStage("ssh-userauth") {
+               options.Logger.Tracef("%s sending ssh-userauth", addr)
+
                // Request the ssh-userauth service
                exts, err = uac.RequestUserAuth()
                if err != nil {
@@ -166,6 +172,8 @@
        _ = conn.SetDeadline(time.Now().Add(options.Timeout * 3))
 
        if !options.SkipStage("auth") {
+               options.Logger.Tracef("%s sending auth", addr)
+
                // Authenticate using the callback
                err = AuthHandler(uac, exts, res)
                if err != nil {
@@ -193,7 +201,7 @@
        }
 
        // The server accepted our authentication
-       sconn, sshChans, sshReqs := uac.Mux()
+       sconn, sshChans, sshReqs := uac.Mux(options.ignoreChannelOpenReply)
        defer sconn.Close()
 
        // Use a multiple of the timeout for the session handler
@@ -215,10 +223,13 @@
                CloseAfterTimeout(authDoneCtx, options.Timeout*10, addr, conn, 
sconn, sclient)
        }()
 
+       options.Logger.Tracef("%s opening session", addr)
+
        // Open a session
        res.Stage = "open-session"
        ses, err := sclient.NewSession()
        if err != nil {
+               options.Logger.Tracef("%s session error %v", addr, err)
                res.Error = err.Error()
                if merr := uac.MuxError(); merr != nil {
                        res.Error = fmt.Sprintf("%v (mux: %v)", err, merr)
@@ -234,7 +245,9 @@
        // Run a custom session handler and let the caller set any timeouts
        if options.sessionHandler != nil {
 
-               // Disable the automatic socket close for custom session handers
+               options.Logger.Tracef("%s session handler running", addr)
+
+               // Disable the automatic socket close for custom session 
handlers
                authDoneCancel()
 
                // Disable the socket deadline
@@ -282,33 +295,43 @@
                time.Sleep(time.Second)
        }
 
+       // Prod the session for more output if stdin is enabled
        if stdIn != nil {
                // Send input likely to trigger useful replies:
                _, err := stdIn.Write([]byte(options.SessionPoke))
                if err != nil {
                        options.Logger.Errorf("%s stdin write returned error: 
%v", addr, err)
                }
-       }
 
-       // Give the session a second to produce any output
-       time.Sleep(time.Second)
+               // Give the session a second to produce any output
+               time.Sleep(time.Second)
 
-       // Peek at the buffered output to determine what other input to send
-       peek := stdOut.Peek()
-       peek = append(peek, stdErr.Peek()...)
+               // Peek at the buffered output to determine what other input to 
send
+               peek := stdOut.Peek()
+               peek = append(peek, stdErr.Peek()...)
+
+               lcVersion := strings.ToLower(res.Version)
+
+               // Poke telnet-in-ssh specifically by trying to use the shell 
escape
+               if bytes.Contains(peek, []byte("scape character is")) {
+                       _, err := stdIn.Write([]byte("\x1d!id||uname||sh\r\n"))
+                       if err != nil {
+                               options.Logger.Errorf("%s stdin write returned 
error: %v", addr, err)
+                       } else {
+                               time.Sleep(time.Second)
+                       }
+               }
 
-       // Poke telnet-in-ssh specifically by trying to use the shell escape
-       if bytes.Contains(peek, []byte("Escape character is")) && stdIn != nil {
-               _, err := stdIn.Write([]byte("\x1d!id||uname||sh\r\n"))
-               if err != nil {
-                       options.Logger.Errorf("%s stdin write returned error: 
%v", addr, err)
-               } else {
-                       time.Sleep(time.Second)
+               // Poke various network devices with "show version\r\n" to get 
better proof data
+               if strings.Contains(lcVersion, "cisco") || 
strings.Contains(lcVersion, "raisecom") {
+                       _, err := stdIn.Write([]byte("show version\r\n"))
+                       if err != nil {
+                               options.Logger.Errorf("%s stdin write returned 
error: %v", addr, err)
+                       } else {
+                               time.Sleep(time.Second)
+                       }
                }
-       }
 
-       // Close stdin
-       if stdIn != nil {
                stdIn.Close()
        }
 
@@ -378,6 +401,46 @@
        if err == nil {
                time.Sleep(time.Second)
        }
+
+       if stdIn != nil {
+               _, err := stdIn.Write([]byte(options.SessionPoke))
+               if err != nil {
+                       options.Logger.Errorf("%s stdin write returned error: 
%v", prefix, err)
+               }
+       }
+
+       // Give the session a second to produce any output
+       time.Sleep(time.Second)
+
+       // Peek at the buffered output to determine what other input to send
+       peek := stdOut.Peek()
+       peek = append(peek, stdErr.Peek()...)
+
+       res.SessionOutput = CleanSessionOutput(peek)
+       return err
+}
+
+func ScrapeExec(options *Options, prefix string, res *AuthResult, ses 
*ssh.Session, cmd string) error {
+       // Buffer stdout/stderr to mutex-protected byte array
+       stdOut := NewSyncByteBuffer(1024 * 16)
+       stdErr := NewSyncByteBuffer(1024 * 16)
+       ses.Stdout = stdOut
+       ses.Stderr = stdErr
+       stdIn, err := ses.StdinPipe()
+       if err != nil {
+               options.Logger.Errorf("%s failed to open stdin pipe: %v", 
prefix, err)
+       }
+
+       // Try to run the specific command
+       err = ses.Start(cmd)
+       if err != nil {
+               options.Logger.Errorf("%s exec command returned error: %v", 
prefix, err)
+       }
+
+       // Give the server a second to process the command
+       if err == nil {
+               time.Sleep(time.Second)
+       }
 
        if stdIn != nil {
                _, err := stdIn.Write([]byte(options.SessionPoke))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/auth/options.go 
new/sshamble-0.3.3/auth/options.go
--- old/sshamble-0.0.5/auth/options.go  2024-09-30 18:50:03.000000000 +0200
+++ new/sshamble-0.3.3/auth/options.go  2025-08-19 06:31:06.000000000 +0200
@@ -30,9 +30,10 @@
        Logger          *logrus.Logger
        SessionPoke     string
 
-       skipStages      []string
-       sessionHandler  SessionHandler
-       postAuthHandler PostAuthHandler
+       skipStages             []string
+       sessionHandler         SessionHandler
+       postAuthHandler        PostAuthHandler
+       ignoreChannelOpenReply bool
 }
 
 func (o *Options) WithRetries(limit uint) *Options {
@@ -53,6 +54,12 @@
        return &n
 }
 
+func (o *Options) WithIgnoreChannelOpenReply(v bool) *Options {
+       n := *o
+       n.ignoreChannelOpenReply = v
+       return &n
+}
+
 func (o *Options) WithTimeout(d time.Duration) *Options {
        n := *o
        n.Timeout = d
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/badkeys/badkeys.go 
new/sshamble-0.3.3/badkeys/badkeys.go
--- old/sshamble-0.0.5/badkeys/badkeys.go       2024-09-30 18:50:03.000000000 
+0200
+++ new/sshamble-0.3.3/badkeys/badkeys.go       2025-08-19 06:31:06.000000000 
+0200
@@ -7,26 +7,83 @@
        "os"
        "path/filepath"
 
+       "github.com/runZeroInc/excrypto/crypto/dsa"
+       "github.com/runZeroInc/excrypto/crypto/ecdh"
+       "github.com/runZeroInc/excrypto/crypto/ecdsa"
+       "github.com/runZeroInc/excrypto/crypto/ed25519"
+
+       "github.com/runZeroInc/excrypto/crypto/rsa"
        "github.com/runZeroInc/excrypto/crypto/sha256"
+       "github.com/runZeroInc/excrypto/crypto/x509"
        "github.com/runZeroInc/excrypto/x/crypto/ssh"
+
+       stddsa "crypto/dsa"
+       stdecdh "crypto/ecdh"
+       stdecdsa "crypto/ecdsa"
+       stded25519 "crypto/ed25519"
+       stdrsa "crypto/rsa"
+
+       stdssh "golang.org/x/crypto/ssh"
 )
 
 const BadKeysMetaURL = "https://update.badkeys.info/v0/badkeysdata.json";
 
-func PrefixFromPublicKey(pub ssh.PublicKey) ([]byte, error) {
-       var res []byte
-       switch pub.Type() {
-       case ssh.KeyAlgoRSA:
-               pk, ok := pub.(ssh.RSAPublicKey)
-               if !ok {
-                       return nil, fmt.Errorf("%s doesn't implement 
RSAPublicKey", pub.Type())
+// PrefixFromPublicKey implements the badkeys `blocklistmaker` hashing method
+func PrefixFromPublicKey(pub any) ([]byte, error) {
+       var rawb []byte
+       switch pub := pub.(type) {
+
+       case ssh.PublicKey:
+               if cpk, ok := pub.(ssh.CryptoPublicKey); ok {
+                       return PrefixFromPublicKey(cpk.CryptoPublicKey())
+               }
+               return nil, fmt.Errorf("unsupported excrypto ssh public key: 
%v", pub.Type())
+       case stdssh.PublicKey:
+               if cpk, ok := pub.(stdssh.CryptoPublicKey); ok {
+                       return PrefixFromPublicKey(cpk.CryptoPublicKey())
                }
-               res = pk.ToRSAPublicKey().N.Bytes()
+               return nil, fmt.Errorf("unsupported stdlib ssh public key: %v", 
pub.Type())
+
+       case *rsa.PublicKey:
+               rawb = pub.N.Bytes()
+       case *stdrsa.PublicKey:
+               rawb = pub.N.Bytes()
+
+       case *ecdsa.PublicKey:
+               rawb = pub.X.Bytes()
+       case *stdecdsa.PublicKey:
+               rawb = pub.X.Bytes()
+
+       case ed25519.PublicKey:
+               rawb = pub
+       case stded25519.PublicKey:
+               rawb = pub
+
+       case x509.X25519PublicKey:
+               rawb = pub
+       /*
+               // Not defined by stdlib
+               case stdx509.X25519PublicKey:
+               rawb = pub
+       */
+
+       case *ecdh.PublicKey:
+               rawb = pub.Bytes() // Verify
+       case *stdecdh.PublicKey:
+               rawb = pub.Bytes() // Verify
+
+       case *dsa.PublicKey:
+               rawb = pub.Y.Bytes()
+       case *stddsa.PublicKey:
+               rawb = pub.Y.Bytes()
+
+       case nil:
+               return nil, fmt.Errorf("unsupported nil key")
        default:
-               res = pub.Marshal()
+               return nil, fmt.Errorf("unsupported key: %T", pub)
        }
-       hash := sha256.Sum256(res)
-       return hash[0:15], nil
+       sha256sum := sha256.Sum256(rawb)
+       return sha256sum[0:15], nil
 }
 
 // GetExecutablePath returns the full path to the running binary
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/badkeys/blocklist.go 
new/sshamble-0.3.3/badkeys/blocklist.go
--- old/sshamble-0.0.5/badkeys/blocklist.go     2024-09-30 18:50:03.000000000 
+0200
+++ new/sshamble-0.3.3/badkeys/blocklist.go     2025-08-19 06:31:06.000000000 
+0200
@@ -72,11 +72,13 @@
        }
        repo, ok := tset.Repos[int(block[15])]
        if !ok {
-               return nil, fmt.Errorf("repo %d is missing", block[15])
+               // No repo ID, likely private
+               return &Result{Private: true}, nil
        }
        info, ok := tset.LookupMap[binary.BigEndian.Uint64(block[:8])]
        if !ok {
-               return nil, fmt.Errorf("lookup %x is missing", block[:8])
+               // No lookup key, likely private
+               return &Result{Private: true, RepoID: int8(repo.ID)}, nil
        }
        parts := make([]string, len(info))
        for i, lk := range info {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/badkeys/cache_test.go 
new/sshamble-0.3.3/badkeys/cache_test.go
--- old/sshamble-0.0.5/badkeys/cache_test.go    2024-09-30 18:50:03.000000000 
+0200
+++ new/sshamble-0.3.3/badkeys/cache_test.go    2025-08-19 06:31:06.000000000 
+0200
@@ -65,7 +65,7 @@
                t.Errorf("unexpected result: %s", diff)
        }
        expURL := 
"https://github.com/badkeys/debianopenssl/blob/main/rsa3072/ssh/be32/29491.key";
-       resURL := res.ToURL()
+       resURL := res.GetURL()
        if resURL != expURL {
                t.Errorf("unexpected url %s got %s", expURL, resURL)
        }
@@ -75,6 +75,21 @@
        if err == nil {
                t.Fatalf("bad hash returned result: %v", res)
        }
+
+       for i := range len(bl.Blocks) / 16 {
+               pre := bl.Blocks[(i * 16) : (i*16)+15]
+               res, err := bl.LookupPrefix(pre)
+               if err != nil {
+                       t.Errorf("key %s returned error %v", 
hex.EncodeToString(pre), err)
+                       continue
+               }
+               if res.Private {
+                       continue
+               }
+               if res.Repo == "" || res.RepoID == 0 || res.RepoName == "" || 
res.RepoPath == "" || res.RepoType == "" {
+                       t.Errorf("non-private key %s returned incomplete 
results %#v", hex.EncodeToString(pre), res)
+               }
+       }
 }
 
 // TestKeyDebOpenSSLRSA3072BE3229491 is the public key from 
https://github.com/badkeys/debianopenssl/blob/main/rsa3072/ssh/be32/29491.key
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/badkeys/result.go 
new/sshamble-0.3.3/badkeys/result.go
--- old/sshamble-0.0.5/badkeys/result.go        2024-09-30 18:50:03.000000000 
+0200
+++ new/sshamble-0.3.3/badkeys/result.go        2025-08-19 06:31:06.000000000 
+0200
@@ -1,6 +1,9 @@
 package badkeys
 
-import "path"
+import (
+       "path"
+       "strconv"
+)
 
 type Result struct {
        Repo     string
@@ -9,11 +12,24 @@
        RepoPath string
        RepoName string
        KeyPath  string
+       Private  bool
+       Hash     string
 }
 
-func (r *Result) ToURL() string {
+func (r *Result) GetID() string {
+       if r.Private {
+               repStr := strconv.FormatUint(uint64(r.RepoID), 10)
+               return "badkeys-private-" + repStr + "-" + r.Hash
+       }
+       return "badkeys-" + r.RepoType + "-" + r.Repo + "-" + r.RepoPath + "-" 
+ r.Hash
+}
+
+func (r *Result) GetURL() string {
+       if r.Private {
+               return "unpublished://" + r.GetID() + "-" + r.Hash
+       }
        if r.RepoType != "github" {
-               return ""
+               return "https://"; + r.RepoType + "/" + path.Join(r.Repo, 
"blob", r.RepoPath, r.KeyPath)
        }
        return "https://github.com/"; + path.Join(r.Repo, "blob", r.RepoPath, 
r.KeyPath)
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/check_bypass.go 
new/sshamble-0.3.3/cmd/check_bypass.go
--- old/sshamble-0.0.5/cmd/check_bypass.go      2024-09-30 18:50:03.000000000 
+0200
+++ new/sshamble-0.3.3/cmd/check_bypass.go      2025-08-19 06:31:06.000000000 
+0200
@@ -203,7 +203,7 @@
 
        res := auth.SSHAuth(addr, options.WithIgnoreAuthError(), 
auth.SSHAuthHandlerSingle(customAuth))
        if bypassAtInterestingStage(tname, addr, conf, res) {
-               conf.Logger.Warnf("%s %s provided a session with empty auth 
method '%s': %s", addr, tname, res.Stage, res.SessionOutput)
+               conf.Logger.Warnf("%s %s provided a session with NULL auth 
method '%s': %s", addr, tname, res.Stage, res.SessionOutput)
                res.SessionMethod = tname
                root.SessionMethod = tname
                root.SessionOutput = res.SessionOutput
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/check_hostkeys.go 
new/sshamble-0.3.3/cmd/check_hostkeys.go
--- old/sshamble-0.0.5/cmd/check_hostkeys.go    2024-09-30 18:50:03.000000000 
+0200
+++ new/sshamble-0.3.3/cmd/check_hostkeys.go    2025-08-19 06:31:06.000000000 
+0200
@@ -2,6 +2,8 @@
 
 import (
        "encoding/base64"
+       "encoding/hex"
+       "strconv"
 
        "github.com/runZeroInc/excrypto/x/crypto/ssh"
        "github.com/runZeroInc/sshamble/auth"
@@ -19,7 +21,7 @@
                return nil
        }
 
-       for hkt, hkv := range root.HostKeys {
+       for _, hkv := range root.HostKeys {
                raw, err := base64.StdEncoding.DecodeString(hkv)
                if err != nil {
                        continue
@@ -37,13 +39,23 @@
                        continue
                }
 
-               conf.Logger.Warnf("%s %s found compromised hostkey: %s", addr, 
tname, bkr.ToURL())
-
-               root.AddVuln(auth.VulnResult{
-                       ID:    "badkeys-" + bkr.RepoType + "-" + bkr.Repo + "-" 
+ bkr.RepoPath + "-" + hkt,
-                       Ref:   "https://badkeys.info/";,
-                       Proof: bkr.ToURL(),
-               })
+               if bkr.Private {
+                       repStr := strconv.FormatUint(uint64(bkr.RepoID), 10)
+                       hexPre := hex.EncodeToString(hpre)
+                       conf.Logger.Warnf("%s %s found compromised unpublished 
hostkey with repo %s and hash %s", addr, tname, repStr, hexPre)
+                       root.AddVuln(auth.VulnResult{
+                               ID:    bkr.GetID(),
+                               Ref:   "https://badkeys.info/";,
+                               Proof: repStr + "-" + hexPre,
+                       })
+               } else {
+                       conf.Logger.Warnf("%s %s found compromised hostkey: 
%s", addr, tname, bkr.GetURL())
+                       root.AddVuln(auth.VulnResult{
+                               ID:    bkr.GetID(),
+                               Ref:   "https://badkeys.info/";,
+                               Proof: bkr.GetURL(),
+                       })
+               }
        }
 
        return nil
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/check_pubkey.go 
new/sshamble-0.3.3/cmd/check_pubkey.go
--- old/sshamble-0.0.5/cmd/check_pubkey.go      2024-09-30 18:50:03.000000000 
+0200
+++ new/sshamble-0.3.3/cmd/check_pubkey.go      2025-08-19 06:31:06.000000000 
+0200
@@ -129,7 +129,7 @@
        }
        defer hf.Close()
 
-       conf.Logger.Debugf("%s %s is running ", addr, tname)
+       conf.Logger.Debugf("%s %s is running", addr, tname)
 
        stime := time.Now()
        perSlice := int(gPubKeyHuntConnLimit)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/check_vuln.go 
new/sshamble-0.3.3/cmd/check_vuln.go
--- old/sshamble-0.0.5/cmd/check_vuln.go        1970-01-01 01:00:00.000000000 
+0100
+++ new/sshamble-0.3.3/cmd/check_vuln.go        2025-08-19 06:31:06.000000000 
+0200
@@ -0,0 +1,9 @@
+package cmd
+
+func initVulnChecks() {
+       // Register pre-session vulnerability checks
+
+       // Disabled by default due to false positives today
+       registerCheck(checkVulnExecSkipUserAuth, "vuln", false, false)
+       registerCheck(checkVulnExecSkipAuth, "vuln", false, false)
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/check_vuln_exec.go 
new/sshamble-0.3.3/cmd/check_vuln_exec.go
--- old/sshamble-0.0.5/cmd/check_vuln_exec.go   1970-01-01 01:00:00.000000000 
+0100
+++ new/sshamble-0.3.3/cmd/check_vuln_exec.go   2025-08-19 06:31:06.000000000 
+0200
@@ -0,0 +1,97 @@
+package cmd
+
+import (
+       "fmt"
+       "io"
+       "net"
+       "strings"
+       "time"
+
+       "github.com/runZeroInc/excrypto/x/crypto/ssh"
+       "github.com/runZeroInc/sshamble/auth"
+)
+
+const checkVulnExecSkipUserAuth = "vuln-exec-skip-userauth"
+
+func sshCheckVulnExecSkipUserAuth(addr string, conf *ScanConfig, options 
*auth.Options, root *auth.AuthResult) *auth.AuthResult {
+       tname := checkVulnExecSkipUserAuth
+       return sshCheckVulnExecHelper(tname, addr, conf, options, root, 
[]string{"ssh-userauth", "auth"})
+}
+
+const checkVulnExecSkipAuth = "vuln-exec-skip-auth"
+
+func sshCheckVulnExecSkipAuth(addr string, conf *ScanConfig, options 
*auth.Options, root *auth.AuthResult) *auth.AuthResult {
+       tname := checkVulnExecSkipUserAuth
+       conf.Logger.Debugf("%s %s is running for user %s", addr, tname, 
options.Username)
+       return sshCheckVulnExecHelper(tname, addr, conf, options, root, 
[]string{"auth"})
+}
+
+func sshCheckVulnExecHelper(tname string, addr string, conf *ScanConfig, 
options *auth.Options, root *auth.AuthResult, skipStages []string) 
*auth.AuthResult {
+       if !conf.IsCheckEnabled(tname) {
+               return nil
+       }
+       conf.Logger.Debugf("%s %s is running for user %s", addr, tname, 
options.Username)
+
+       /*
+               This test identifies cases where the server processes the 
channel open and exec commands
+               but does not send a reply to either request (ex: Erlang-SSHD). 
The theory is that non-vulnerable
+               servers will either reply with an error or close the socket.
+       */
+       var maxWaitSeconds = 5
+       options = options.
+               WithSkipStages(skipStages...).
+               WithIgnoreChannelOpenReply(true).
+               WithSessionHandler(func(c net.Conn, sclient *ssh.Client, ses 
*ssh.Session, r *auth.AuthResult) error {
+                       _ = c.SetDeadline(time.Now().Add(time.Second * 
time.Duration(maxWaitSeconds)))
+                       stime := time.Now()
+                       err := ses.Start(`help`)
+                       if err == nil {
+                               conf.Logger.Debugf("%s %s completed exec 
without error", addr, tname)
+                               return nil
+                       }
+                       if err == io.EOF && time.Since(stime) > 
time.Second*time.Duration(maxWaitSeconds-1) {
+                               conf.Logger.Debugf("%s %s completed exec with 
timeout", addr, tname)
+                               return nil
+                       }
+                       conf.Logger.Debugf("%s %s unlikely exec error %v after 
%s", addr, tname, err, time.Since(stime))
+                       return err
+               })
+
+       res := auth.SSHAuth(addr, options, 
auth.SSHAuthHandlerSingle(ssh.None()))
+       if bypassAtInterestingStage(tname, addr, conf, res) {
+               var proof = root.SessionOutput
+               if res.SessionOutput == "" {
+                       proof = "timeout reached"
+               }
+               root.AddVuln(auth.VulnResult{
+                       ID:    tname,
+                       Ref:   
"https://www.openwall.com/lists/oss-security/2025/04/16/2";,
+                       Proof: fmt.Sprintf("exec may have been processed: %s 
(skipped stages: %v)", proof, strings.Join(skipStages, ",")),
+               })
+               return res
+       }
+
+       // An alternate implementation using raw messages instead
+       /*
+               cb := func(c net.Conn, uac *ssh.UnauthClientConn, r 
*auth.AuthResult) error {
+                       _ = c.SetDeadline(time.Now().Add(time.Second * 15))
+                       raw := uac.BuildChannelOpen(0, "session", nil)
+                       uac.WriteRaw(raw, false)
+                       raw = uac.BuildChannelRequestString(0, "exec", "id", 
true)
+                       uac.WriteRaw(raw, false)
+                       for {
+                               reply, err := uac.ReadRaw()
+                               if err != nil {
+                                       conf.Logger.Warnf("%s %s got error 
%#v", addr, tname, err)
+                                       break
+                               }
+
+                       }
+                       return nil
+               }
+               options = options.WithSkipStages("ssh-userauth", 
"auth").WithIgnoreAuthError().WithPostAuthHandler(cb)
+       */
+
+       // Note: For Erlang-SSHD, the payload `ssh:stop().` kills the service
+       return nil
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/cmd_analyze.go 
new/sshamble-0.3.3/cmd/cmd_analyze.go
--- old/sshamble-0.0.5/cmd/cmd_analyze.go       2024-09-30 18:50:03.000000000 
+0200
+++ new/sshamble-0.3.3/cmd/cmd_analyze.go       2025-08-19 06:31:06.000000000 
+0200
@@ -21,9 +21,9 @@
 
 // analyzeCmd processes a scan output file and buckets results
 var analyzeCmd = &cobra.Command{
-       Use:   "analyze -o results-directory scan.json ...",
-       Short: "Analyzes a scan JSON output file and buckets results",
-       Long:  "Analyzes a scan JSON output file and buckets results",
+       Use:   "analyze -o results-directory scan.jsonl ...",
+       Short: "Analyzes a scan JSONL output file and buckets results",
+       Long:  "Analyzes a scan JSONL output file and buckets results",
        Run:   runAnalyze,
 }
 
@@ -172,14 +172,11 @@
                return nil
        }
 
-       if isHoneypot(conf, res) {
-               conf.writeAnalysisRecord("honeypots", res)
-       }
-
        if conf.BadKeyCache != nil && isBadKey(conf, res) {
                conf.writeAnalysisRecord("badkeys", res)
        }
 
+       // Only write a single record for each session
        if name := isKnownDevice(conf, res); name != "" {
                conf.writeAnalysisRecord("session_known_"+name, res)
        } else if res.SessionMethod != "" {
@@ -223,7 +220,7 @@
 }
 
 func (conf *ScanConfig) writeAnalysisRecord(name string, res *auth.AuthResult) 
{
-       fd, err := os.OpenFile(filepath.Join(gOutput, 
filepath.Base(name)+".json"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
+       fd, err := os.OpenFile(filepath.Join(gOutput, 
filepath.Base(name)+".jsonl"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
        if err != nil {
                conf.Logger.Fatalf("failed to write file %s: %v", name, err)
        }
@@ -268,9 +265,19 @@
        fd.Close()
 }
 
+// Simple honeypot detection using common strings
+// TODO: Replace these with regexes or heuristics
+// TODO: Keep track of whether the kex init is sent before the client version 
since this is an obvious tell for fake OpenSSH.
 var commonHoneypotStrings = []string{
        "The programs included with the Debian GNU/Linux system are free 
software;",
+       "ABSOLUTELY NO WARRANTY",
        "Welcome to Ubuntu",
+       "microsoft-standard-WSL2",
+       "ost ~]$",
+       "ost tmp]$",
+       " ~tmp]$",
+       " /tmp]$",
+       "HONEYPOT",
 }
 
 func isHoneypot(conf *ScanConfig, res *auth.AuthResult) bool {
@@ -284,7 +291,7 @@
 
 func isBadKey(conf *ScanConfig, res *auth.AuthResult) bool {
        found := 0
-       for hkt, hkv := range res.HostKeys {
+       for _, hkv := range res.HostKeys {
                raw, err := base64.StdEncoding.DecodeString(hkv)
                if err != nil {
                        continue
@@ -301,18 +308,28 @@
                if err != nil {
                        continue
                }
-               res.AddVuln(auth.VulnResult{
-                       ID:    "badkeys-" + bkr.RepoType + "-" + bkr.Repo + "-" 
+ bkr.RepoPath + "-" + hkt,
-                       Ref:   "https://badkeys.info/";,
-                       Proof: bkr.ToURL(),
-               })
+               if bkr.Private {
+                       res.AddVuln(auth.VulnResult{
+                               ID:    bkr.GetID(),
+                               Ref:   "https://badkeys.info/";,
+                               Proof: bkr.GetURL(),
+                       })
+               } else {
+                       res.AddVuln(auth.VulnResult{
+                               ID:    bkr.GetID(),
+                               Ref:   "https://badkeys.info/";,
+                               Proof: bkr.GetURL(),
+                       })
+               }
                found++
        }
        return found != 0
 }
 
 var commonDeviceStrings = map[string]string{
-       "sonicwall":    "SonicWall",
+       "sonicwall1":   "SonicWall",
+       "sonicwall2":   "SonicWALL",
+       "sonicwall3":   "Sonicwall",
        "atos":         "ATOSNT Remote CLI", // No password
        "yamaha-rtx":   "Error: Login access is restricted",
        "dlink":        "D-Link Corporation",
@@ -326,21 +343,36 @@
        "lancom":       "Connection No.:",
        "realpresence": "Here is what I know about myself",
        "mikrotik":     "MikroTik RouterOS",
-       "exceed":       "exceeds the specificaitons",
+       "exceed":       "exceeds the specificaitons", // Typo intentional
        "hpswitch":     "HEWLETT-PACKARD COMPANY, 3000 Hanover St",
        "gitee":        "GITEE.COM does not provide shell access",
        "tl1":          "Starting Interactive TL1",
        "sshs":         "SSHS>",
        "keenetic":     "https://keenetic";,
        "vstfs2":       "Your Git command did not succeed",
+       "vstfs3":       "remote: Shell access is not supported",
        "cellrtr":      "Cellular Router>",
        "ruckus":       "Please login: \r\nPlease login",
        "snips.sh":     "snips.sh",
        "mioffice_mfa": "https://xiaomi.f.mioffice.cn";,
        "l2switch":     "Welcome to Layer 2 Managed Switch",
+       "listensocks":  "listensocks",
+       "axgate":       "AxGate",
+       "gitops":       "This port is reserved for git operations",
+       "segfault":     "segfault",
+       "telnet":       "Escape character is",
+       "busybox":      "built-in shell",
+       "juniper":      "Juniper Networks, Inc. All rights 
reserved.\r\n\n\r\n\r\n\rUsername: \n\rPassword:",
+       "snips":        "snips.sh",
+       "syncronet":    "Synchronet BBS",
+       "raisecom":     "<capability>urn:ietf:params:",
+       "abilis":       "Abilis CPX",
 }
 
 func isKnownDevice(conf *ScanConfig, res *auth.AuthResult) string {
+       if isHoneypot(conf, res) {
+               return "honeypots"
+       }
        for k, v := range commonDeviceStrings {
                if strings.Contains(strings.ToLower(res.SessionOutput), 
strings.ToLower(v)) {
                        return k
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/cmd_root.go 
new/sshamble-0.3.3/cmd/cmd_root.go
--- old/sshamble-0.0.5/cmd/cmd_root.go  2024-09-30 18:50:03.000000000 +0200
+++ new/sshamble-0.3.3/cmd/cmd_root.go  2025-08-19 06:31:06.000000000 +0200
@@ -21,7 +21,7 @@
 
 // rootCmd represents the base command when called without any subcommands
 var rootCmd = &cobra.Command{
-       Use:   "sshamble {scan -o results.json 192.168.0.0/24, analyze 
results.json -d results-dir}",
+       Use:   "sshamble {scan -o results.jsonl 192.168.0.0/24, analyze 
results.jsonl -d results-dir}",
        Short: "An exploration tool for (in)secure shell services",
        Long: `
 
@@ -41,11 +41,11 @@
 
 Start a network scan using:
 
-$ sshamble scan -o results.json 192.168.0.0/24
+$ sshamble scan -o results.jsonl 192.168.0.0/24
 
 Analyze the results using:
 
-$ sshamble analyze -o results-directory results.json
+$ sshamble analyze -o results-directory results.jsonl
 
 `,
 }
@@ -68,6 +68,7 @@
        initUserEnumChecks()
        initGSSAPIChecks()
        initHostkeyChecks()
+       initVulnChecks()
 
        cobra.OnInitialize(initConfig)
 
@@ -117,7 +118,7 @@
                return err
        }
        line = bytes.ReplaceAll(line, []byte{0x00}, []byte{})
-       if gStdinManager != nil && gStdinManager.IsRawMode() {
+       if m := GetStdinManager(); m != nil && !m.IsRawMode() {
                // Use CRLF for raw mode and don't filter escapes
                line = append([]byte{'\r', '\n'}, bytes.TrimSpace(line)...)
                line = append(line, '\r', '\n')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/cmd_scan.go 
new/sshamble-0.3.3/cmd/cmd_scan.go
--- old/sshamble-0.0.5/cmd/cmd_scan.go  2024-09-30 18:50:03.000000000 +0200
+++ new/sshamble-0.3.3/cmd/cmd_scan.go  2025-08-19 06:31:06.000000000 +0200
@@ -23,7 +23,7 @@
 
 // scanCmd handles scanning
 var scanCmd = &cobra.Command{
-       Use:   "scan [-p 22] [-u root,admin] [-o scan.json] [-l scan.log] 
[--log-level trace] 192.168.0.0/24 ...",
+       Use:   "scan [-p 22] [-u root,admin] [-o scan.jsonl] [-l scan.log] 
[--log-level trace] 192.168.0.0/24 ...",
        Short: "Enumerates a set of targets for SSH capabilities and exposures",
        Long:  "Enumerates a set of targets for SSH capabilities and exposures",
        Run:   runScan,
@@ -54,6 +54,10 @@
        gLogfile                    string
        gLogLevel                   string
        gPProfPort                  string
+       gSkipVersions               string
+       gSkipVersionsRegex          *regexp.Regexp
+       gOneSessionOnly             bool
+       gSessionPoke                string
 
        interactMutex sync.Mutex
 )
@@ -132,6 +136,10 @@
        scanCmd.Flags().StringVarP(&gLogLevel, "log-level", "L", "info", "The 
log level to write (trace,debug,info,warn,error)")
        scanCmd.Flags().StringVar(&gPProfPort, "pprof", "", "Start a Go pprof 
debug listener on the provided port")
        scanCmd.Flags().UintVar(&gRetries, "retries", 2, "The retry count for 
subsequent failed connections after an initial success")
+       scanCmd.Flags().StringVar(&gSkipVersions, "skip-versions", "", "A 
regular expression of SSH versions to skip (ex: '(?i)openssh|dropbear)'")
+       scanCmd.Flags().BoolVar(&gOneSessionOnly, "one-session-only", false, 
"Only open one session per target")
+       scanCmd.Flags().StringVar(&gSessionPoke, "session-poke", 
"\\x0a\\x0d\\r\\n", "A byte sequence sent to sessions to elicit further 
responses (hex or ascii)")
+
 }
 
 var TestKeyRSASizes = []int{1024, 2048, 4096}
@@ -239,6 +247,13 @@
                conf.Logger.Fatalf("unable to read targets from stdin while 
interact is enabled")
        }
 
+       if gSkipVersions != "" {
+               gSkipVersionsRegex, err = regexp.Compile(gSkipVersions)
+               if err != nil {
+                       conf.Logger.Fatalf("invalid skip-versions regex '%s': 
%v", gSkipVersions, err)
+               }
+       }
+
        // Configure private key
        var privateKey ssh.Signer
        if gPrivateKeyFile != "" {
@@ -342,7 +357,7 @@
                                        Timeout:       time.Second * 
time.Duration(gTimeout),
                                        Logger:        conf.Logger,
                                        ClientVersion: gClientVersion,
-                                       SessionPoke:   "\r\n\r\n",
+                                       SessionPoke:   
string(processEscapedByteString(gSessionPoke)),
                                }
                        }
 
@@ -385,7 +400,7 @@
        // Start the stdin manager if interaction was requested
        if gInteract != "none" && gInteract != "" {
                conf.Logger.Debugf("interaction enabled, starting stdin 
manager...")
-               gStdinManager = NewStdinManager()
+               NewStdinManager()
        }
 
        // Add on any command-line targets
@@ -473,6 +488,7 @@
 
 // GetSession runs through all potential checks that can lead to a session
 func (conf *ScanConfig) GetSession(addr string, options *auth.Options, cached 
*auth.AuthResult) (root *auth.AuthResult) {
+
        // Start with a required "none" authentication check to determine 
server capabilities
        root = auth.SSHAuthNone(addr, options)
 
@@ -499,6 +515,12 @@
                return
        }
 
+       // Exit early if the version matches the skip regex
+       if gSkipVersionsRegex != nil && 
gSkipVersionsRegex.MatchString(root.Version) {
+               conf.Logger.Debugf("%s version %s matches skip-version 
pattern", addr, root.Version)
+               return
+       }
+
        var res *auth.AuthResult
 
        shouldInteract := func() {
@@ -510,14 +532,23 @@
        }
 
        shouldReturn := func() bool {
+
+               // Service is no longer available, stop trying
                if res != nil && res.Unreachable {
                        conf.Logger.Errorf("%s failed to reconnect: %v", addr, 
res.Error)
                        return true
                }
+
+               // Interact on the first session (skipping additional checks)
                if gInteract == "first" && root.SessionMethod != "" {
                        conf.Logger.Warnf("%s returned a session, interacting 
via %s", addr, root.SessionMethod)
                        return true
                }
+
+               // Break on the first session for this target
+               if gOneSessionOnly && root.SessionMethod != "" {
+                       return true
+               }
                return false
        }
 
@@ -657,6 +688,14 @@
                }
        }
 
+       // Process pre-session vulnerability checks
+       vulnChecks := []sshCheckFunc{
+               sshCheckVulnExecSkipUserAuth,
+               sshCheckVulnExecSkipAuth,
+       }
+       for _, check := range vulnChecks {
+               _ = check(addr, conf, options, root)
+       }
        return
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/interact.go 
new/sshamble-0.3.3/cmd/interact.go
--- old/sshamble-0.0.5/cmd/interact.go  2024-09-30 18:50:03.000000000 +0200
+++ new/sshamble-0.3.3/cmd/interact.go  2025-08-19 06:31:06.000000000 +0200
@@ -68,6 +68,7 @@
 }
 
 var gStdinManager *stdinManager
+var gStdinManagerM sync.Mutex
 
 type stdinManager struct {
        sync.Mutex
@@ -76,11 +77,22 @@
        rawMode  bool
 }
 
-func NewStdinManager() *stdinManager {
+func GetStdinManager() *stdinManager {
+       gStdinManagerM.Lock()
+       defer gStdinManagerM.Unlock()
+       return gStdinManager
+}
+
+func NewStdinManager() {
+       gStdinManagerM.Lock()
+       defer gStdinManagerM.Unlock()
+       if gStdinManager != nil {
+               return
+       }
        m := &stdinManager{
                output: nil,
        }
-       return m
+       gStdinManager = m
 }
 
 func (m *stdinManager) CleanTerminal() {
@@ -277,7 +289,12 @@
                        return fmt.Errorf("interact mode requires a controlling 
terminal")
                }
 
-               go gStdinManager.Relay(conf, intDoneCtx)
+               m := GetStdinManager()
+               if m == nil {
+                       return fmt.Errorf("failed to get stdin manager")
+               }
+
+               go m.Relay(conf, intDoneCtx)
 
                state := &interactSessionState{}
 
@@ -298,10 +315,10 @@
                }()
 
                // Make sure our terminal is in raw mode
-               if !gStdinManager.IsRawMode() {
-                       gStdinManager.SetRawTerminalMode()
-               }
-               defer gStdinManager.RestoreTerminalMode()
+               m.SetRawTerminalMode()
+
+               // Restore the terminal mode after completion
+               defer m.RestoreTerminalMode()
 
                // Close the ssh.Client first (last defer) to avoid deadlocks
                defer sclient.Close()
@@ -314,7 +331,7 @@
                        if cmd == "" {
                                continue
                        }
-                       fmt.Printf(" sshamble> " + cmd + "\r\n")
+                       fmt.Printf(" sshamble> %s\r\n", cmd)
                        spawned, err := conf.InteractCommand(addr, []byte(cmd), 
ses, sclient, state, sesInput)
                        if err != nil {
                                conf.Logger.Errorf("%s command '%s' returned 
error: %v", addr, cmd, err)
@@ -356,7 +373,6 @@
                        sclient.Close()
                        sesInput.Close()
                }
-               gStdinManager.RestoreTerminalMode()
                return nil
        }
 }
@@ -380,18 +396,21 @@
        fmt.Printf("    send     string            - Send string to the 
session\r\n")
        fmt.Printf("    sendb    string            - Send string to the session 
one byte at a time\r\n")
        fmt.Printf("    wait     cmd arg1 arg2     - Send another command and 
wait for a reply\r\n")
+       fmt.Printf("    sleep    duration          - Sleep for the specified 
duration (1s, 100ms)\r\n")
        fmt.Printf("\r\n\r\n")
 }
 
 func (conf *ScanConfig) InteractRelay(addr string, quit chan bool, shell 
io.WriteCloser, sclient *ssh.Client, ses *ssh.Session, state 
*interactSessionState) {
        input := make(chan []byte, 1)
-       gStdinManager.SetOutput(input)
+
+       m := GetStdinManager()
+       m.SetOutput(input)
 
        defer func() {
                if r := recover(); r != nil {
                        conf.Logger.Errorf("panic: interact relay: %v", r)
                }
-               gStdinManager.SetOutput(nil)
+               m.SetOutput(nil)
                sclient.Close()
        }()
 
@@ -558,11 +577,11 @@
 
        case "send":
                data := strings.Join(args[1:], " ")
-               _, err := shell.Write(processSendBytes(data))
+               _, err := shell.Write(processEscapedByteString(data))
                return false, err
 
        case "sendb":
-               data := processSendBytes(strings.Join(args[1:], " "))
+               data := processEscapedByteString(strings.Join(args[1:], " "))
                for i := 0; i < len(data); i++ {
                        _, err := shell.Write(data[i : i+1])
                        if err != nil {
@@ -679,6 +698,16 @@
                        c.Close()
                }()
                return false, nil
+       case "sleep":
+               if len(args) < 2 {
+                       return false, fmt.Errorf("missing sleep duration")
+               }
+               duration, err := time.ParseDuration(args[1])
+               if err != nil {
+                       return false, fmt.Errorf("invalid sleep duration: %v", 
err)
+               }
+               time.Sleep(duration)
+               return false, nil
        }
 
        return false, fmt.Errorf("unknown command: %s", strings.Join(args, " "))
@@ -686,7 +715,7 @@
 
 var patReplaceHexEscape = regexp.MustCompile(`(\\x[a-fA-F0-9]{2}|\\[rnt])`)
 
-func processSendBytes(s string) []byte {
+func processEscapedByteString(s string) []byte {
        s = patReplaceHexEscape.ReplaceAllStringFunc(s, func(h string) string {
                if len(h) == 4 {
                        b, _ := hex.DecodeString(h[2:])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/interact_test.go 
new/sshamble-0.3.3/cmd/interact_test.go
--- old/sshamble-0.0.5/cmd/interact_test.go     2024-09-30 18:50:03.000000000 
+0200
+++ new/sshamble-0.3.3/cmd/interact_test.go     2025-08-19 06:31:06.000000000 
+0200
@@ -19,7 +19,7 @@
                {"\\r\\nABC\\tDEFG", []byte("\r\nABC\tDEFG")},
        }
        for _, tc := range testCases {
-               got := processSendBytes(tc.Input)
+               got := processEscapedByteString(tc.Input)
                if !bytes.Equal(tc.Expected, got) {
                        t.Errorf("got %s for %s, expected %s", 
hex.EncodeToString(got), tc.Input, hex.EncodeToString(tc.Expected))
                }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/cmd/session.go 
new/sshamble-0.3.3/cmd/session.go
--- old/sshamble-0.0.5/cmd/session.go   2024-09-30 18:50:03.000000000 +0200
+++ new/sshamble-0.3.3/cmd/session.go   2025-08-19 06:31:06.000000000 +0200
@@ -17,7 +17,7 @@
 }
 
 func initSessionChecks() {
-       // Register vulnerability checks
+       // Register session-based vulnerability checks
        registerCheck(checkVulnGogsEnv, "vuln", true, true)
        registerCheck(checkVulnRuckusPasswordEscape, "vuln", true, true)
        registerCheck(checkVulnSoftServe, "vuln", true, true)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/go.mod new/sshamble-0.3.3/go.mod
--- old/sshamble-0.0.5/go.mod   2024-09-30 18:50:03.000000000 +0200
+++ new/sshamble-0.3.3/go.mod   2025-08-19 06:31:06.000000000 +0200
@@ -1,40 +1,35 @@
 module github.com/runZeroInc/sshamble
 
-go 1.23.1
+go 1.24
 
 require (
-       github.com/google/go-cmp v0.6.0
+       github.com/google/go-cmp v0.7.0
        github.com/logrusorgru/aurora/v3 v3.0.0
        github.com/mmcloughlin/professor v0.0.0-20170922221822-6b97112ab8b3
-       github.com/runZeroInc/excrypto v0.0.0-20240929210559-608d208b7065
+       github.com/runZeroInc/excrypto v0.0.0-20250629231451-7e62a70a4cc3
        github.com/sirupsen/logrus v1.9.3
-       github.com/spf13/cobra v1.8.1
-       github.com/spf13/viper v1.19.0
+       github.com/spf13/cobra v1.9.1
+       github.com/spf13/viper v1.20.1
        github.com/ulikunitz/xz v0.5.12
-       golang.org/x/net v0.29.0
-       golang.org/x/sys v0.25.0
-       golang.org/x/term v0.24.0
-       gonum.org/v1/gonum v0.15.1
+       golang.org/x/crypto v0.41.0
+       golang.org/x/term v0.34.0
+       gonum.org/v1/gonum v0.16.0
 )
 
 require (
-       github.com/fsnotify/fsnotify v1.7.0 // indirect
-       github.com/hashicorp/hcl v1.0.0 // indirect
+       github.com/fsnotify/fsnotify v1.9.0 // indirect
+       github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
        github.com/inconshreveable/mousetrap v1.1.0 // indirect
-       github.com/magiconair/properties v1.8.7 // indirect
-       github.com/mitchellh/mapstructure v1.5.0 // indirect
-       github.com/pelletier/go-toml/v2 v2.2.3 // indirect
-       github.com/sagikazarmark/locafero v0.6.0 // indirect
-       github.com/sagikazarmark/slog-shim v0.1.0 // indirect
-       github.com/sourcegraph/conc v0.3.0 // indirect
-       github.com/spf13/afero v1.11.0 // indirect
-       github.com/spf13/cast v1.7.0 // indirect
-       github.com/spf13/pflag v1.0.5 // indirect
+       github.com/pelletier/go-toml/v2 v2.2.4 // indirect
+       github.com/sagikazarmark/locafero v0.10.0 // indirect
+       github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // 
indirect
+       github.com/spf13/afero v1.14.0 // indirect
+       github.com/spf13/cast v1.9.2 // indirect
+       github.com/spf13/pflag v1.0.7 // indirect
        github.com/subosito/gotenv v1.6.0 // indirect
        github.com/weppos/publicsuffix-go v0.40.2 // indirect
-       go.uber.org/multierr v1.11.0 // indirect
-       golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
-       golang.org/x/text v0.18.0 // indirect
-       gopkg.in/ini.v1 v1.67.0 // indirect
+       golang.org/x/net v0.43.0 // indirect
+       golang.org/x/sys v0.35.0 // indirect
+       golang.org/x/text v0.28.0 // indirect
        gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sshamble-0.0.5/go.sum new/sshamble-0.3.3/go.sum
--- old/sshamble-0.0.5/go.sum   2024-09-30 18:50:03.000000000 +0200
+++ new/sshamble-0.3.3/go.sum   2025-08-19 06:31:06.000000000 +0200
@@ -2,15 +2,16 @@
 github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod 
h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
 github.com/bwesterb/go-ristretto v1.2.0/go.mod 
h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 github.com/cloudflare/circl v1.1.0/go.mod 
h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
-github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod 
h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod 
h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc 
h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/frankban/quicktest v1.14.6 
h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 github.com/frankban/quicktest v1.14.6/go.mod 
h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
-github.com/fsnotify/fsnotify v1.7.0 
h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
-github.com/fsnotify/fsnotify v1.7.0/go.mod 
h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/fsnotify/fsnotify v1.9.0 
h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod 
h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/go-viper/mapstructure/v2 v2.4.0 
h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
+github.com/go-viper/mapstructure/v2 v2.4.0/go.mod 
h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
 github.com/golang/protobuf v1.3.1/go.mod 
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.5.0/go.mod 
h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2/go.mod 
h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@@ -18,12 +19,11 @@
 github.com/google/go-cmp v0.5.5/go.mod 
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.8/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod 
h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/go-github/v50 v50.2.0/go.mod 
h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=
 github.com/google/go-querystring v1.1.0/go.mod 
h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hashicorp/hcl v1.0.0/go.mod 
h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/inconshreveable/mousetrap v1.1.0 
h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod 
h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -32,48 +32,38 @@
 github.com/kr/text v0.2.0/go.mod 
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/logrusorgru/aurora/v3 v3.0.0 
h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4=
 github.com/logrusorgru/aurora/v3 v3.0.0/go.mod 
h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
-github.com/magiconair/properties v1.8.7 
h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
-github.com/magiconair/properties v1.8.7/go.mod 
h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
-github.com/mitchellh/mapstructure v1.5.0 
h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
-github.com/mitchellh/mapstructure v1.5.0/go.mod 
h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mmcloughlin/professor v0.0.0-20170922221822-6b97112ab8b3 
h1:2YMbJ6WbdQI9K73chxh9OWMDsZ2PNjAIRGTonp3T0l0=
 github.com/mmcloughlin/professor v0.0.0-20170922221822-6b97112ab8b3/go.mod 
h1:LQkXsHRSPIEklPCq8OMQAzYNS2NGtYStdNE/ej1oJU8=
-github.com/pelletier/go-toml/v2 v2.2.3 
h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
-github.com/pelletier/go-toml/v2 v2.2.3/go.mod 
h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
+github.com/pelletier/go-toml/v2 v2.2.4 
h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
+github.com/pelletier/go-toml/v2 v2.2.4/go.mod 
h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 
h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
-github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rogpeppe/go-internal v1.9.0 
h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
 github.com/rogpeppe/go-internal v1.9.0/go.mod 
h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/runZeroInc/excrypto v0.0.0-20240929201155-1ec9f9e6504e 
h1:N9uY2fZNa44bqH3K5isLVZYelrzzY2pEYXVOXjOC+vc=
-github.com/runZeroInc/excrypto v0.0.0-20240929201155-1ec9f9e6504e/go.mod 
h1:ai0QX/YMgRVCUSlcByz860UOclsOFHBlczfBaKy47vo=
-github.com/runZeroInc/excrypto v0.0.0-20240929203415-13f233cb0836 
h1:tnBRzDl1wcloV3+hVohnuNrw0v9UdnIKHnasChIYd4Y=
-github.com/runZeroInc/excrypto v0.0.0-20240929203415-13f233cb0836/go.mod 
h1:ai0QX/YMgRVCUSlcByz860UOclsOFHBlczfBaKy47vo=
-github.com/runZeroInc/excrypto v0.0.0-20240929210559-608d208b7065 
h1:/Dj751CaMI0hup6t+/TFqW852KVrou6za887nT0lhtE=
-github.com/runZeroInc/excrypto v0.0.0-20240929210559-608d208b7065/go.mod 
h1:ai0QX/YMgRVCUSlcByz860UOclsOFHBlczfBaKy47vo=
+github.com/runZeroInc/excrypto v0.0.0-20250629231451-7e62a70a4cc3 
h1:nHWx/OmoqfYMkTKY383N//ll3a36JjoszUqurhucPW4=
+github.com/runZeroInc/excrypto v0.0.0-20250629231451-7e62a70a4cc3/go.mod 
h1:Kk/CHQ2eYnHRYmR1eT1mA6L8LbNXA10fg8m8CqF6s2E=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod 
h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/sagikazarmark/locafero v0.6.0 
h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
-github.com/sagikazarmark/locafero v0.6.0/go.mod 
h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
-github.com/sagikazarmark/slog-shim v0.1.0 
h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
-github.com/sagikazarmark/slog-shim v0.1.0/go.mod 
h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sagikazarmark/locafero v0.10.0 
h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc=
+github.com/sagikazarmark/locafero v0.10.0/go.mod 
h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw=
 github.com/sirupsen/logrus v1.9.3 
h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod 
h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/sourcegraph/conc v0.3.0 
h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
-github.com/sourcegraph/conc v0.3.0/go.mod 
h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
-github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
-github.com/spf13/afero v1.11.0/go.mod 
h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
-github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
-github.com/spf13/cast v1.7.0/go.mod 
h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
-github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
-github.com/spf13/cobra v1.8.1/go.mod 
h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod 
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
-github.com/spf13/viper v1.19.0/go.mod 
h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
+github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 
h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
+github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod 
h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
+github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
+github.com/spf13/afero v1.14.0/go.mod 
h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
+github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
+github.com/spf13/cast v1.9.2/go.mod 
h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
+github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
+github.com/spf13/cobra v1.9.1/go.mod 
h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
+github.com/spf13/pflag v1.0.6/go.mod 
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
+github.com/spf13/pflag v1.0.7/go.mod 
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
+github.com/spf13/viper v1.20.1/go.mod 
h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
 github.com/stretchr/objx v0.1.0/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.7.0/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.9.0 
h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/stretchr/testify v1.9.0/go.mod 
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.10.0 
h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod 
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/subosito/gotenv v1.6.0 
h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 github.com/subosito/gotenv v1.6.0/go.mod 
h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
 github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
@@ -81,8 +71,6 @@
 github.com/weppos/publicsuffix-go v0.40.2 
h1:LlnoSH0Eqbsi3ReXZWBKCK5lHyzf3sc1JEHH1cnlfho=
 github.com/weppos/publicsuffix-go v0.40.2/go.mod 
h1:XsLZnULC3EJ1Gvk9GVjuCTZ8QUu9ufE4TZpOizDShko=
 github.com/yuin/goldmark v1.4.13/go.mod 
h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
-go.uber.org/multierr v1.11.0/go.mod 
h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod 
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod 
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.7.0/go.mod 
h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
@@ -90,10 +78,8 @@
 golang.org/x/crypto v0.19.0/go.mod 
h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.23.0/go.mod 
h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/crypto v0.25.0/go.mod 
h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
-golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e 
h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
-golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod 
h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
-golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 
h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
-golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod 
h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod 
h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod 
h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -110,8 +96,10 @@
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
-golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
-golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
+golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
+golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
 golang.org/x/oauth2 v0.6.0/go.mod 
h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -133,8 +121,8 @@
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
-golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod 
h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod 
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -145,8 +133,8 @@
 golang.org/x/term v0.17.0/go.mod 
h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.20.0/go.mod 
h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 golang.org/x/term v0.22.0/go.mod 
h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
-golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
-golang.org/x/term v0.24.0/go.mod 
h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
+golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
+golang.org/x/term v0.34.0/go.mod 
h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -158,8 +146,8 @@
 golang.org/x/text v0.14.0/go.mod 
h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod 
h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.16.0/go.mod 
h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
-golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
-golang.org/x/text v0.18.0/go.mod 
h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod 
h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod 
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod 
h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -168,8 +156,8 @@
 golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod 
h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
-gonum.org/v1/gonum v0.15.1/go.mod 
h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod 
h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
 google.golang.org/appengine v1.6.7/go.mod 
h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod 
h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod 
h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
@@ -178,8 +166,6 @@
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 
h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
-gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod 
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

++++++ vendor.tar.gz ++++++
++++ 65372 lines of diff (skipped)

Reply via email to