Traversio

Public API

The public types, methods, and lifecycle rules for Traversio.

Entry Points

The public surface starts here:

public static func connect(
    configuration: SSHClientConfiguration
) async throws -> SSHConnection

public static func connect(
    configuration: SSHClientConfiguration,
    logHandler: SSHClientLogHandler
) async throws -> SSHConnection

public static func withConnection<Result>(
    configuration: SSHClientConfiguration,
    _ body: @escaping (SSHConnection) async throws -> Result
) async throws -> Result

public static func withConnection<Result>(
    configuration: SSHClientConfiguration,
    logHandler: SSHClientLogHandler,
    _ body: @escaping (SSHConnection) async throws -> Result
) async throws -> Result

public static func discoverAuthenticationMethods(
    configuration: SSHAuthenticationMethodDiscoveryConfiguration
) async throws -> SSHAuthenticationMethodDiscoveryResult

public static func discoverAuthenticationMethods(
    configuration: SSHAuthenticationMethodDiscoveryConfiguration,
    logHandler: SSHClientLogHandler
) async throws -> SSHAuthenticationMethodDiscoveryResult

connect(configuration:) is the core long-lived entry point.

withConnection(configuration:_:) is a convenience wrapper over that same connection setup path. It closes the returned SSHConnection automatically when the body returns or throws.

discoverAuthenticationMethods(configuration:) runs the same transport, host-key, proxy, jump-host, compression, legacy-algorithm, and timeout setup as a real connection, then sends SSH none userauth for the configured username and returns the server's advertised auth methods plus any banners. It closes the temporary transport before returning.

These entry points live at the package floor documented in Quickstart and declared in Package.swift.

On Apple 26+ systems, Traversio automatically prefers the newer transport backend. Older supported releases use the compatibility backend behind the same public surface.

For version-specific API additions and release notes, see Releases.

The logHandler: overloads receive structured connection lifecycle and wrapped operation-failure events. Logging stays disabled until you pass a handler.

Long-running connection, session, SFTP, and forwarding operations observe Swift task cancellation. When cancellation wins the race, they surface CancellationError; if the transport closes, the server disconnects, or a protocol failure arrives first, the operation may instead surface the corresponding typed Traversio failure. Session transcript collectors attempt a best-effort channel-close on cancellation. Session and raw-channel event iterators attempt the same close when the iteration task is cancelled or the iterator exits before channel close. That close is a cleanup attempt, not a guarantee that a peer which has already closed or stopped reading will process it.

SSHConnection.latency

For an established connection, Traversio exposes the latest SSH round-trip latency observed on that live connection:

let snapshot = await connection.latency
print(snapshot?.roundTripTimeMilliseconds)

SSHConnection.latency is a snapshot, not a separate probe. Traversio updates it when the live connection completes request/reply pairs such as channel open, channel request, global request, and configured keepalive replies. If the connection is offline, waiting for host-key trust, or failed authentication, there is no live SSHConnection and the latency value should be treated as unavailable.

Use this property for dashboard-style monitoring of a connected machine. A UI can poll the property on its own refresh interval, while Traversio owns the SSH traffic that updates the snapshot. Enable SSHKeepalivePolicy on the connection when you want idle connections to keep refreshing latency without opening extra SSH routes.

Supporting types:

  • SSHConnectionLatency
  • SSHConnectionLatencySource

SSHConnectionLatency.roundTripTimeMilliseconds is the ping-like value most user interfaces should display.

SSHClient.measurePortLatency(...)

Traversio also exposes a small public latency utility for SSH-port timing across direct, proxy, and ProxyJump routes:

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, visionOS 1.0, *)
public static func measurePortLatency(
    host: String,
    port: UInt16 = 22,
    options: SSHPortLatencyOptions = .init()
) async throws -> SSHPortLatencyReport

public static func measurePortLatency(
    host: String,
    port: UInt16 = 22,
    connectionProxy: SSHConnectionProxy? = nil,
    proxyJumpHosts: [SSHProxyJumpHost] = [],
    options: SSHPortLatencyOptions = .init(),
    logHandler: SSHClientLogHandler = .disabled
) async throws -> SSHPortLatencyReport

public static func measurePortLatency(
    configuration: SSHClientConfiguration,
    options: SSHPortLatencyOptions = .init(),
    logHandler: SSHClientLogHandler = .disabled
) async throws -> SSHPortLatencyReport

This utility opens a route to the target host and port, records route setup timing, completes final-target SSH transport setup, then measures an encrypted SSH_MSG_SERVICE_REQUEST("ssh-userauth") / SSH_MSG_SERVICE_ACCEPT round trip. It is a latency diagnostic, not a trust or login check: final-target samples verify the host key signature during key exchange but do not apply the connection's hostKeyPolicy, and they stop before final-target user authentication. It should not be used as a recurring dashboard latency source for a machine that already has a live SSHConnection; use SSHConnection.latency for that case.

Scope:

  • direct targets use Traversio's normal TCP transport factory
  • connectionProxy targets perform the configured SOCKS5 or HTTP CONNECT setup before sampling
  • proxyJumpHosts targets authenticate jump hosts once, then open a fresh direct-tcpip channel to the final endpoint for each sample
  • configuration: reuses the endpoint, connection proxy, and jump-host route from an existing SSHClientConfiguration
  • full authentication and session startup still belong to SSHClient.connect(...)
  • use SSHClient.connect(...) when the app needs the normal final-target host-trust decision

The main public supporting types are:

  • SSHPortLatencyOptions
  • SSHPortLatencyReport
  • SSHPortLatencySample
  • SSHPortLatencyFailure
  • SSHPortLatencyStatistics

SSHPortLatencyOptions.validate() checks sample count and timeout values and throws typed SSHPortLatencyError cases for invalid input. The measurement entry points run the same validation before opening a route.

Practical example:

import Traversio

@available(macOS 10.15, iOS 13.0, *)
func inspectSSHPort() async throws {
    let report = try await SSHClient.measurePortLatency(
        host: "example.com",
        port: 22,
        options: SSHPortLatencyOptions(
            sampleCount: 5,
            connectTimeout: 2,
            firstServerByteTimeout: 2,
            delayBetweenSamples: 0.1
        )
    )

    print(report.connectRTTStatistics.averageMilliseconds)
    print(report.sshServiceRequestRTTStatistics.averageMilliseconds)
    print(report.estimatedPathOneWayFromSSHServiceRequestStatistics.averageMilliseconds)
}

ProxyJump example:

let report = try await SSHClient.measurePortLatency(
    configuration: machineConfiguration,
    options: SSHPortLatencyOptions(sampleCount: 5)
)

Keep the relevant summary metrics when possible:

  • connectRTTStatistics.averageMilliseconds tells you how quickly the route setup step completed; it is not always comparable to ping
  • sshServiceRequestRTTStatistics.averageMilliseconds is the ping-like final-server SSH request/response RTT
  • estimatedPathOneWayFromSSHServiceRequestStatistics.averageMilliseconds is only the SSH service-request RTT divided by two, and should be labeled as an estimated one-way value

For a longer guide on when to choose one metric versus the other, see SSH Port Latency.

SSHClientConfiguration

Use SSHClientConfiguration to describe the remote endpoint and auth/trust policy.

FieldTypeMeaning
hostStringRemote host name or address
portUInt16Remote port, default 22
usernameStringSSH username
authenticationSSHAuthenticationMethodAuth method
hostKeyPolicySSHHostKeyPolicyRequired host trust policy
compressionPreferenceSSHCompressionPreferenceOptional transport compression preference: .disabled, RFC 4253 .zlib, or delayed OpenSSH .delayedZlib
legacyAlgorithmOptionsSSHLegacyAlgorithmOptionsOptional explicit legacy ssh-rsa host-key and RSA auth compatibility
automaticRekeyPolicySSHAutomaticRekeyPolicyOptional automatic client-initiated rekey thresholds and idle timer
keepalivePolicySSHKeepalivePolicyOptional post-auth idle keepalive policy, disabled by default
timeoutPolicySSHTimeoutPolicySetup, host-key trust, and reply timeout policy; defaults to 30 seconds for setup, 120 seconds for host-key trust, and unbounded reply waits
connectionProxySSHConnectionProxy?Optional outermost-hop SOCKS5 or HTTP CONNECT proxy used before SSH starts
proxyJumpHosts[SSHProxyJumpHost]Optional explicit jump-host chain used before the final target

Example:

let configuration = SSHClientConfiguration(
    host: "example.com",
    port: 22,
    username: "deploy",
    authentication: .password("secret"),
    hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts"),
    legacyAlgorithmOptions: .disabled,
    compressionPreference: .delayedZlib,
    automaticRekeyPolicy: .currentProfileDefault,
    keepalivePolicy: .init(interval: 30),
    timeoutPolicy: .init(
        connectionSetupTimeInterval: 15,
        hostKeyTrustTimeInterval: 120,
        responseTimeInterval: 5
    )
)

By default, compressionPreference is .disabled, legacyAlgorithmOptions is .disabled, automaticRekeyPolicy is .currentProfileDefault, keepalivePolicy is .disabled, and timeoutPolicy is .currentProfileDefault. The default timeout profile applies a 30-second connection setup limit, allows 120 seconds for host-key trust confirmation, and leaves reply waits unbounded.

Use supportedAlgorithms when an application needs to display or validate the effective algorithm profile before connecting:

let algorithms = configuration.supportedAlgorithms

print(algorithms.keyExchangeAlgorithms)
print(algorithms.algorithms(for: .compressionClientToServer))

SSHSupportedAlgorithms.currentProfile exposes the default profile, while SSHSupportedAlgorithms(compressionPreference:legacyAlgorithmOptions:) lets tools inspect a specific compression and legacy-RSA combination. The snapshot covers key exchange, host keys, ciphers, MACs, compression, and public-key signature names.

SSHProxyJumpHost uses the same explicit per-hop inputs:

  • host
  • port
  • username
  • authentication
  • hostKeyPolicy
  • compressionPreference
  • legacyAlgorithmOptions
  • automaticRekeyPolicy
  • keepalivePolicy
  • timeoutPolicy

Each jump host also exposes supportedAlgorithms, using that hop's compression and legacy settings.

SSHCompressionPreference keeps a focused surface:

  • .disabled
  • .zlib
  • .delayedZlib

.zlib means RFC 4253 zlib: compression starts once encrypted transport is active after each key exchange and stays on for later protected packets until the next rekey resets the context.

.delayedZlib means OpenSSH [email protected]: compression stays off during key exchange and user authentication, then turns on for later protected packets after authentication succeeds.

proxyJumpHosts defines SSH hop chaining. connectionProxy defines the transport proxy used for the first TCP hop.

Current connectionProxy scope:

  • .socks5(SSHSOCKS5ConnectionProxy(...))
  • .httpConnect(SSHHTTPConnectConnectionProxy(...))

If you set both connectionProxy and proxyJumpHosts, the external proxy is used only for the first TCP connection. Later SSH hops still travel inside SSH direct-tcpip channels.

SSHAuthenticationMethodDiscoveryConfiguration

Use SSHAuthenticationMethodDiscoveryConfiguration when the application wants the server's advertised auth-method list before choosing or attempting a real auth method.

FieldTypeMeaning
hostStringRemote host name or address
portUInt16Remote port, default 22
usernameStringSSH username used for the none auth request
hostKeyPolicySSHHostKeyPolicyRequired host trust policy
compressionPreferenceSSHCompressionPreferenceOptional transport compression preference for the temporary discovery connection
legacyAlgorithmOptionsSSHLegacyAlgorithmOptionsOptional explicit legacy host-key compatibility for the temporary discovery connection
timeoutPolicySSHTimeoutPolicySetup, host-key trust, and reply timeout policy; defaults to 30 seconds for setup and 120 seconds for host-key trust
connectionProxySSHConnectionProxy?Optional outermost-hop SOCKS5 or HTTP CONNECT proxy
proxyJumpHosts[SSHProxyJumpHost]Optional explicit jump-host chain before the final target

This configuration does not include authentication, automaticRekeyPolicy, or keepalivePolicy. The discovery path closes after the first userauth reply, so it does not create a long-lived authenticated session.

SSHAuthenticationMethodDiscoveryResult and SSHAuthenticationBanner

SSHAuthenticationMethodDiscoveryResult is returned by SSHClient.discoverAuthenticationMethods(...).

Current fields:

  • username
  • serviceName
  • availableMethods
  • partialSuccess
  • allowsUnauthenticatedAccess
  • banners

availableMethods preserves the ordered raw SSH method names advertised by the server, such as publickey, password, or keyboard-interactive.

If allowsUnauthenticatedAccess is true, the server accepted the none request and availableMethods is empty.

Each SSHAuthenticationBanner exposes:

  • message
  • languageTag

The same banner type appears in SSHConnectionMetadata.authenticationBanners after successful connection setup and in SSHClientError.authenticationRejected(...) when connection-time authentication fails after the server has sent SSH_MSG_USERAUTH_BANNER.

SSHLegacyAlgorithmOptions

Use SSHLegacyAlgorithmOptions when one connection or one jump-host hop must talk to an older SSH server.

Current members:

  • allowsSSHRSA
  • .disabled
  • .sshRSA

.sshRSA enables these behaviors for that configuration:

  • Traversio appends ssh-rsa to the preferred server-host-key list for key exchange
  • callback-backed and agent-backed public-key auth may select ssh-rsa
  • built-in RSA private-key authentication can retry with ssh-rsa after the server declines the RSA-SHA2 attempt and still offers publickey

The built-in RSA private-key path still starts with rsa-sha2-512 and rsa-sha2-256. When .disabled is in effect, Traversio removes ssh-rsa from callback-backed and agent-backed candidate lists before selecting the public-key signature algorithm. Use .sshRSA only for older endpoints which still require the SHA-1 RSA signature path.

SSHConnectionProxy

Use SSHConnectionProxy when Traversio itself must reach the SSH server through an external proxy before the SSH handshake begins.

Current cases:

  • .socks5(SSHSOCKS5ConnectionProxy)
  • .httpConnect(SSHHTTPConnectConnectionProxy)

SSHSOCKS5ConnectionProxy exposes:

  • host
  • port
  • authentication

SSHSOCKS5ProxyAuthentication exposes:

  • .none
  • .usernamePassword(username:password:)

SSHHTTPConnectConnectionProxy exposes:

  • host
  • port
  • authentication

SSHHTTPConnectProxyAuthentication exposes:

  • .none
  • .basic(username:password:)

These types describe the outermost TCP proxy hop. Later proxyJumpHosts continue through SSH direct-tcpip channels after the first SSH session is established.

SSHAutomaticRekeyPolicy

Use SSHAutomaticRekeyPolicy when you want to keep the default packet thresholds, disable automatic client-initiated rekey, or tighten those thresholds for testing.

Current members:

  • outboundPacketThreshold
  • inboundPacketThreshold
  • idleTimeInterval
  • .currentProfileDefault
  • .disabled

currentProfileDefault currently uses 1_048_576 encrypted packets in each direction.

The outbound threshold is checked before the next protected send. The inbound threshold is checked after authentication, before the next protected receive wait on the connection/session path. The optional idle timer is checked after authentication and can start a local rekey even when the connection is otherwise idle.

disabled turns off the automatic client-initiated rekey path entirely, including the idle timer.

Example:

let configuration = SSHClientConfiguration(
    host: "example.com",
    username: "deploy",
    authentication: .password("secret"),
    hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts"),
    automaticRekeyPolicy: .init(
        outboundPacketThreshold: 50_000,
        inboundPacketThreshold: 50_000,
        idleTimeInterval: 600
    )
)

SSHKeepalivePolicy

Use SSHKeepalivePolicy when you want Traversio to send a keepalive on an otherwise idle authenticated connection.

Current members:

  • interval
  • .disabled

interval is a post-auth idle threshold in seconds.

Current behavior:

  • keepalive is checked only after authentication succeeds
  • it is meant for long-lived idle connections, shells, and forwarding sessions
  • it is not a blanket timeout for long-running output collection or already-open streams
  • backend-provided path or viability changes can trigger one extra bounded keepalive check, and those transitions surface through SSHConnection.stateEvents when the selected transport backend emits them

If SSHTimeoutPolicy.responseTimeInterval is also set, keepalive reply waits use the tighter of that reply timeout and the keepalive interval itself.

Example:

let configuration = SSHClientConfiguration(
    host: "example.com",
    username: "deploy",
    authentication: .password("secret"),
    hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts"),
    keepalivePolicy: .init(interval: 30)
)

SSHTimeoutPolicy

Use SSHTimeoutPolicy when the caller wants to tune connection setup bounds or bound reply-style protocol waits.

Current members:

  • defaultConnectionSetupTimeInterval
  • defaultHostKeyTrustTimeInterval
  • .currentProfileDefault
  • .disabled
  • connectionSetupTimeInterval
  • hostKeyTrustTimeInterval
  • responseTimeInterval

connectionSetupTimeInterval covers one connect attempt, including transport setup, identification exchange, key exchange, and user authentication. The current default is 30 seconds.

hostKeyTrustTimeInterval covers host-key trust confirmation separately from setup timing. The current default is 120 seconds.

Pass .disabled to turn off setup, host-key trust, and reply timeout handling.

responseTimeInterval covers waits where the server is expected to answer promptly:

  • channel-open replies
  • channel-request replies such as exec, pty-req, shell, and subsystem
  • keepalive replies
  • global-request replies such as tcpip-forward
  • SFTP responses

It does not act as a blanket timeout for long-running shell/exec output collection, event streams, or remote-forward accept loops.

Example:

let configuration = SSHClientConfiguration(
    host: "example.com",
    username: "deploy",
    authentication: .password("secret"),
    hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts"),
    timeoutPolicy: .init(
        connectionSetupTimeInterval: 15,
        hostKeyTrustTimeInterval: 120,
        responseTimeInterval: 5
    )
)

SSHClientLogHandler

Use SSHClientLogHandler with the logHandler: overloads on SSHClient.connect(...), SSHClient.withConnection(...), and SSHClient.discoverAuthenticationMethods(...) when the application wants structured library log events.

Current members:

  • .disabled
  • init(minimumLevel:emitter:)
  • .sink(minimumLevel:_:)
  • .recorder(_:minimumLevel:)
  • .osLog(_ logger: Logger, minimumLevel:)
  • .osLog(subsystem:category:minimumLevel:)

SSHClientLogLevel exposes:

  • .debug
  • .info
  • .notice
  • .warning
  • .error

SSHClientLogCategory exposes:

  • .connection
  • .authentication
  • .session
  • .sftp
  • .forwarding
  • .transport

SSHClientLogEvent exposes:

  • timestamp
  • level
  • category
  • message
  • metadata

message, metadata, formattedLine, and OSLog output are redacted before export for sensitive metadata keys and common inline secret fragments such as passwords, passphrases, private-key values, tokens, credentials, and authorization headers.

Traversio currently emits structured events for connection start/success/failure, auth-method discovery start/result, authentication success/rejection, and wrapped operation failures on the stable public error paths.

SSHClientLogRecorder

Use SSHClientLogRecorder when the application wants a bounded in-memory buffer of recent SSHClientLogEvent values for support export or an in-app diagnostics screen.

Current members:

  • init(maximumEventCount:)
  • record(_:)
  • clear()
  • snapshot()
  • diagnosticReport(for:)
  • logHandler(minimumLevel:)

SSHClientLogRecorderSnapshot exposes:

  • events
  • maximumEventCount
  • droppedEventCount
  • formattedLines
  • formattedText
  • diagnosticReport(for:)

When the recorder reaches its capacity it keeps the newest events, drops older ones, and records how many were discarded in droppedEventCount.

SSHAuthenticationMethod

Current cases:

  • .password(String)
  • .passwordWithChangeResponse(password:responseProvider:)
  • .keyboardInteractive(submethods:responseProvider:)
  • .ed25519PrivateKey(rawRepresentation: [UInt8])
  • .rsaPrivateKey(pkcs1DERRepresentation: [UInt8])
  • .ecdsaP256PrivateKey(rawRepresentation: [UInt8])
  • .ecdsaP384PrivateKey(rawRepresentation: [UInt8])
  • .ecdsaP521PrivateKey(rawRepresentation: [UInt8])
  • .publicKey(algorithmNames:publicKey:signatureProvider:)

Current factory helpers:

  • try SSHAuthenticationMethod.ed25519PrivateKey(openSSHPrivateKey: String, passphrase: String? = nil)
  • try SSHAuthenticationMethod.ed25519PrivateKey(contentsOfOpenSSHPrivateKeyFile: String, passphrase: String? = nil)
  • try SSHAuthenticationMethod.rsaPrivateKey(openSSHPrivateKey: String, passphrase: String? = nil)
  • try SSHAuthenticationMethod.rsaPrivateKey(contentsOfOpenSSHPrivateKeyFile: String, passphrase: String? = nil)
  • try SSHAuthenticationMethod.ecdsaPrivateKey(openSSHPrivateKey: String, passphrase: String? = nil)
  • try SSHAuthenticationMethod.ecdsaPrivateKey(contentsOfOpenSSHPrivateKeyFile: String, passphrase: String? = nil)
  • try SSHAuthenticationMethod.privateKeyPEM(String, passphrase: String? = nil)
  • try SSHAuthenticationMethod.privateKeyPEM(contentsOfFile: String, passphrase: String? = nil)
  • try SSHOpenSSHKeyPair.generate(algorithm: SSHOpenSSHKeyPair.Algorithm, comment: String = "traversio", encryption: SSHOpenSSHPrivateKeyEncryption? = nil)
  • try SSHOpenSSHPrivateKeyInfo.parse(String)
  • try SSHOpenSSHPrivateKeyInfo.parse(contentsOfFile: String)

The Ed25519 raw case expects a 32-byte private-key seed. The RSA raw case expects PKCS#1 DER private-key bytes. The ECDSA raw cases expect the corresponding curve-specific raw private-key representation. The OpenSSH-specific throwing helpers load OpenSSH openssh-key-v1 Ed25519, RSA, or ECDSA private keys and return the matching enum case. privateKeyPEM(...) is the broader app/user import path for OpenSSH private keys plus OpenSSL-style PEM: PKCS#8 PRIVATE KEY for Ed25519/RSA/ECDSA, traditional RSA PRIVATE KEY, and traditional EC PRIVATE KEY. Traditional RSA PEM may be unencrypted or passphrase-encrypted with supported OpenSSL legacy AES-CBC or DES-EDE3-CBC headers; traditional EC PEM is unencrypted-only. SSHOpenSSHKeyPair.generate(...) returns the generated authenticationMethod, the OpenSSH private-key PEM text, and the matching authorized-key line. Pass passphrase: or SSHOpenSSHPrivateKeyEncryption(...) when the private key should use the OpenSSH bcrypt KDF and a supported AES cipher such as aes256-ctr or aes256-cbc; encrypted PKCS#8 ENCRYPTED PRIVATE KEY is not loaded yet.

SSHOpenSSHPrivateKeyInfo is the metadata-only path for OpenSSH private-key files. It reads the public envelope without decrypting the private-key block, so apps can display labels and fingerprints before asking for a passphrase. The returned value includes cipherName, cipher, kdfName, keyDerivationFunction, keyCount, publicKeys, primaryPublicKey, privateKeyBlockByteCount, and isEncrypted. Public-key entries expose the raw public-key blob, algorithm classification, SHA-256 fingerprint, and authorizedKeyLine(comment:).

publicKey(algorithmNames:publicKey:signatureProvider:) is the callback-backed public-key path for external signers such as app-owned credential stores. Traversio performs the normal SSH_MSG_USERAUTH_PK_OK confirmation, then passes an SSHPublicKeyAuthenticationSigningRequest to your async closure. The closure returns an SSH signature blob. When your signer returns raw signature bytes, call request.makeSignatureBlob(rawSignature:).

SSHPublicKeyAuthenticationSigningRequest exposes:

  • username
  • serviceName
  • algorithmName
  • publicKey
  • signatureData
  • makeSignatureBlob(rawSignature:)

SSHAgentClient talks to an OpenSSH-compatible agent socket. The default initializer reads SSH_AUTH_SOCK; pass socketPath: for a specific Unix-domain socket. identities() returns SSHAgentIdentity values with comments, key types, raw SSH public keys, and supported authentication algorithm names. authenticationMethod(for:) returns a .publicKey(...) method backed by agent signing.

import Traversio

enum AgentAuthSetupError: Error {
    case noIdentity
}

func connectWithAgentIdentity() async throws {
    let agent = try SSHAgentClient()
    guard let identity = try await agent.identities().first else {
        throw AgentAuthSetupError.noIdentity
    }

    let configuration = SSHClientConfiguration(
        host: "example.com",
        username: "deploy",
        authentication: agent.authenticationMethod(for: identity),
        hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts")
    )

    try await SSHClient.withConnection(configuration: configuration) { connection in
        let result = try await connection.execute("whoami")
        print(String(decoding: result.standardOutput, as: UTF8.self))
    }
}

SSHAgentIdentity exposes:

  • publicKey
  • comment
  • keyType
  • supportedAuthenticationAlgorithmNames

RSA identities advertise rsa-sha2-512, rsa-sha2-256, and ssh-rsa; the connection's legacyAlgorithmOptions decides whether ssh-rsa is allowed to participate in selection. Signing maps the SHA-2 names to the matching OpenSSH agent signature flags.

SSHOpenSSHKeyPair.Algorithm exposes:

  • .ed25519
  • .ecdsaP256
  • .ecdsaP384
  • .ecdsaP521
  • .rsa(bitCount: Int)

SSHOpenSSHPrivateKeyEncryption.Cipher exposes:

  • .aes128CTR
  • .aes192CTR
  • .aes256CTR
  • .aes128CBC
  • .aes192CBC
  • .aes256CBC

SSHOpenSSHKeyPair exposes:

  • algorithm
  • comment
  • authenticationMethod
  • privateKeyPEM
  • authorizedKeyLine

For the full generation workflow, including saving the generated PEM and installing the matching public line, see Key Generation.

passwordWithChangeResponse starts with normal password authentication and handles SSH_MSG_USERAUTH_PASSWD_CHANGEREQ by calling your async response provider. Traversio passes an SSHPasswordChangeChallenge containing the username, service name, prompt, language tag, and banners collected before the password-change request. Your closure returns the new password, and Traversio sends a password-change request with the old and new passwords.

SSHPasswordChangeChallenge exposes:

  • username
  • serviceName
  • prompt
  • languageTag
  • banners

keyboardInteractive gives you a generic async challenge-response hook. Traversio passes an SSHKeyboardInteractiveChallenge containing the server's name, instruction, languageTag, and ordered prompts. Your closure must return one UTF-8 response per prompt.

SSHKeyboardInteractiveChallenge exposes:

  • username
  • serviceName
  • name
  • instruction
  • languageTag
  • prompts

Each SSHKeyboardInteractivePrompt exposes:

  • prompt
  • shouldEcho

If your callback returns the wrong number of responses, Traversio throws SSHAuthenticationMethodError.invalidKeyboardInteractiveResponseCount(expected:received:).

SSHHostKeyPolicy

Current factory members:

  • .acceptAnyVerifiedHostKey
  • .requireMatch(SSHTrustedHostKey)
  • .requireMatchAny([SSHTrustedHostKey])
  • .trustOnFirstUse(lookup:store:)
  • .trustOnFirstUse(lookup:store:onStoredHostKeyMismatch:)
  • .trustOnFirstUse(using: SSHHostKeyTrustStore)
  • .knownHostsFile(String)
  • .knownHostsFile(String, additionalLookupNames: [String])
  • .callback((SSHHostKeyValidationRequest) async throws -> SSHHostKeyTrustMethod)

.trustOnFirstUse(lookup:store:) is the simplest first-seen trust path. Traversio gives your app a lookup closure plus an SSHHostKeyStoreRequest on writes. That store request includes expectedStoredHostKey, so a shared trust store can do compare-and-set instead of blindly overwriting concurrent updates.

The overload with onStoredHostKeyMismatch keeps the same store boundary but lets the app decide whether a changed stored key should be rejected or replaced. That callback receives an SSHHostKeyChangeRequest with both the stored and newly received host key, and it returns an SSHHostKeyChangeDecision.

.trustOnFirstUse(using:) wraps that same first-seen flow behind an app-owned SSHHostKeyTrustStore. The default protocol implementation rejects changed stored keys, and a store can opt into planned rotation by implementing decisionForChangedHostKey(_:). The write hook still receives the same compare-and-set SSHHostKeyStoreRequest.

.knownHostsFile(...) currently resolves exact, wildcard, negated, hashed, CIDR, @revoked, and @cert-authority OpenSSH known_hosts entries, including non-default-port forms like [host]:port. The overload with additionalLookupNames lets you include extra host or IP strings in the lookup, so a connection opened as example.com can still match entries stored under a resolved address like 192.0.2.10.

.callback(...) runs after the server proves possession of its host key and before the SSH session is activated. That gives the upper layer one explicit place to consult or update its own trust store without requiring Traversio to own persistence.

SSHTrustedHostKey exposes:

  • algorithmName
  • rawRepresentation
  • fingerprintSHA256

SSHHostKeyTrustMethod tells you which trust path was used:

  • acceptAnyVerifiedHostKey
  • exactMatch
  • trustedSetMatch
  • certificateAuthorityMatch
  • callback

SSHHostKeyValidationRequest exposes:

  • endpointHost
  • endpointPort
  • remoteIdentification
  • trustedHostKey
  • matches(_:)

SSHHostKeyChangeRequest exposes:

  • endpointHost
  • endpointPort
  • remoteIdentification
  • storedHostKey
  • receivedHostKey

SSHHostKeyStoreRequest exposes:

  • endpointHost
  • endpointPort
  • remoteIdentification
  • expectedStoredHostKey
  • trustedHostKey
  • matchesExpectedStoredHostKey(_:)

SSHHostKeyChangeDecision exposes:

  • reject
  • replaceStoredHostKey

SSHHostKeyTrustStore exposes:

  • lookupHostKey(endpointHost:endpointPort:)
  • storeHostKey(_:)
  • decisionForChangedHostKey(_:)

SSHConnection

SSHConnection is the public connection wrapper returned by connect(configuration:) and passed into withConnection(...).

Public members:

MemberTypeMeaning
metadataSSHConnectionMetadataHandshake and trust details for the active connection
stateEventsSSHConnectionStateEventSequenceIncremental connection-state stream for transport state, backend-provided path, viability, and better-path details, liveness, close, and loss transitions
currentState()async -> SSHConnectionStateSnapshotRead the latest connection-state snapshot without consuming the event stream
close()asyncClose the SSH connection explicitly and invalidate every wrapper created from it
execute(_:environment:)async throws -> SSHExecResultExecute a one-shot remote command, optionally with SSH env requests sent before exec
openExec(_:environment:)async throws -> SSHSessionOpen a streamed exec session on a session channel, optionally with SSH env requests
openShell(pseudoTerminalRequest:environment:)async throws -> SSHSessionOpen a PTY-backed shell session, optionally with SSH env requests sent before shell startup
openSubsystem(_:environment:)async throws -> SSHSessionOpen a named subsystem on a session channel, optionally with SSH env requests sent before subsystem startup
openSFTP(clientVersion:)async throws -> SFTPClientStart an SFTP client on a subsystem channel
receiveSCPFile(_:maximumFileSize:)async throws -> SSHSCPReceivedFileReceive one regular file through the legacy SCP protocol and buffer it in memory
sendSCPFile(_:remotePath:fileName:permissions:)async throws -> SSHSCPTransferResultSend one in-memory regular file through the legacy SCP protocol
downloadSCPFile(_:to:maximumFileSize:)async throws -> SSHSCPTransferResultReceive one SCP file and write it to a local file URL
uploadSCPFile(from:to:fileName:permissions:)async throws -> SSHSCPTransferResultRead one local file URL and send it through the legacy SCP protocol
openDirectTCPIPChannel(targetHost:targetPort:originatorAddress:originatorPort:)async throws -> SSHDirectTCPIPChannelOpen a raw direct-tcpip forwarding channel
openDirectStreamLocalChannel(socketPath:originatorAddress:originatorPort:)async throws -> SSHDirectStreamLocalChannelOpen a raw OpenSSH [email protected] channel
withLocalPortForwarding(targetHost:targetPort:localHost:localPort:_:)async throws -> ResultStart a closure-scoped local listener
withDynamicPortForwarding(localHost:localPort:socks5Authentication:_:)async throws -> ResultStart a closure-scoped local SOCKS proxy
withRemotePortForwardListener(remoteHost:remotePort:_:)async throws -> ResultStart a closure-scoped remote listener and accept raw incoming forwarded-tcpip channels
withRemoteStreamLocalForwardListener(socketPath:_:)async throws -> ResultStart a closure-scoped remote Unix socket listener and accept raw incoming [email protected] channels
withRemotePortForwarding(localPort:remoteHost:remotePort:localHost:_:)async throws -> ResultStart a closure-scoped remote listener and bridge it back to one local TCP endpoint

SCP Transfer Types

SSHSCPReceivedFile is returned by receiveSCPFile(...).

Fields:

  • remotePath
  • fileName
  • permissions
  • byteCount
  • contents
  • exitStatus

SSHSCPTransferResult is returned by sendSCPFile(...), downloadSCPFile(...), and uploadSCPFile(...).

Fields:

  • remotePath
  • fileName
  • byteCount
  • exitStatus

SSHSCPTransferDefaults.maximumBufferedFileByteCount is the default receive/download memory limit. It is currently 64 MiB.

SSHSCPTransferError covers invalid inputs, malformed or unexpected SCP control messages, remote SCP warning/fatal records, directory records on the single-file receive path, oversized files, premature stream end, and non-zero remote exit status.

For examples and current boundaries, see SCP Transfers.

SSHConnectionMetadata

Metadata fields:

  • endpointHost
  • endpointPort
  • username
  • clientIdentification
  • remoteIdentification
  • preIdentificationLines
  • authenticationBanners
  • hostKeyAlgorithm
  • hostKeyFingerprintSHA256
  • hostKeyTrustMethod

This is useful when you want to log what server answered, which userauth banners were shown, which key was accepted, and which trust path allowed the handshake to continue.

SSHConnectionStateEventSequence

Use SSHConnection.stateEvents when the application wants to observe connection health over time.

Current shape:

  • the first event is .connected
  • later events report transport-state changes, backend-provided path, viability, and better-path signals, successful proactive liveness checks, background failure, and explicit close
  • transport observations that reach a terminal failed or cancelled state now also end the public connection lifetime, so the stream advances to .lost without waiting for the next shell, SFTP, or forwarding operation
  • the sequence finishes after .lost or .closed
  • applications use this sequence as the input for reconnect and channel-recreation policy

SSHConnectionStateEvent

SSHConnectionStateEvent contains:

  • trigger
  • snapshot

trigger tells you why the snapshot changed. snapshot carries the latest classified connection state plus the most recent observed network details.

SSHConnectionStateSnapshot

Snapshot fields:

  • state
  • transportState
  • networkPath
  • isTransportViable
  • betterPathAvailable
  • detail

state is one of:

  • .ready
  • .degraded
  • .lost
  • .closed

SSHConnectionNetworkPath

Path fields:

  • status
  • availableInterfaces
  • isExpensive
  • isConstrained
  • supportsIPv4
  • supportsIPv6

This path model is meant for app-side recovery policy and diagnostics. It describes the current transport path. Applications pair it with explicit reconnect and session recreation when a path change ends the SSH session.

SSHExecResult

SSHExecResult contains:

  • standardOutput
  • standardError
  • exitStatus
  • exitSignal
  • didReceiveEOF

It is a value type designed for one-shot command execution. execute(_:) remains the collected-result convenience API, while openExec(_:) exposes the same underlying session channel as a streamed SSHSession.

SSHSessionEnvironmentVariable

Use SSHSessionEnvironmentVariable when you want execute(...), openExec(...), openShell(...), or openSubsystem(...) to send one or more RFC 4254 env requests before the remote process, shell, or subsystem starts.

Fields:

  • name
  • value

Each value becomes one SSH env request. Traversio currently waits for a reply to every environment request, so a server-side rejection fails the session startup instead of being ignored silently.

SSHSession

SSHSession is the public wrapper for an open session channel used by streamed exec, named subsystem, and PTY-backed shell flows.

Public methods:

MethodMeaning
write(_ bytes: [UInt8])Send raw bytes to the session
write(_ string: String)Send UTF-8 text
writeStandardError(_ bytes: [UInt8])Send RFC 4254 standard-error extended data
writeStandardError(_ string: String)Send UTF-8 text as standard-error extended data
sendEOF()Close the sending side
close()Send channel close explicitly
resizePseudoTerminal(characterWidth:characterHeight:pixelWidth:pixelHeight:)Send an SSH window-change request on the current session channel
sendSignal(_:)Send an SSH signal request on the current session channel without waiting for a reply
channelWindowSnapshot()Report the current receive and send window state for this channel
adjustReceiveWindow(by:)Send SSH_MSG_CHANNEL_WINDOW_ADJUST and return the updated window snapshot
nextEvent()Read the next shell event, or nil after channel close
eventsConsume shell events as an AsyncSequence
readStandardOutputChunk()Read the next stdout chunk, or nil on close
collectOutputUntilClose()Gather stdout, stderr, exit status, exit signal, and EOF state until the session closes

Supporting value type:

  • SSHSessionOutput mirrors the transcript fields of SSHExecResult
  • SSHSessionEvent exposes .standardOutput, .standardError, .exitStatus, .exitSignal, and .endOfFile
  • resizePseudoTerminal(...) is mainly useful for PTY-backed shells opened through openShell(...); plain exec sessions currently do not allocate a PTY automatically
  • sendSignal(_:) sends RFC 4254 signal requests such as TERM or INT; the server may ignore them, and there is no reply message for this request
  • remote exit-signal requests now surface through SSHSessionEvent.exitSignal(...) and the exitSignal field on collected outputs
  • choose one output reader per session: nextEvent() / events, readStandardOutputChunk(), or collectOutputUntilClose()
  • when a streaming reader reaches nil, Traversio releases the channel's buffered output state
  • cancelling collectOutputUntilClose() now triggers a best-effort channel-close before the task surfaces CancellationError
  • cancelling for try await event in session.events or breaking out of that loop before channel close now triggers that same best-effort channel-close
  • if transport loss, remote disconnect, or an operation failure reaches the session before task cancellation is observed, callers should handle the typed Traversio error instead of assuming every stopped task becomes CancellationError

SSHChannelWindowSnapshot

SSHChannelWindowSnapshot reports the public channel flow-control state for session, raw TCP/IP, and raw streamlocal channel wrappers.

Fields:

  • localChannelID
  • remoteChannelID
  • receiveWindowByteCount
  • receiveInitialWindowByteCount
  • sendWindowByteCount
  • sendInitialWindowByteCount
  • sendMaximumPacketByteCount

SSHSessionSignal

Use SSHSessionSignal when you need to send a POSIX-style signal request on a shell or streamed exec session.

Common members include:

  • .interrupt
  • .terminate
  • .kill
  • .quit
  • .hangup
  • .abort
  • .alarm
  • .floatingPointException
  • .illegalInstruction
  • .brokenPipe
  • .segmentationViolation
  • .user1
  • .user2

For custom names, use SSHSessionSignal(rawValue: ...).

Traversio sends the raw value directly as the RFC 4254 signal name, so standard signals should use the protocol form without the SIG prefix.

SSHSessionExitSignal

SSHSessionExitSignal describes a remote exit-signal notification reported by the peer.

Fields:

  • signal
  • didCoreDump
  • errorMessage
  • languageTag

The errorMessage and languageTag fields are optional because many peers send them as empty strings.

SSHDirectTCPIPChannel

SSHDirectTCPIPChannel is the current expert forwarding wrapper for a raw direct-tcpip channel.

Public methods:

MethodMeaning
write(_ bytes: [UInt8])Send raw bytes to the forwarded target
write(_ string: String)Send UTF-8 text
sendEOF()Close the sending side
close()Send channel close explicitly
nextEvent()Read the next forwarding event, or nil after channel close
eventsConsume forwarding events as an AsyncSequence
readChunk()Read the next data chunk, or nil on close
channelWindowSnapshot()Report the current receive and send window state for this channel
adjustReceiveWindow(by:)Send SSH_MSG_CHANNEL_WINDOW_ADJUST and return the updated window snapshot
collectDataUntilClose()Gather all bytes plus EOF state until the channel closes

Supporting value type:

  • SSHDirectTCPIPChannelOutput contains data and didReceiveEOF
  • SSHTCPIPChannelEvent exposes .data and .endOfFile
  • for one consistent incremental view, use nextEvent() / events as the single read style on that channel
  • readChunk(), nextEvent() / events, and collectDataUntilClose() each own a distinct buffering mode
  • when readChunk() or an event reader reaches nil, Traversio releases the channel's buffered output state
  • cancelling for try await event in channel.events or breaking out of that loop before channel close now attempts a best-effort channel-close

SSHDirectStreamLocalChannel

SSHDirectStreamLocalChannel is the expert forwarding wrapper for a raw OpenSSH [email protected] channel.

Public methods:

MethodMeaning
write(_ bytes: [UInt8])Send raw bytes to the remote Unix socket
write(_ string: String)Send UTF-8 text
sendEOF()Close the sending side
close()Send channel close explicitly
nextEvent()Read the next streamlocal event, or nil after channel close
eventsConsume streamlocal events as an AsyncSequence
readChunk()Read the next data chunk, or nil on close
channelWindowSnapshot()Report the current receive and send window state for this channel
adjustReceiveWindow(by:)Send SSH_MSG_CHANNEL_WINDOW_ADJUST and return the updated window snapshot
collectDataUntilClose()Gather all bytes plus EOF state until the channel closes

Supporting value types:

  • SSHDirectStreamLocalChannelOutput contains data and didReceiveEOF
  • SSHStreamLocalChannelEvent exposes .data and .endOfFile
  • SSHStreamLocalChannelEventSequence is the event-stream wrapper
  • use one read style per channel: readChunk(), nextEvent() / events, or collectDataUntilClose()
  • when a streaming reader reaches nil, Traversio releases the channel's buffered output state

The public shape mirrors SSHDirectTCPIPChannel; the channel-open payload targets a Unix domain socket path.

SSHLocalPortForward

SSHLocalPortForward describes the currently bound local forwarding listener inside withLocalPortForwarding(...).

Fields:

  • localHost
  • localPort
  • targetHost
  • targetPort

Current usage note:

  • withLocalPortForwarding(...) is available at the package floor
  • Apple 26+ systems prefer the modern listener backend automatically, and older supported releases use the compatibility listener backend
  • the helper is closure-scoped, just like withConnection
  • the helper currently uses a best-effort shutdown contract: after scope exit Traversio stops bridging data and late accepted local connections are closed as soon as possible
  • Traversio does not currently promise that the local port becomes immediately unconnectable at the exact moment the scope returns
  • failure of one accepted local connection stays scoped to that connection; it does not close the parent SSHConnection or poison unrelated sessions/SFTP work

SSHDynamicPortForward

SSHDynamicPortForward describes the currently bound local SOCKS listener inside withDynamicPortForwarding(...).

Fields:

  • localHost
  • localPort

Supporting current auth type:

  • SSHSOCKS5ProxyAuthentication.none
  • SSHSOCKS5ProxyAuthentication.usernamePassword(username:password:)

Current usage note:

  • withDynamicPortForwarding(...) is available at the package floor
  • Apple 26+ systems prefer the modern listener backend automatically, and older supported releases use the compatibility listener backend
  • the helper is closure-scoped, just like withConnection
  • the local listener speaks SOCKS5 in no-auth or username/password mode, and it still accepts SOCKS4 / SOCKS4a only when SOCKS5 auth is not configured
  • shutdown is currently best-effort rather than an instant listener invalidation guarantee
  • failure of one accepted SOCKS connection stays scoped to that connection; it does not close the parent SSHConnection or poison unrelated sessions/SFTP work

SSHRemotePortForward

SSHRemotePortForward describes the currently active remote listener inside withRemotePortForwarding(...).

Fields:

  • localHost
  • localPort
  • remoteHost
  • remotePort

Current usage note:

  • withRemotePortForwarding(...) is available at the package floor
  • Apple 26+ systems prefer the modern outbound transport backend automatically, and older supported releases use the compatibility backend for the local bridge path
  • the helper is closure-scoped, just like withConnection and withLocalPortForwarding(...)
  • if you request remotePort: 0, Traversio reports the allocated remote port through SSHRemotePortForward.remotePort
  • the helper now builds on the lower-level remote listener API, but it still bridges to one fixed local TCP endpoint
  • accepted remote bridge failures stay scoped to the offending connection instead of poisoning later remote clients in the same forwarding scope
  • multiple accepted remote connections can stay bridged at the same time while the forwarding scope remains open
  • if a server rejects cancel-tcpip-forward on scope exit, Traversio closes the parent SSHConnection before returning the error so the remote listener does not stay active on the server
  • after scope exit, Traversio does not leave the remote listener active; if the server cannot confirm cancellation, connection close is the cleanup boundary

SSHRemotePortForwardListener

SSHRemotePortForwardListener describes the currently active remote listener inside withRemotePortForwardListener(...).

Fields:

  • remoteHost
  • remotePort

Public methods:

MethodMeaning
accept()Wait for the next incoming forwarded-tcpip channel

Current usage note:

  • withRemotePortForwardListener(...) is closure-scoped, just like the other public connection helpers
  • if you request remotePort: 0, Traversio reports the allocated remote port through SSHRemotePortForwardListener.remotePort
  • pending accepts are canceled before Traversio sends cancel-tcpip-forward on scope exit
  • if the server rejects that shutdown request, Traversio closes the parent SSHConnection and surfaces the request failure to preserve listener-cleanup semantics
  • an accepted channel failure stays scoped to that channel; callers should continue accepting while the listener scope is active unless accept() itself reports listener or connection lifetime failure
  • after scope exit, Traversio does not leave the remote listener active; if the server cannot confirm cancellation, connection close is the cleanup boundary

SSHRemoteStreamLocalForwardListener

SSHRemoteStreamLocalForwardListener describes the active remote Unix socket listener inside withRemoteStreamLocalForwardListener(...).

Fields:

  • socketPath

Public methods:

MethodMeaning
accept()Wait for the next incoming [email protected] channel

Current usage note:

  • withRemoteStreamLocalForwardListener(...) is closure-scoped, just like the other public connection helpers
  • pending accepts are canceled before Traversio sends [email protected] on scope exit
  • if the server rejects that shutdown request, Traversio closes the parent SSHConnection and surfaces the request failure to preserve listener-cleanup semantics
  • incoming forwarded-tcpip and [email protected] opens are queued separately, so an active accept loop for one listener type can preserve incoming opens for the other type
  • an accepted streamlocal channel failure stays scoped to that channel; callers should continue accepting while the listener scope is active unless accept() itself reports listener or connection lifetime failure

SSHForwardedTCPIPChannel

SSHForwardedTCPIPChannel is the current expert wrapper for one incoming remote-forward connection accepted through SSHRemotePortForwardListener.accept().

Fields:

  • listeningHost
  • listeningPort
  • originatorHost
  • originatorPort

Public methods:

MethodMeaning
write(_ bytes: [UInt8])Send raw bytes back through the accepted forwarded channel
write(_ string: String)Send UTF-8 text
sendEOF()Close the sending side
close()Send channel close explicitly
nextEvent()Read the next forwarding event, or nil after channel close
eventsConsume forwarding events as an AsyncSequence
readChunk()Read the next data chunk, or nil on close
channelWindowSnapshot()Report the current receive and send window state for this channel
adjustReceiveWindow(by:)Send SSH_MSG_CHANNEL_WINDOW_ADJUST and return the updated window snapshot
collectDataUntilClose()Gather all bytes plus EOF state until the channel closes

Supporting value type:

  • SSHForwardedTCPIPChannelOutput contains data and didReceiveEOF
  • SSHTCPIPChannelEvent exposes .data and .endOfFile
  • for one consistent incremental view, use nextEvent() / events as the single read style on that channel
  • readChunk(), nextEvent() / events, and collectDataUntilClose() each own a distinct buffering mode
  • when readChunk() or an event reader reaches nil, Traversio releases the channel's buffered output state
  • cancelling for try await event in channel.events or breaking out of that loop before channel close now attempts a best-effort channel-close

SSHForwardedStreamLocalChannel

SSHForwardedStreamLocalChannel is the wrapper for one incoming streamlocal connection accepted through SSHRemoteStreamLocalForwardListener.accept().

Fields:

  • socketPath

Public methods:

MethodMeaning
write(_ bytes: [UInt8])Send raw bytes back through the accepted streamlocal channel
write(_ string: String)Send UTF-8 text
sendEOF()Close the sending side
close()Send channel close explicitly
nextEvent()Read the next streamlocal event, or nil after channel close
eventsConsume streamlocal events as an AsyncSequence
readChunk()Read the next data chunk, or nil on close
channelWindowSnapshot()Report the current receive and send window state for this channel
adjustReceiveWindow(by:)Send SSH_MSG_CHANNEL_WINDOW_ADJUST and return the updated window snapshot
collectDataUntilClose()Gather all bytes plus EOF state until the channel closes

Supporting value type:

  • SSHForwardedStreamLocalChannelOutput contains data and didReceiveEOF
  • SSHStreamLocalChannelEvent exposes .data and .endOfFile
  • use one read style per channel: readChunk(), nextEvent() / events, or collectDataUntilClose()
  • when a streaming reader reaches nil, Traversio releases the channel's buffered output state
  • cancelling for try await event in channel.events or breaking out of that loop before channel close attempts a best-effort channel-close

SSHPseudoTerminalRequest

Use this type when opening a shell with custom PTY settings.

Fields:

  • terminalType
  • characterWidth
  • characterHeight
  • pixelWidth
  • pixelHeight
  • encodedTerminalModes

Convenience:

  • SSHPseudoTerminalRequest.default

SFTPClient

SFTPClient is the public file-transfer wrapper.

Public methods:

MethodMeaning
close()Close the SFTP subsystem channel explicitly
currentVersionExchange()Return SSHSFTPVersionExchange
realPath(_:)Resolve a remote path
lstat(_:)Query symlink-aware metadata
stat(_:)Query metadata
setAttributes(_:,attributes:)Update path metadata with SSH_FXP_SETSTAT
fileSystemAttributes(_:)Query filesystem-level capacity and flags through OpenSSH [email protected]
openFile(_:,flags:,attributes:)Open a public SFTPFileHandle for handle-scoped reads, writes, metadata, and explicit close
listDirectory(_:)Read directory entries
readFile(_:,chunkSize:maxConcurrentReads:progress:)Read a whole file, optionally using bounded concurrent read requests on one handle and reporting cumulative progress
downloadFile(_:,to:expectedSize:chunkSize:maxConcurrentReads:progress:shouldContinue:)Stream one remote file directly into a local file URL, optionally using a bounded read window when the expected size is known
downloadDirectory(_:,to:chunkSize:maxConcurrentReads:progress:shouldContinue:)Recursively download one remote directory tree into a local directory URL, skipping symlinks and unsupported entry kinds
resumeDownloadFile(_:,existingData:,chunkSize:maxConcurrentReads:progress:)Resume a whole-file download from a caller-provided local prefix when the remote file is larger, then return SSHSFTPResumeDownloadResult
writeFile(_:,data:,chunkSize:maxConcurrentWrites:syncAfterWrite:progress:)Write a whole file, optionally keeping a bounded number of SSH_FXP_WRITE requests in flight before close and reporting cumulative progress
uploadFile(from:to:attributes:chunkSize:maxConcurrentWrites:syncAfterWrite:progress:shouldContinue:)Stream one local file URL into a remote path with a bounded write window
uploadDirectory(from:to:fileAttributes:directoryAttributes:chunkSize:maxConcurrentWrites:syncAfterWrite:progress:shouldContinue:)Recursively upload one local directory tree into a remote directory path, skipping symlinks and unsupported entry kinds
resumeUploadFile(_:,data:,chunkSize:maxConcurrentWrites:syncAfterWrite:progress:)Resume a whole-file upload from the current remote file size when the server reports one, then return SSHSFTPResumeUploadResult
makeDirectory(_:,attributes:)Create a directory
removeFile(_:)Remove a file
removeDirectory(_:)Remove a directory
rename(_:,to:)Rename a path
readLink(_:)Read a symlink target
createSymbolicLink(targetPath:linkPath:)Create a symlink

SFTPFileHandle

SFTPFileHandle is the public low-level file-handle wrapper returned by SFTPClient.openFile(...).

Public methods:

MethodMeaning
tell()Return the handle cursor offset used by sequential handle reads and writes
seek(to:)Set the handle cursor offset
rewind()Set the handle cursor offset to zero
read(length:)Read from the current handle cursor and advance by the returned byte count
read(at:length:)Read up to length bytes from a specific offset, returning nil on EOF
readAll(chunkSize:maxConcurrentReads:progress:)Read a whole file from the current handle using bounded concurrent read requests when desired, with cumulative progress reporting
readChunks(startingAt:chunkSize:)Return an AsyncSequence of SSHSFTPFileChunk values for caller-controlled streamed reads
write(_:)Write bytes at the current handle cursor and advance by the written byte count
write(_:at:)Write bytes at a specific offset
write(contentsOf:startingAt:progress:)Consume an AsyncSequence<[UInt8]> and write the yielded chunks sequentially from one starting offset
stat()Query handle-scoped metadata through SSH_FXP_FSTAT
setAttributes(_:)Update handle-scoped metadata through SSH_FXP_FSETSTAT
fileSystemAttributes()Query handle-scoped filesystem capacity and flags through OpenSSH [email protected]
synchronize()Request OpenSSH [email protected] for the open handle
close()Close the remote file handle explicitly

Supporting public SFTP value and callback types:

  • SFTPFileHandle
  • SSHSFTPFileHandleError
  • SSHSFTPFileChunk
  • SSHSFTPFileChunkSequence
  • SSHSFTPVersionExchange
  • SSHSFTPExtension
  • SSHSFTPNameEntry
  • SSHSFTPOpenFileFlags
  • SSHSFTPDirectoryTransferSummary
  • SSHSFTPDirectoryTransferError
  • SSHSFTPLocalFileTransferError
  • SSHSFTPResumeDownloadResult
  • SSHSFTPResumeUploadResult
  • SSHSFTPResumeError
  • SSHSFTPTransferProgress
  • SSHSFTPTransferContinuationHandler
  • SSHSFTPFileAttributes
  • SSHSFTPFileSystemAttributes
  • SSHSFTPFileSystemFlags

SSHSFTPDirectoryTransferSummary and SSHSFTPDirectoryTransferError

downloadDirectory(...) and uploadDirectory(...) return SSHSFTPDirectoryTransferSummary.

Current members:

  • bytesTransferred
  • filesTransferred
  • directoriesTraversed
  • skippedEntries

SSHSFTPDirectoryTransferError currently exposes:

  • .localURLMustReferenceDirectory(URL)
  • .localURLReferencesFile(URL)
  • .remotePathIsNotDirectory(String)

Current directory-helper behavior:

  • regular files and directories are traversed recursively
  • symbolic links and other unsupported entry kinds are skipped and counted in skippedEntries
  • existing destination directories are reused instead of being removed first
  • helpers check Swift task cancellation and SSHSFTPTransferContinuationHandler
  • partial-tree cleanup policy stays with the caller
  • if the app uses security-scoped resources, the app still owns startAccessingSecurityScopedResource() and stopAccessingSecurityScopedResource()

SSHSFTPLocalFileTransferError

The local-file convenience helpers currently expose:

  • .localURLMustReferenceFile(URL)
  • .localURLReferencesDirectory(URL)

These helpers only accept filesystem-backed file URLs. If the app uses security-scoped resources, the app still owns startAccessingSecurityScopedResource() and stopAccessingSecurityScopedResource() around the transfer call. They also accept shouldContinue: for caller-owned cancellation state and throw CancellationError when the callback returns false.

SSHSFTPResumeDownloadResult, SSHSFTPResumeUploadResult, and SSHSFTPResumeError

resumeDownloadFile(...) returns SSHSFTPResumeDownloadResult.

Current members:

  • path
  • startingOffset
  • bytesDownloaded
  • totalBytes
  • data
  • didResume
  • finalOffset

resumeUploadFile(...) returns SSHSFTPResumeUploadResult.

Current members:

  • path
  • startingOffset
  • bytesUploaded
  • totalBytes
  • didResume
  • finalOffset

SSHSFTPResumeError currently exposes:

  • .remoteFileSizeUnavailable(path:)
  • .remoteFileIsSmallerThanLocalData(path:remoteSize:localSize:)
  • .remoteFileIsLargerThanLocalData(path:remoteSize:localSize:)

SSHSFTPTransferProgress

SSHSFTPTransferProgress is the public progress payload used by the whole-file SFTP convenience APIs.

Current members:

  • operation
  • bytesTransferred
  • totalBytes
  • fractionCompleted

Current behavior:

  • reads report cumulative bytesTransferred
  • resumeDownloadFile(...) reports cumulative read progress against the full remote length
  • writes report cumulative bytesTransferred plus totalBytes
  • downloadDirectory(...) and uploadDirectory(...) report cumulative transferred bytes across the whole tree and currently leave totalBytes as nil
  • fractionCompleted is available when totalBytes is known

SSHSFTPTransferContinuationHandler

SSHSFTPTransferContinuationHandler is the public callback shape used by local-file and recursive directory helpers.

Current behavior:

  • return true to continue transfer work
  • return false to stop with CancellationError
  • helper-owned remote handles are closed during cancellation cleanup
  • directory helpers forward the same callback into child file transfers

Error Surface

The stable public error type today is SSHClientError.

Current cases:

  • authenticationRejected(methodName:availableMethods:partialSuccess:banners:)
  • connectionFailed(SSHConnectionFailure)
  • operationFailed(SSHOperationFailure)
  • passwordChangeRequired(prompt:languageTag:banners:)
  • connectionScopeEnded

authenticationRejected(...) includes the raw SSH auth method name, the methods the server advertised for continuation, the partial-success flag, and any userauth banners collected before rejection. Servers with zero SSH_MSG_USERAUTH_BANNER messages receive an empty banner array.

Supporting public connection-diagnostics types:

  • SSHConnectionFailure
  • SSHConnectionFailureStage
  • SSHConnectionFailureCode
  • SSHConnectionFailureCallbackSource
  • SSHConnectionFailureCallbackDetails
  • SSHCallbackFailureDiagnosticProviding
  • SSHConnectionFailureDiagnostics
  • SSHNegotiatedTransportAlgorithms
  • SSHRemoteDisconnect
  • SSHRemoteDebugMessage
  • SSHOperationFailure
  • SSHOperationFailureScope
  • SSHOperationFailureCode
  • SSHOperationFailureDiagnostics
  • SSHSFTPStatusDetails
  • SSHSFTPStatusCode
  • SSHClientLogRecorder
  • SSHClientLogRecorderSnapshot

connectionFailed(...) is used for connection-setup failures and keeps stage plus diagnostic context instead of exposing lower-level error enums directly.

SSHConnectionFailure now also has diagnosticReport, a copy-ready multi-line summary for support/debug flows.

operationFailed(...) is the stable wrapper for post-auth library operations such as session reads/writes, direct TCP/IP and streamlocal channel setup, forwarded channel I/O, remote TCP and streamlocal listener accept, and SFTP requests. It keeps:

  • the operation scope
  • a stable failure code
  • connection/channel diagnostics such as negotiated algorithms, whether the server sent SSH_MSG_EXT_INFO, the server extension names seen so far, remote disconnect/debug context, local/remote channel IDs when known, and SFTP status details when the server returned them

SSHSFTPStatusDetails keeps the raw numeric code, typed statusCode, optional standard SSH_FX_* name, server message, and language tag. Unknown server-specific status codes keep the raw integer and use nil for the standard name.

SSHOperationFailure now also has diagnosticReport for the same copy/paste workflow.

Failure diagnosticReport output redacts common inline secret fragments in failure messages, pre-identification lines, remote disconnect/debug text, SFTP status messages, and server-provided language-tag fields. The raw diagnostic structs remain focused on protocol evidence; use the report/export helpers for support text.

For source-compatible app logic, branch on the public cases and category fields: SSHClientError, SSHConnectionFailure.stage, SSHConnectionFailure.code, SSHOperationFailure.scope, SSHOperationFailure.code, SSHSFTPStatusDetails.statusCode, and SSHPortLatencyError cases. Treat message, per-sample latency failure text, and diagnosticReport prose as human support text rather than parser input. Traversio keeps these reports recognizable and redacted by default, but exact English wording and line order are not the compatibility contract.

SSHNegotiatedTransportAlgorithms now also reports effective integrity per direction. For CTR + HMAC or UMAC transports that matches the negotiated MAC name; for OpenSSH AES-GCM and [email protected] it reports implicit while still keeping the raw negotiated MAC fields available.

SSHClientLogEvent also now has formattedLine. Applications can still build their own sink-based pipeline around that, and SSHClientLogRecorder now provides the built-in bounded recent-event buffer when they want a ready-to-use copy/export path.

For public client errors which are not connectionFailed(...) or operationFailed(...), SSHClientLogRecorderSnapshot.diagnosticReport(for:) generates a compact SSH client error support section. That covers authenticationRejected(...), passwordChangeRequired(...), and connectionScopeEnded; when recent log events are present, the report appends the same redacted formatted log lines. Use this support-export path for background-failure flows where a later escaped operation fails at the connection lifetime boundary.

For a concrete developer-facing integration pattern, including a recent-event recorder and copyable support payload export, see Diagnostics.

When the host-key policy callback, password-change response callback, keyboard-interactive response callback, or public-key signature callback throws, SSHConnectionFailureDiagnostics.callbackFailure describes the callback source and error type without exposing lower-level callback plumbing. If the thrown error conforms to SSHCallbackFailureDiagnosticProviding, Traversio also copies its stable diagnostic code and optional safe summary into SSHConnectionFailureCallbackDetails, connection log metadata, and diagnosticReport support text. That is the branching surface for app-owned callback semantics such as rejected host trust, unavailable UI context, persistence failure, or concurrent trust-store updates.

Some errors can still escape unchanged when they come from your own callbacks or trust-policy code. The library does not hide those behind SSHClientError.

Practical Rule

When you evaluate Traversio, think of this public API as:

  • real enough to build against
  • focused on the documented client workflows
  • explicit about connection ownership and wrapper lifetime
  • explicit about cancellation and forwarding lifecycle limits, with broader host-trust breadth and additional interoperability evidence still outside the current release line

On this page