Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
Changed¶
- Updated GitHub Actions pins for Node 24-compatible action releases while keeping full commit-SHA hardening:
actions/checkout6.0.3,actions/setup-python6.2.0,actions/upload-artifact7.0.1,actions/create-github-app-token3.2.0, andgithub/codeql-action4.36.1. - Pinned compatibility smoke runners to
windows-2025-vs2026andmacos-26, and added.python-versionso GitHub's Automatic Dependency Submission uses Python 3.11 instead of the ambient runnerPATH.
[0.7.2] - 2026-05-30¶
Fixed¶
- Made the protected publish job idempotent after partial PyPI uploads, added a manual recovery mode for already-published versions, configured Git identity before annotated tag creation, and finalized changelog sections for both new and recovered release-version PR branches before release-note extraction.
Changed¶
- Removed SHA-1 from
CryptographyBackend.HASH_ALGORITHMS— SHA-1 now raisesCryptoExceptionif requested, enforcing the no-SHA-1 security invariant at the backend layer. - Replaced all
default_backend()calls (kex.py,backend.py,pkey.py) with the implicit default introduced incryptography>=36.0.0, eliminating deprecation warnings. - Moved
_AEAD_CIPHERSfrozenset to module level intransport.py— was being re-allocated on every_build_packet()call. - Version-string silent fallbacks in
_compute_ecdh_exchange_hash,_compute_curve25519_exchange_hash, and_compute_exchange_hashreplaced with explicitCryptoExceptionguards — a missing version string now fails loudly instead of producing a wrong exchange hash. - Dead
try: … except Exception: raisewrapper removed from_perform_dh_group14_sha256. KeyExchange.generate_keys()deprecated withDeprecationWarning; will be removed in v1.0.WarningPolicydocstring corrected to accurately describe TOFU behaviour;AutoAddPolicynow emits alogger.warningalongside the existingUserWarning.- Hardcoded
"utf-8"literals inssh_client.pyreplaced withSSH_STRING_ENCODING. - Added
[tool.isort]and[tool.black]configs topyproject.tomlso both tools align with ruff's formatting style.
Fixed¶
MSG_EXT_INFO = 7added toprotocol/constants.py; magic literals7and60intransport.pyreplaced with named constants (MSG_EXT_INFO,MSG_USERAUTH_PK_OK).- Dead KEX fallback in
KeyExchange.start_kex()replaced with an explicitCryptoException— the old path would have sent a spurious secondKEXINITin server mode. assertstatements in crypto and KEX paths replaced with explicitif … raise CryptoExceptionguards, which survive Python's-Ooptimisation flag._kex_threadand_server_keydeclared inTransport.__init__— previously only set viagetattrfallbacks, causing undeclared-attribute mypy warnings.- Misleading
_recv_byteslock-ordering comment updated to accurately describe the fast-path vs slow-path locking behaviour. SSHClient.save_host_keys()no longer accesses private_filenameand_keysattributes ofHostKeyStorage; newHostKeyStorage.copy_from()method provides the correct encapsulated API.SSHClient.connect()now raisesConfigurationExceptionimmediately forcompress=Trueandgss_kex=Truerather than silently ignoring them.
Added¶
- Public project policy docs for production usage, compatibility, API stability, release policy, governance, dependency handling, repository settings, CI policy, release operations, release verification, vulnerability response, architecture/security boundaries, logging operations, and public roadmap tracking.
- Architecture Decision Records for release policy, documentation layout, supported platforms, and API stability boundaries.
- Root GitHub entry points for contributing and support so GitHub, Read the Docs, and repository visitors all route to the maintained documentation source.
Changed¶
- Restructured GitHub Actions so PR validation runs through one sequential orchestrator and expensive reusable workflows do not launch independently on PRs or
mainpushes. - Moved post-merge CodeQL and full security scanning into the sequential release orchestrator for
mainpushes, preserving security coverage while avoiding parallel free-tier runner contention. - Documented the protected release-version PR flow, artifact integrity expectations, and maintainer release runbook.
- Replaced the oversized
meta/CONTRIBUTING.mdcopy with a short pointer to the maintained public contributor guide. - Rebuilt the Docker integration stack around locally maintained OpenSSH and Dropbear services so real-server workflow tests can run without relying on a removed public Dropbear image.
Fixed¶
- Switched protected release-version PR creation to the release GitHub App token and made the step retry PR creation when the release branch already exists without an open PR.
- Limited protected release-publish detection to the canonical first commit-message line so
[publish release]text in a merge body cannot accidentally enter the release-publish path. - Restricted protected release-publish mode to merged PRs from the generated
release/vX.Y.Zbranch with source PR metadata, so regular PR titles cannot trigger the publish path. - Prevented Docker SSH integration tests and local benchmarks from sharing host-key files across OpenSSH, Dropbear, or the developer's real home directory.
- Raised the documentation dependency floor to
pymdown-extensions>=10.21.3, pinned the Alpine test image digest, and documented path-scoped Trivy ignores for intentional SSH test-fixture Dockerfile findings. - Folded the open Dependabot GitHub Actions pin updates into the release-scan fix:
actions/checkout6.0.2,astral-sh/setup-uv8.1.0,codecov/codecov-action6.0.1,github/codeql-action4.36.0, andossf/scorecard-action2.4.3.
Verification¶
- Read the Docs continues to build from
.readthedocs.yamlthroughmkdocs.yml; newly added public docs are explicitly surfaced in the MkDocs navigation rather than being included by the Read the Docs config directly.
[0.7.1] - 2026-05-29¶
Summary¶
This maintainer-facing readiness entry documents the repository lifecycle work released in 0.7.1.
[0.7.0] - 2026-05-16¶
Added¶
- ChaCha20-Poly1305 cipher: Full
chacha20-poly1305@openssh.comimplementation - encrypt, decrypt_length, and decrypt_body - added toCryptographyBackend. Registered as the preferred cipher inCipherSuite(first inENCRYPTION_ALGORITHMS), withAEAD_CIPHERSsentinel set andkey_len: 64entry inCIPHER_INFO. - ChaCha20 sync transport path:
_encrypt_packetand_recv_packetinTransporthandle ChaCha20-Poly1305 natively via the two-key AEAD construction (64-byte key split into body key + header key, Poly1305 tag over enc_length + enc_body). - ChaCha20 async transport path:
_recv_packet_asyncinAsyncTransportimplements the AEAD inbound path - reads enc_length (4 B), decrypts length, reads enc_body + Poly1305 tag (16 B), verifies and decrypts body. - Strict-KEX sequence reset: After sending
NEWKEYSwith strict-KEX active (kex-strict-c-v00@openssh.com), the outbound sequence number is reset to 0 in the async path (Terrapin defense, RFC 9142). - SFTP
limits@openssh.comnegotiation:SFTPClient._query_limits()sendsSSH_FXP_EXTENDEDafter version negotiation and parses the 4×uint64 reply to obtainmax_write_len. On OpenSSH servers this raises the write chunk from 64 KB to 255 KB, reducing round trips for a 1 MiB upload from ~16 to ~4. PacketProfiler: Optional per-stage packet timing. SetSPINDLEX_PROFILE=1to record build / encrypt / socket-write latencies for every sent packet; calltransport._profiler.summary()for median and P95 per stage.- Algorithms reference page: New
docs/algorithms.mdcovering all supported KEX, host-key, cipher, and MAC algorithms with preference order, notes, and an explicit exclusion list.
Changed¶
- Preferred cipher: ChaCha20-Poly1305 is now the first cipher advertised in
CipherSuite.ENCRYPTION_ALGORITHMS. - KEX signal token: Corrected from
kex-strict-c-v01@openssh.comto the standardkex-strict-c-v00@openssh.com. _packet_buffer: Changed frombytestobytearrayinTransport, eliminating O(n) copies on buffer advance.- SFTP write chunk:
SFTPFile.write()andSFTPClient.put()now use the negotiated_max_write_len(default 64 KB, up to 255 KB on OpenSSH) instead of a hardcoded constant. validate_packet_structure: Minimum packet body relaxed to 8 bytes for AEAD cipher compatibility.- Writer presence check: In
_send_message_async, the_writerguard is now checked before packet build/encrypt.
Fixed¶
- mypy
Optional[bytes]narrowing for_chacha20_key_out/_chacha20_key_inat all call sites. - Import order in
sftp_client.py(transport imports moved above module-level constant).
[0.6.11] - 2026-05-12¶
Performance¶
- Async throughput overhaul: SpindleX async SFTP now outperforms other leading SSH libraries on handshake, upload, and download benchmarks (LAN: ~42 ms handshake vs ~65 ms; ~14 ms/MiB upload vs ~15 ms/MiB; ~13 ms/MiB download vs ~14 ms/MiB).
- Native async packet receive: Replaced
asyncio.to_thread(super()._read_message)with_recv_packet_async(), which reads directly from the asyncioStreamReader. Eliminates two cross-thread context switches per received SSH packet. - Threshold-based write drain:
_send_message_async()no longer callsdrain()after every packet. Drain fires only when the asyncio write buffer exceeds 64 KB, eliminating ~32 event-loop yields per 1 MiB SFTP upload window. - Doubled SFTP pipeline depth:
_WINDOWand_PIPELINE_DEPTHincreased from 32 to 64, keeping up to 2 MiB of SFTP reads/writes in flight (was 1 MiB). - Larger channel window:
DEFAULT_WINDOW_SIZEraised from 2 MiB to 4 MiB so the server can push larger bursts before needing a window adjustment. - Socket tuning:
TCP_NODELAYenabled on async connections to suppress Nagle coalescing on control packets;SO_SNDBUF/SO_RCVBUFraised to 1 MiB each. - Optimized SFTP message limits: Raised
MAX_MESSAGE_SIZEto 1 MiB andSFTP_MAX_PACKET_SIZEto 64 KB, providing a 2x throughput improvement for SFTP data payloads while maintaining standard compatibility.
Fixed¶
- SFTP large write protocol crash: Implemented automatic 64 KB chunking in
SFTPClientandAsyncSFTPClientto preventProtocolException: String too longand connection resets when sending large data buffers. - Async connection reliability: Added a robust retry mechanism with exponential backoff and jitter to
AsyncSSHClient.connect(), covering the entire handshake phase to mitigate transient server-side resets (MaxStartups). - SFTP pipelining integrity: Fixed a potential data corruption bug where unhandled short reads during pipelined transfers could lead to offset drift.
- Unified exception propagation: Standardized
AsyncTransportto preserve specificSSHExceptionsubclasses (likeCryptoException) during initialization, ensuring parity with the synchronous transport's error reporting. - Append mode in SFTP:
SFTPClient._mode_to_flags()now correctly setsSSH_FXF_APPENDfor"a"open mode (previously the flag was defined but never used). key_passwordparameter:SSHClient.connect()andAsyncSSHClient.connect()now accept a dedicatedkey_passwordargument for encrypted private keys, separate from the loginpassword.SSHClient.usernameproperty: Added a read-onlyusernameproperty that returns the authenticated username afterconnect(), orNoneif not connected.ChannelFile.read()exception handling: Narrowed the exception catch fromExceptiontoChannelExceptionso only genuine channel errors (timeout, closed) are handled; other errors propagate correctly.put_recursive()mkdir error handling:SFTPClient.put_recursive()andAsyncSFTPClient.put_recursive()now only suppressSSH_FX_FAILURE(directory already exists); other SFTP error codes propagate as before._expect_message()transport closed: Changed a silentreturn Nonetoraise TransportException("Transport closed")so callers are not handed aNonemessage when the transport shuts down mid-wait.- Rekey counter reset: Removed premature
_bytes_since_rekeyand_last_rekey_timeresets inside_check_rekey()that ran before the KEX thread completed, potentially skipping a required rekey cycle. HostKeyStorageload errors: The constructor now silently ignoresFileNotFoundError(normal on first use) but logs a warning for any other load error, rather than silently ignoring everything.HostKeyStoragemarker: Thesave()method now writesspindlexas the library name in the known-hosts comment instead of the genericssh_library.WarningPolicyTOFU:WarningPolicy.missing_host_key()now stores and persists the host key on first contact (trust-on-first-use), matching the documented behavior.- Keyboard-interactive fallback removed:
AsyncSSHClient._authenticate()no longer silently attempts keyboard-interactive as a final fallback when no credentials are provided, preventing unexpected interactive prompts.
Improved¶
configure_sanitizing_logging()public API: Added a convenience function to attachSanitizingFilterto any logger by name, making it easy to redact credentials from application logs.- Async channel close safety:
AsyncChannel._handle_close()now schedules the EOF+CLOSE response as anasyncio.ensure_futuretask rather than calling_send_message()synchronously, preventing a potential event-loop deadlock in the native async receive path. - Transport architecture: Extracted
_dispatch_packet()from_read_message()in the baseTransportclass so the async path can reuse the same packet dispatch logic without the thread bridge. - Bandit/ruff noise reduction:
AUTH_PASSWORD = "password"now carries both# noqa: S105and# nosec B105markers with an explanatory comment; the four incorrect global ruff ignores (S105,S106,S303,S324) were removed frompyproject.toml. - Code hygiene: Removed all
# Bug #N Fixed:inline comments, a deadif TYPE_CHECKING: passblock, and a misleading comment in_recv_version(). - Benchmark scripts:
AutoAddPolicy(accept_risk=True)used in benchmark scripts to suppress the host-key warning during testing.
[0.6.10] - 2026-05-08¶
Added¶
- Modern NIST Curve Support: Added full support for
ecdh-sha2-nistp384andecdh-sha2-nistp521key exchange algorithms, andecdsa-sha2-nistp384andecdsa-sha2-nistp521host key algorithms. - RSA-SHA2-512 Support: Added support for the
rsa-sha2-512host key algorithm. - SFTP API Parity: Implemented
lstat,symlink, andreadlinkin bothSFTPClient(sync) andAsyncSFTPClient(async) to ensure interface consistency. - Enhanced Configuration: Integrated
SPINDLEX_LOG_LEVELandSPINDLEX_BUFFER_SIZEenvironment variables for better external control over library behavior. - Benchmarking Suite: Updated
scripts/benchmark_compare.pyandscripts/benchmark_ciphers.pyto cover all newly added algorithms and modern cryptographic configurations.
Fixed¶
- NIST Curve Key Derivation: Fixed a critical bug in
KeyExchangewhere the incorrect hash algorithm (SHA-256) was being used for NIST P-384 and P-521 curves, causing authentication failures. - Beta Release Planning: Updated release metadata parsing so
featurePRs remain patch releases during beta stabilization,feature-minorexplicitly advances the beta minor line, and breaking beta changes map to minor releases until the stable1.0.0policy is enabled. - Release PR Detection: Added retry and merge-message fallback behavior for push-triggered release planning so GitHub API association lag does not skip a release immediately after merge.
- Type Safety & Linting: Resolved over 35 type errors and linting issues identified by
mypyandruff, particularly aroundbytes/bytearraybuffer handling. - Regression Repairs: Fixed
KeyExchangeunit tests by implementing backward-compatibility aliases and normalizing error messages.
[0.6.9] - 2026-05-05¶
Fixed¶
- Routed the intentionally gated legacy
ssh-rsaSHA-1 compatibility path through a dedicated helper so Semgrep no longer reports the release-blocking directhashes.SHA1()calls.
[0.6.8] - 2026-05-05¶
Summary¶
This patch release closes the remaining code-scanning items that can be fixed in source control after the v0.6.7 security pipeline rollout.
Security¶
- Raised the minimum supported
cryptographyversion to>=46.0.7to avoid the vulnerable range reported by OSV/Scorecard for GHSA-p423-j2cm-9vmq. - Expanded the root
SECURITY.mdwith reporting channels, expected report contents, disclosure timeline, practices, and threat model details so repository security policy scanners can evaluate the maintained policy directly. - Moved direct workflow
python -m pip installverification steps touv pip install --python ...so Scorecard no longer reports un-hashed pip commands for the isolated release and license-audit virtual environments. - Tightened Semgrep suppressions around the intentionally gated legacy
ssh-rsaSHA-1 compatibility path. - Added a repository
CODEOWNERSfile so branch protection can require code owner review.
[0.6.7] - 2026-05-05¶
Summary¶
This release hardens the project lifecycle around release automation, PR gates, security scanning, and incident triage. It resolves the Scorecard/OSV dependency findings reported after v0.6.6, adds corporate-oriented runtime dependency license checks, and turns the CI/CD system into the main release safety surface for future patch releases.
Security¶
- Raised the minimum supported
cryptographyversion to>=46.0.6, avoiding the vulnerable ranges reported by OSV/Scorecard for the runtime dependency. - Raised documentation dependency floors for
pymdown-extensions>=10.16.1andpygments>=2.20.0to avoid known vulnerable documentation-build dependency ranges. - Added a runtime dependency license audit to the PR gate and scheduled security workflow. The audit fails on denied corporate-risk licenses such as GPL, LGPL, AGPL, SSPL, BUSL, and proprietary/commercial markers, and uploads a dependency license report from the full security workflow.
- Added a full scheduled security workflow covering Semgrep, pip-audit, Gitleaks, Trivy, CodeQL SARIF upload, and OpenSSF Scorecard.
- Documented project security policy and vulnerability reporting expectations in public and maintainer-facing security docs.
- Kept legacy
ssh-rsaSHA-1 handling explicitly gated behindallow_sha1=Truewhile adding scanner suppressions that document the intentional compatibility path.
CI/CD¶
- Added a fast PR gate with stable PR type parsing, ruff, mypy, unit tests, docs build, workflow linting, security checks, and an aggregate quality gate.
- Split compatibility and Docker-backed integration checks into reusable workflows used by both PR validation and release dry runs.
- Added Linux Python 3.9-3.13 coverage plus Windows and macOS smoke coverage for the compatibility matrix.
- Added full SHA pin dereferencing for GitHub Actions usage and tightened code-scanning-friendly workflow configuration.
- Updated Trivy action usage and fixed Scorecard workflow permissions.
- Removed legacy duplicated CI and release helper entry points in favor of the root
Makefileand the dedicated reusable workflows.
Release Automation¶
- Added PR-driven release planning that maps PR type checkboxes to patch, minor, major, or no-release outcomes.
- Added release dry-run validation for PRs so release planning, version sync, build, and publish checks are exercised before merge.
- Added changelog enforcement for release-producing PRs so bug, feature, and breaking changes cannot merge without changelog updates.
- Added changelog-section extraction for GitHub Release notes so automated releases keep the same detailed release-note shape as previous manual releases.
- Added workflow failure tracking that opens or updates CI failure, flaky pipeline, and release-blocked incident issues with structured templates.
- Added release publish and partial-publish triage hooks to preserve release failure context for maintainers.
Documentation¶
- Moved internal lifecycle planning docs out of the public documentation site and into
meta/internal/lifecycle. - Added lifecycle epics, stage gates, release-process epics, quality/security epics, product-readiness epics, release-candidate planning, operations epics, failure-handling guidance, and implementation roadmap docs.
- Updated contributing docs and PR guidance to match the new stable PR type tokens and release automation.
- Refreshed badges and operational references after the workflow cleanup.
Tests¶
- Added tests for PR body validation, release planning/version sync, changelog enforcement, workflow failure tracking, and dependency license policy checks.
- Added
pytest-timeoutto the test tooling used by the split CI workflows.
Removed¶
- Removed the legacy
.github/workflows/ci.ymlworkflow after the PR gate, matrix, integration, release, and security workflows took over its responsibilities. - Removed obsolete
scripts/release.py,scripts/build.sh, andscripts/Makefilehelpers.
[0.6.6] - 2026-05-01¶
Summary¶
This release is a broad hardening pass across every layer of the library - SFTP client/server, transport, key exchange, async forwarding, logging, and host key verification. It resolves 37 issues identified since v0.6.5, including critical resource leaks, protocol correctness bugs, race conditions, and API/documentation gaps.
Fixed¶
SFTP & Client * ChannelFile.close() left the underlying channel open, leaking resources (#71). * AsyncSFTPClient request timeout, pipelining failures, and sentinel ID collision when request ID rolled over to 0 (#82, #84, #125). * SFTPClient timeout handling, write offset not advancing between chunks, handle leak on flush error, and unused object instantiation (#80, #81, #95, #112). * SFTPServer error code misclassification, file-descriptor leak when handle limit was reached, and magic numbers replaced with named constants (#100, #102, #116). * read_string() size limit was too small for large SFTP payloads, silently truncating data (#89). * NameError: os module not imported in AsyncSFTPClient. * Fixed gaps between public API documentation and implementation: SFTP pipelining parameters, recursive transfer methods, host key convenience methods, and port range validation.
Transport & Channels * Multiple transport issues: _recv_message incorrect return type, dead channel message handlers, KEX race condition, and deadlock risk under concurrent channel use (#72, #73, #75, #91, #92, #94, #106). * Lock release/acquire exception safety in channel.send() - lock could be permanently held on exception (#70). * ChannelExtendedDataMessage was missing the data length prefix, violating the SSH wire format (#69).
Key Exchange * DH exchange hash mpint encoding was incorrect, causing handshake failures with some servers (#66). * Signaling tokens (ext-info-c, kex-strict-c-v01@openssh.com) missing from client KEXINIT (#86). * KEX session ID guard: session ID was not validated before use (#78). * mpint contract documented and enforced in derive_key() to prevent silent misuse (#67). * Message type 60/61 aliasing (MSG_USERAUTH_PK_OK vs MSG_USERAUTH_INFO_REQUEST) documented with explicit dispatch guidance (#88, #105).
Async & Concurrency * AsyncChannel buffer data race eliminated; threading import moved to module level (#97, #110). * Deprecated asyncio.get_event_loop() replaced with asyncio.get_running_loop() in async_transport.py (#103).
Security & Protocol * Async host key verification could silently bypass the check, accepting any host key (#68). * RSAKey reported the wrong algorithm name in signatures; ECDSAKey now supports the OpenSSH wire format (#74, #85). * WarningPolicy.missing_host_key() called get_name() instead of algorithm_name, raising AttributeError on every unknown host (#114). * SSHFormatter applied log sanitization twice, garbling already-sanitized records (#111). * Log sanitizer regex tightened to avoid false positives on non-sensitive fields (#124). * GSSAPI authentication context was not actually released in cleanup() (#87).
Server * SSHServer leaked transport objects and had a race condition during connection teardown (#98, #99).
Forwarding * Async forwarding: relay writer not properly closed on tunnel teardown; tunnel.tasks set grew unboundedly, leaking memory (#104, #120). * Dead else branch and NameError in synchronous forwarding.py (#117, #119).
Logging * os.makedirs('') crash when a log file path had no directory component (bare filename) (#101, #113).
Tests * Fixed TestGenerateSessionKeys by initializing _session_id to None before use. * Fixed hanging SFTP unit tests caused by incorrect initialization sentinel value. * Fixed test_known_key_match_passes unit test. * Skipped test_async_sftp_file_open and test_async_sftp_open_read_write in the real-server suite due to known timeout issues.
Removed¶
chacha20-poly1305@openssh.comdropped from the cipher list. The AEAD construction requires a fundamentally different packet framing (no separate MAC field, length encrypted separately) that is not yet implemented. Removed to prevent negotiating a cipher the transport cannot correctly handle.aes128-gcm@openssh.comandaes256-gcm@openssh.comdropped for the same reason - GCM AEAD requires the same alternative framing path as ChaCha20-Poly1305. Both will be re-introduced in a future release once AEAD framing support is implemented.
Changed¶
scripts/benchmark_ciphers.py: removed the-ppassword CLI argument in favour of an interactive prompt to prevent credentials appearing in shell history (#123).
Code Health¶
- Applied isort, black, ruff, and mypy fixes across the full library and test suite.
- Removed redundant
_write_stringdelegation inpassword.py(#108). - Moved
asyncioimport to local scope inkeyboard_interactive.py(#109). SFTPErrordocstring corrected to reference the right RFC section (#122).
[0.6.5] - 2026-04-26¶
Performance¶
- SFTP 32-deep pipelined request window: Replaced the strict send-one-wait-ACK-repeat loop in
SFTPFile.read(-1)andSFTPFile.write()with a sliding window of 32 concurrent in-flight SFTP requests. Benchmark against a LAN server (1 MiB file): upload 79 ms → 14 ms (now ~1.6× faster than other SSH libraries), download 50 ms → 15 ms (on par with other SSH libraries).SFTPClient.get()andSFTPClient.put()also use equivalent pipelining.
Fixed¶
- Transport rekeying deadlock:
_recv_messageand_expect_messagecalledself._stop_event.wait(0.1)while holdingself._lock.threading.Event.wait()does not release locks, starving the KEX thread and causing rekeying to deadlock until pytest-timeout killed it. Fixed by moving thewait()call outside thewith self._lock:block. - SFTP messages > 32 KB silently truncated:
_send_message()usedchannel.send()which sends onlymin(data, window, max_packet_size)bytes, silently dropping the remainder. Switched tochannel.sendall()so full SFTP messages are always transmitted. - Transport cross-talk and deadlock: Resolved a set of interleaved-lock and cross-talk issues in the transport layer that could cause hangs under concurrent channel use.
- Async authentication gaps: Implemented missing async authentication methods; removed test console interaction bugs that caused CI to hang.
- KEX public-key parsing and algorithm negotiation: Fixed edge cases in key-exchange public-key parsing and algorithm selection that could break handshakes with certain server configurations.
AutoAddPolicysecured with opt-in flag:AutoAddPolicynow requires an explicit opt-in and logs aDeprecationWarning; host-key persistence errors are surfaced instead of silently swallowed.- IPv4 hardcoding removed: All internal socket calls now work with IPv6 hosts; port validation added to reject out-of-range values early.
- Python 3.9 compatibility: Replaced
X | Yunion syntax withOptional[X]throughout to satisfy mypy on Python 3.9 targets. ForwardingTunneltype definition: Corrected the type alias so mypy no longer reports attribute errors for forwarding address tuples.- Missing
import asyncioinkeyboard_interactive.py: Added the missing import; removed a redundant localimport threadinginsideTransport.close()that shadowed the module-level import.
Added¶
_receive_message_for_id(): NewSFTPClienthelper that reads SFTP responses and buffers out-of-order ones by request ID, enabling true request pipelining without response mismatches._flush_write_queue(): NewSFTPFilemethod that drains all deferred write ACKs onclose(), ensuring write errors are always surfaced before the file handle is released.- CI coverage split: Unit tests upload with
flag=unit; Docker/integration tests upload withflag=integration. Codecov merges both per commit so real SSH server test coverage contributes to the overall reported percentage. - Docker-tests timeout: Added
timeout-minutes=20at the job level and--timeout=120per test viapytest-timeoutto prevent indefinite CI hangs when SSH containers are slow to respond.
Changed¶
- Test suite overhauled: Removed nine redundant mock-heavy transport/SFTP test files; replaced with focused unit tests for auth, client, channel, kex, sftp, and logging modules. Expanded
real_servercoverage and updated CI to split unit and Docker-based integration suites cleanly. - README modernized: Dark grey/purple theme, improved navigation, restored beta warning block, fixed XML entity error in logo URL, removed outdated demo paths.
typing-extensionsdependency: Updated minimum requirement to>=4.15.0.
[0.6.4] - 2026-04-23¶
Fixed¶
- Sync recv()/recv_stderr()/send_channel_request flat 100 ms penalty: in sync mode each of these waited up to 100 ms on a
threading.Eventbefore drivingTransport._pump(), but nothing else sets that event without a background pump thread. They now drive_pump()directly when no_kex_threadis present, withselect()to bound the wait when a channel timeout is set. Warmexec_commanddropped from ~215 ms → ~5 ms; large reads from ~5.3 s → ~20 ms; SFTP upload from ~8.4 s → ~80 ms. - SFTP downloads stalling after ~2 MiB:
Channel._adjust_windowwas incrementing_local_window_sizeand the transport-side helper was incrementing it again, double-counting the local view of the advertised window. The threshold check then stopped firing, no furtherWINDOW_ADJUSTpackets were sent, and the server's view of our window expired. Removed the duplicate increment; transport-side bookkeeping is now the single source of truth. _recv_byteslock scope:_read_lockwas held for the entire receive flow, including the buffer-served fast path. Restructured so_lockguards short non-blocking buffer slices and_read_lockis only held around the actual blockingsocket.recv. Threads that already have buffered data return without contending with a peer blocked inrecv.- Two-lock deadlock in
Transport.close(): heldself._lockwhile callingChannel.close(), which re-takes its own lock and then_close_channelwhich re-takesself._lock- inverting the order taken by concurrentChannel.close()callers. Snapshot channels under the lock, drop it, close each channel, then re-acquire briefly to clearself._channels. Channel.sendhalved the effective remote window: bothChannel.send()andTransport._send_channel_data()were decrementing_remote_window_size, causing premature flow-control stalls. Removed the duplicate decrement on the transport side; the defensive size check is preserved.- SFTPServer path traversal hardening:
_resolve_pathnow usesrealpathcontainment checks and rejects NUL bytes; exception handlers narrowed.
Security¶
- Strict-KEX / Terrapin defense: the extension filter listed
kex-strict-{c,s}-v00@openssh.com, but real implementations (and our own transport) advertise/detect the v01 spelling. The v01 marker leaked through the negotiator and the Terrapin defense could silently fail to activate against real OpenSSH peers. Filter now matches v01;CipherSuite.negotiate_algorithmsalso explicitly excludes all strict-KEX /ext-infomarkers from the KEX category and iterates the client's preference order per RFC 4253 §7.1. - Strict-KEX sequence-number reset and channel-open hardening in transport.
- Public-key auth signature algorithm bug: the algorithm name was hardcoded as
ssh-rsain signatures regardless of the negotiated algorithm - fixed. - Atomic rekeying state transitions under lock; aligned inbound MAC sequence-number wrap with the outbound path.
- Global logging sanitizer bypass: child loggers escaped sanitization; routed through a
LogRecordFactoryhook so the sanitizer applies uniformly. - SHA-1 RSA gated: signatures over SHA-1 now require an explicit
allow_sha1=Trueand emit aDeprecationWarning. - Defaults shifted to secure-by-default: docs, examples, and shipped demos now lead with
RejectPolicyand a known_hosts helper instead ofAutoAddPolicy.
Added¶
Channel/Transportcontext-manager support (with channel:/with transport:).AsyncSFTPClient.rename/chmod/normalizeimplementations.scripts/benchmark_compare.py: cross-library SSH/SFTP benchmark vs other SSH libraries across handshake, exec_command (small + ~1.4 MB), SFTP upload/download, and 10 parallel handshakes. Per-library failures are isolated and rendered asFAILED -- <error>instead of aborting the run.
Changed¶
- Test layout: ~50 test files reorganized into per-component subfolders (
auth/,channel/,client/,crypto/,hostkeys/,log/,misc/,protocol/,real_server/,sftp/,transport/); imports re-grouped per first-party isort rules. - CI: added
real-server-testsjob (OpenSSH Docker);real_servermarker excluded from the unit-tests job. - Channel timeout no longer hangs: replaced blocking
_pump()withselect()-bounded waits when a channel timeout is set. - Project description: dropped the technically inaccurate "pure-Python" claim.
[0.6.3] - 2026-04-20¶
Fixed¶
- RSA Authentication: Refactored
RSAKeyto dynamically support SHA-2 signature algorithms (rsa-sha2-256,rsa-sha2-512), resolving "Authentication Failed" errors on modern OpenSSH servers. - Transport Stability: Resolved a potential
AttributeErrorin the transport layer by adding missing attributes to internal sentinel messages. Cleaned up noisy debug logs for better terminal readability. - SFTP Robustness: Fixed a critical logic bug in
SFTPFile.read(-1)that caused crashes when downloading files without an explicit size. - Protocol Compliance: Corrected RSA signature verification to support legacy SHA-1 signatures while maintaining modern SHA-2 defaults.
[0.6.2] - 2026-04-20¶
Added¶
- ProxyJump Support: Added
sockparameter toSSHClient.connect()andAsyncSSHClient.connect(), enabling connections via existing channels (bastion hosts) or custom sockets. - Stream Iteration:
ChannelFile(sync) andAsyncChannelFile(async) are now iterable, allowing line-by-line reading:for line in stdout: print(line). - Enhanced Connectivity: Added
sendall()andreadline()methods to baseChannelandAsyncChannelfor better socket-like compatibility.
Fixed¶
- Authentication Logic: Fixed
SSHClient.connect()to correctly attempt all provided credentials (Public Key then Password) sequentially if one fails. - Documentation Accuracy: Corrected misleading method names in the User Guide (e.g.,
close_port_forward) and updatedProxyJumprecipes to use the newsockparameter. - Async Consistency: Updated async examples to properly use
awaitonrecv_exit_status()and other stream operations.
Security¶
- Log Sanitization: Integrated
LogSanitizerto automatically redact sensitive credentials (passwords, keys) from all project logs. - Protocol Hardening: Fixed sequence number wrapping and improved host key verification to check all stored keys for a host.
- IPv6 Support: Resolved IPv6 connectivity issues by switching to
socket.create_connection().
[0.6.1] - 2026-04-18¶
Fixed¶
- Key Derivation: Resolved a critical issue where ciphers would fail during the initial handshake due to missing
session_idsynchronization in the key derivation function. - Exit Status Mapping: Fixed a bug in
Channelwhere command exit codes and termination signals from the server were acknowledged but not correctly parsed and stored in the session state. - Transport Robustness: Improved the resiliency of channel request parsing, ensuring that malformed or empty packets for standard types like
exit-statusdo not cause protocol exceptions.
[0.6.0] - 2026-04-18¶
Fixed¶
- Curve25519 Key Exchange: Resolved a critical issue where the key exchange could fail if the server's public key had leading zeros. De-coupled algorithm-specific message parsing (types 30 and 31) from generic message unpacking.
- Diffie-Hellman Group 14: Fixed an incorrect
mpintparsing logic in the client-side key exchange that could lead to protocol failures.
Added¶
- Python 3.13 Support: Formally verified and added support for Python 3.13, including CI pipeline integration.
Changed¶
- Protocol Stability: Standardized message unpacking to handle algorithm-dependent message structures more robustly.
[0.5.2] - 2026-04-16¶
Added¶
- Unified Client API: Standardized the interface between
SSHClientandAsyncSSHClientfor better consistency and easier migration between sync and async code. - Improved SSH Key Management: Enhanced Ed25519 and RSA key generation/loading with broader compatibility for OpenSSH-formatted keys.
- Enhanced Demos: Added a suite of comprehensive, high-fidelity demo scripts (
complex_setup_demo.py, etc.) to showcase real-world performance. - Verified Demo Results: Integrated demo result tracking for actual execution metrics.
Changed¶
- Performance: Optimized internal buffering in
AsyncTransportfor high-throughput scenarios. Connection establishment is ~2.6x faster than legacy libraries.
Fixed¶
- Protocol Stability: Resolved several race conditions in
AsyncTransportduring high-concurrency SFTP transfers. - Security Hardening: Tightened standard compliance for cryptographic primitives and handshake sequences.
- Context Manager Cleanup: Fixed resource leaks in
async withblocks where sessions were not properly closed on exception.
[0.5.0] - 2026-04-11¶
Added¶
- Rekeying Support: Implemented RFC 4253 compliant rekeying triggered by data volume (1GB), time (1 hour), or sequence number limits (2^31 packets).
- Peer-Initiated Rekeying: Added support for handling server-initiated key exchanges seamlessly.
- Keyboard-Interactive Authentication: Fully implemented client-side support for interactive authentication prompts (RFC 4256), enabling MFA and complex PAM configurations.
- Request Delegation: Implemented structured channel and global request delegation to the
SSHServerinterface, replacing the previous "accept all" policy.
Fixed¶
- Message Dispatching: Resolved critical protocol message ambiguity where multiple messages shared type code 60 (e.g.,
MSG_USERAUTH_PK_OKvsMSG_USERAUTH_INFO_REQUEST). - Port Forwarding Stability: Improved error handling and logging in data relays to distinguish between expected closures and unexpected errors.
- Transport Threading: Fixed potential race conditions during simultaneous rekeying attempts.
[0.4.2] - 2026-04-10¶
Changed¶
- Security: Purged legacy SHA-1 based MACs and weak ciphers from default negotiation list.
- Performance: Improved
AsyncSFTPClientconcurrency by optimizing message pumping in the underlying transport.
Fixed¶
- Documentation: Clarified dependencies and qualified performance claims in README.
- CI/CD: Fixed invalid
.codecov.ymlstructure and improved coverage path mapping.
[0.4.1] - 2026-04-10¶
Fixed¶
- Fixed critical "Receive timeout" in
AsyncSSHClientandAsyncSFTPClientby implementing transport pumping inAsyncChannel. - Corrected attribute naming in
AsyncChannel(e.g.,_eof_received,_closed). - Fixed
AsyncChannel.sendto correctly return total bytes sent and handle window adjustments robustly. - Fixed
AsyncTransportto properly initialize channel window and packet sizes from server confirmation. - Corrected message argument naming in
AsyncTransport(e.g.,bytes_to_addin window adjust). - Fixed message loss bug in
AsyncTransportby ensuring all pumped messages are queued. - Fixed
AsyncSFTPClient._recv_messageto robustly read length-prefixed messages using newrecv_exactlymethod.
Added¶
- Added
recv_exactlymethod toAsyncChannelfor reliable protocol-level data retrieval. - Added
removemethod toAsyncSFTPClient. - Added asynchronous context manager support (
async with) toAsyncSFTPClientandAsyncSFTPFile. - Added
makefile_stderrsupport toAsyncChannel.
[0.4.0] - 2026-04-07¶
Fixed¶
- Transport Layer: Resolved a critical hang in SSH command execution caused by an infinite loop in
Transport._expect_messagewhere unhandled messages were repeatedly re-queued and re-read. - Message Dispatching: Improved
Transport._handle_channel_messageto correctly extract recipient channel IDs for all message types, ensuring proper dispatch to channel instances. - Concurrency: Added
_read_locktoTransportto prevent concurrent socket access and potential data corruption during simultaneous read operations. - SSH Client: Ensured
SSHClientpermanently sets the connection timeout on the underlying transport after a successful handshake. - Channel Stability: Updated
Channel.exec_commandto automatically send EOF after the command, improving compatibility with various SSH server implementations.
Added¶
- SFTP Client: Implemented context manager support (
__enter__/__exit__) forSFTPClientfor easier resource management. - SFTP Operations: Implemented
SFTPClient.open()and theSFTPFileclass to support synchronous file-like operations (read/write). - Channel Timeouts: Enhanced
Channel.recvto respect per-channel timeouts, preventing indefinite blocking.
[0.3.0] - 2026-04-04¶
Fixed¶
- Protocol Utilities: Fixed
write_mpintto use the minimum number of bytes for negative integers (e.g., -128 now correctly serializes to0x80instead of0xff80). - Version Parsing: Improved error handling in
parse_version_stringto provide clearer messages for invalid SSH version strings. - SSH Connection Stability: Resolved numerous issues preventing reliable SSH connections, including corrected KEX initialization,
bytearrayvsbytestype issues, and more. - Authentication: Fixed
PasswordAuth.authenticateto use the correct service name (ssh-connection). - Channel: Fixed
ChannelFile.read()to correctly read until EOF.
Added¶
- Unit Tests: Created a comprehensive test suite in
tests/covering protocol utilities and constants. - Configuration Consolidation: Unified project configuration into
pyproject.toml.
[0.2.0] - 2026-03-31¶
Added¶
- Basic logging structure and sanitization for test files.
[0.1.0] - 2026-03-20¶
Added¶
- Initial project setup and basic SSH client/server structure.
- Core protocol messages and transport layer implementation.