All notable changes to SSH Algorithm Security Scanner are documented here.
Improve IPv6 support: fix validation, add --prefer-ipv6 / --ipv6-only
sshscan.py: - Added ipv6_only parameter to resolve() - Added self.prefer_ipv6 and self.ipv6_only to SSHEnhancedScanner.init() - Added --prefer-ipv6 and --ipv6-only argparse arguments - Wired both flags in main() README.md: - Added --prefer-ipv6 and --ipv6-only to Scanning options table completion/sshscan.bash-completion: - Added --prefer-ipv6 and --ipv6-only
sshscan.py: - Fixed _is_valid_hostname() to accept IPv6 literals before the DNS-name regex - Fixed parse_host_string() to handle [::1] without port - Pass prefer_ipv4/ipv6_only to resolve() in parse_host_string() README.md: - Updated IPv6 notation note in Input File Formats - Updated IPv6 Troubleshooting entry
- add --list-algorithms
- update workflow with editor auto mode
update update_homebrew worflow and autom. workflow via update_homebrew.sh script
- Documentation and update script
- Jump host / bastion host support —
--jump-host [USER@]HOST[:PORT]routes all connections through an SSH jump host using OpenSSH's native-Jflag; supports chained hops via comma-separated list (e.g.--jump-host hop1.corp,admin@hop2.corp:2222) - Generic proxy support —
--proxy-command CMDpasses an arbitraryProxyCommandto SSH; enables SOCKS5 (nc -X 5 -x socks5host:1080 %h %p) and HTTP CONNECT (nc -X connect -x ...) - Per-host proxy in host files — each host entry in JSON/YAML can carry a
viadict (type,host,port,user); CSV files support four extra columns (via_type,via_host,via_port,via_user);.txtfiles use the global proxy flags ProxyConfigdata class — internal representation withto_ssh_args()andfrom_dict(); proxy priority: per-hostvia→--jump-host→--proxy-command→ direct connection- Banner scan through proxy — when a proxy is active the SSH banner is fetched via the SSH
binary with
LogLevel=VERBOSE(parsesremote software versionfrom stderr) instead of the raw socket path, which cannot traverse jump hosts or SOCKS/HTTP proxies - Config file keys —
jump_hostandproxy_commandadded to[scanner]; both are also configurable per-host in host files and overridable via CLI
- Extended
--filtertokens — six new tokens across two new groups:- Type tokens — filter by protocol layer:
cipher,mac,kex,hostkey- Composable with category tokens:
--filter kex,weakshows only weak KEX algorithms - Multiple types combine with OR:
--filter cipher,macshows both
- Composable with category tokens:
- Output-mode tokens — suppress algorithm detail lines:
security— show only security score and compliance per host (no algorithm lines)banner— show only the SSH banner per host (no algorithm lines)banner/securitycombined with type or category tokens re-enables those algo lines
- Type tokens — filter by protocol layer:
--list-filteroutput expanded with all new token groups and combined examples
- ANSI color output — live scan lines are color-coded by severity; summary sections use color for headers, counts, and scores
[x]supported → green /[-]unsupported → dim /[!]weak → yellow /[!]NSA high → red /[!]NSA medium → yellow- Banner line → cyan; scan header → bold; compliance PASS → green / FAIL → red; security score colored by range
- Summary: section headers bold, counts and stats colored, NSA section header bold red
- Auto-disabled when stdout is not a TTY (piped/redirected) or when
--formatexports to stdout
--no-color— force-disable ANSI color output--show-hostnames/-n— display original DNS names in output instead of resolved IPs; original name preserved through all input paths (stdin,--host,--fileall formats); stored asSSHHostResult.hostnameand included in exports- Summary host lists — the summary report now lists affected hosts by name in addition to counts:
- Failed scans: host + error type
- Compliance failures: non-compliant host list under the percentage line
- NSA risks: affected hosts with per-host high/medium risk counts
- Hardening Guide — new README section with ready-to-paste
sshd_configand~/.ssh/configsnippets for two security levels:- Balanced (no weak algorithms, NIST curves kept) — maps to NIST / BSI_TR_02102 / ANSSI
- Strict (no NIST curves, Curve25519/Ed25519 only) — maps to PRIVACY_FOCUSED
- Includes apply/verify workflow, Ed25519 host key generation, and per-host client override example
--filter TOKENS— Filter live output by algorithm category and/or host result- Algorithm tokens:
supported,unsupported,flagged,weak,nsa - Host tokens:
passed,failed,error - Combinable, e.g.
--filter nsa,failed
- Algorithm tokens:
--list-filter— Print all available filter tokens with descriptions--rate-limit N— Throttle scan submission to at most N new SSH connections per second; also configurable viarate_limitin[scanner]--timeout-banner SEC— Override the SSH banner grab timeout independently from--timeout; also configurable viabanner_timeoutin[scanner]--strict-host-key-checking MODE— Set SSH StrictHostKeyChecking (yes / no / accept-new); also configurable viastrict_host_key_checkingin[scanner]; default:accept-newSSHHostResult.from_dict()— Deserialize ato_dict()payload back into a fullSSHHostResultincludingSSHAlgorithmInfoobjects and parseddatetime- Stdin piping — Hosts can be piped via stdin when no
--host/--file/--localis given:echo "host1,host2:2222" | ./sshscan.py; any mix of commas, spaces, and newlines is accepted - Scan header — One-line summary printed before each batch scan showing host count, threads, timeout, and active options
- Exit codes — Structured exit codes:
0= clean,1= fatal/usage error,2= compliance failures,3= scan errors (connection/timeout)
sanitize_host_input()— Switched from character denylist to allowlist ([a-zA-Z0-9.\-:\[\]_%]); prevents bypass via uncommon characters- Inner parallelism —
AlgorithmTestermax_workers changed frommin(10, max_workers // 2)tomin(3, max_workers); eliminates potential 200+ concurrent SSH processes; threshold for using parallel algo scan lowered from> 10to> 1 --portnot applied to--filemode —default_portnow passed throughload_hosts_from_file()andparse_host_string()- ConfigValidator compliance default — No longer silently defaults to
NISTwhen no framework is configured; compliance check is skipped unless explicitly set - Compliance
requiredcheck —bool(required & supported)replaced withrequired.issubset(supported); previously passing with only one matching required algorithm minimum_scorenever enforced —security_scorenow passed tocheck_compliance();score_meets_minimumincluded in resultshow_nsa_warnings/--no-nsa-warnings— NSA analysis always runs; flag controls display only; NSA data still included in all exports- IPv6 banner scan —
socket.create_connection()replacessocket.socket(AF_INET)to correctly handle IPv6 hosts - Deduplication logging — Added
_skippedcounter to all six dedup paths; log message now only fires when count > 0 --list-frameworks— Fixed empty descriptions; now shows the framework'snamefieldSetmissing from typing import — AddedSettofrom typing import ...
Config files now use standard INI format (parsed by Python's built-in configparser) instead of TOML.
This removes the toml external dependency entirely — only PyYAML remains.
| Before | After |
|---|---|
config.toml |
sshscan.conf |
privacy_focus_config.toml |
privacy_focus.conf |
framework = "NIST" |
framework = NIST (no quotes) |
Auto-discovery order: ./sshscan.conf (local) → ~/.conf/sshscan.conf (user) → /etc/sshscan/sshscan.conf (system).
SSHMultiplexer— Removed entirely; ControlMaster multiplexing caused false positives (multiplexed slaves ignore-o Ciphers/MACs/Kexflags) and had an unresolvable TOCTOU race condition inestablish_master()- tqdm /
TQDM_AVAILABLE— Replaced with a lightweight built-inSpinnerclass (stderr, no external dependency, TTY-aware); eliminatesglobal TQDM_AVAILABLEanti-pattern - Scan resume —
ResumeManager,--resume,--list-scansremoved from scope; pickle-based state was an RCE risk
setup_logging()— Structured logging with three levels: DEBUG (--debug), INFO/VERBOSE (--verbose), WARNING (default)--debugflag — Enables full function-level debug output including line numbers
EnhancedDNSCache— Thread-safe TTL cache with IPv6 support, background cleanup thread, and hostname validationConfigValidator— Validates all config values with explicit ranges before use; emits warnings for invalid valuesAlgorithmTester— Handles inner-host algorithm parallelism with configurable worker countKNOWN_ALGORITHMS— Authoritative hardcoded algorithm list covering all known SSH algorithms including those removed from modern OpenSSH; supplementsssh -QoutputNSABackdoorDetector— Static analysis flagging NIST/NSA-linked algorithms (NIST P-curves, certain ECDH/ECDSA variants) with risk levelsComplianceFramework— Five built-in frameworks:NIST,FIPS_140_2,BSI_TR_02102,ANSSI,PRIVACY_FOCUSED--list-frameworks— List available compliance frameworks--compliance FRAMEWORK— Check hosts against a compliance framework--explicit ALGOS— Test a specific comma-separated list of algorithms instead of the full set--local— Scan the local SSH server (127.0.0.1)--summary/--summary-only— Print aggregated summary after scan;--summary-onlysuppresses live output (with spinner)--no-nsa-warnings— Suppress NSA risk annotations in live output--format/--output— Export results as JSON, CSV, or YAMLSSHHostResult.to_dict()— Serialize result to plain dict; timestamp as ISO stringSSHHostResultfields — Addederror_type,timestamp,retry_countSSHAlgorithmInfo.__hash__()— Enables use in setssanitize_host_input()— Strips shell metacharacters from host inputvalidate_port()— Range-check 1–65535 with clear errorretry_on_failuredecorator — Exponential backoff with configurable attempts and exceptionsmlkem768x25519-sha256— Added toKNOWN_ALGORITHMS['kex'](ML-KEM post-quantum KEX)hmac-sha1variants — Added toWEAK_ALGORITHMS['mac']
- TOML config ignored —
__init__now reads from nestedconfig['scanner']/config['compliance']sub-dicts - CLI args always overriding config — All overridable args use
default=None; only applied when explicitly provided - SSH port format — All SSH commands now use
-p {port} {host}instead of{host}:{port} str in bytesTypeError — Rejection patterns changed tobytesliterals (b'no matching cipher found')- Path traversal in control socket —
get_control_path()sanitizes hostname withre.sub(r'[^\w.-]', '_', host) retry_countnever incremented — Dead code removed from retry decoratorDict[str, any]— Changed toDict[str, Any](capitalA, fromtyping)- IPv6 host parsing —
[::1]:22bracket notation handled via regex before the generic:split ConnectionErrorshadowing builtin — Custom exception renamed toSSHConnectionErrorurlparseunused import — Removedimport ioinside method — Moved to top-level importsUserKnownHostsFile=/dev/null— Added to all SSH commands;LogLevel=ERRORreplacesLogLevel=quietget_local_ssh_algorithms()called per host — Now cached inself._local_algorithms_cacheafter first call
- TOML config auto-discovery:
--config FILE,~/.sshscan/config.toml,~/.sshscan.toml,/etc/sshscan/config.toml,/etc/sshscan.toml [scanner]keys:threads,timeout,retry_attempts,dns_cache_ttl,banner_timeout,rate_limit,strict_host_key_checking[compliance]keys:framework
Original version. 16 documented bugs across config parsing, port format, compliance logic, security, and type errors. See project history for details.