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 -> SSHAuthenticationMethodDiscoveryResultconnect(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:
SSHConnectionLatencySSHConnectionLatencySource
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 -> SSHPortLatencyReportThis 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
connectionProxytargets perform the configured SOCKS5 or HTTP CONNECT setup before samplingproxyJumpHoststargets authenticate jump hosts once, then open a freshdirect-tcpipchannel to the final endpoint for each sampleconfiguration:reuses the endpoint, connection proxy, and jump-host route from an existingSSHClientConfiguration- 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:
SSHPortLatencyOptionsSSHPortLatencyReportSSHPortLatencySampleSSHPortLatencyFailureSSHPortLatencyStatistics
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.averageMillisecondstells you how quickly the route setup step completed; it is not always comparable to pingsshServiceRequestRTTStatistics.averageMillisecondsis the ping-like final-server SSH request/response RTTestimatedPathOneWayFromSSHServiceRequestStatistics.averageMillisecondsis 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.
| Field | Type | Meaning |
|---|---|---|
host | String | Remote host name or address |
port | UInt16 | Remote port, default 22 |
username | String | SSH username |
authentication | SSHAuthenticationMethod | Auth method |
hostKeyPolicy | SSHHostKeyPolicy | Required host trust policy |
compressionPreference | SSHCompressionPreference | Optional transport compression preference: .disabled, RFC 4253 .zlib, or delayed OpenSSH .delayedZlib |
legacyAlgorithmOptions | SSHLegacyAlgorithmOptions | Optional explicit legacy ssh-rsa host-key and RSA auth compatibility |
automaticRekeyPolicy | SSHAutomaticRekeyPolicy | Optional automatic client-initiated rekey thresholds and idle timer |
keepalivePolicy | SSHKeepalivePolicy | Optional post-auth idle keepalive policy, disabled by default |
timeoutPolicy | SSHTimeoutPolicy | Setup, host-key trust, and reply timeout policy; defaults to 30 seconds for setup, 120 seconds for host-key trust, and unbounded reply waits |
connectionProxy | SSHConnectionProxy? | 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:
hostportusernameauthenticationhostKeyPolicycompressionPreferencelegacyAlgorithmOptionsautomaticRekeyPolicykeepalivePolicytimeoutPolicy
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.
| Field | Type | Meaning |
|---|---|---|
host | String | Remote host name or address |
port | UInt16 | Remote port, default 22 |
username | String | SSH username used for the none auth request |
hostKeyPolicy | SSHHostKeyPolicy | Required host trust policy |
compressionPreference | SSHCompressionPreference | Optional transport compression preference for the temporary discovery connection |
legacyAlgorithmOptions | SSHLegacyAlgorithmOptions | Optional explicit legacy host-key compatibility for the temporary discovery connection |
timeoutPolicy | SSHTimeoutPolicy | Setup, host-key trust, and reply timeout policy; defaults to 30 seconds for setup and 120 seconds for host-key trust |
connectionProxy | SSHConnectionProxy? | 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:
usernameserviceNameavailableMethodspartialSuccessallowsUnauthenticatedAccessbanners
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:
messagelanguageTag
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-rsato 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-rsaafter the server declines the RSA-SHA2 attempt and still offerspublickey
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:
hostportauthentication
SSHSOCKS5ProxyAuthentication exposes:
.none.usernamePassword(username:password:)
SSHHTTPConnectConnectionProxy exposes:
hostportauthentication
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:
outboundPacketThresholdinboundPacketThresholdidleTimeInterval.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.stateEventswhen 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:
defaultConnectionSetupTimeIntervaldefaultHostKeyTrustTimeInterval.currentProfileDefault.disabledconnectionSetupTimeIntervalhostKeyTrustTimeIntervalresponseTimeInterval
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, andsubsystem - 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:
.disabledinit(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:
timestamplevelcategorymessagemetadata
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:
eventsmaximumEventCountdroppedEventCountformattedLinesformattedTextdiagnosticReport(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:
usernameserviceNamealgorithmNamepublicKeysignatureDatamakeSignatureBlob(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:
publicKeycommentkeyTypesupportedAuthenticationAlgorithmNames
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:
algorithmcommentauthenticationMethodprivateKeyPEMauthorizedKeyLine
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:
usernameserviceNamepromptlanguageTagbanners
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:
usernameserviceNamenameinstructionlanguageTagprompts
Each SSHKeyboardInteractivePrompt exposes:
promptshouldEcho
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:
algorithmNamerawRepresentationfingerprintSHA256
SSHHostKeyTrustMethod tells you which trust path was used:
acceptAnyVerifiedHostKeyexactMatchtrustedSetMatchcertificateAuthorityMatchcallback
SSHHostKeyValidationRequest exposes:
endpointHostendpointPortremoteIdentificationtrustedHostKeymatches(_:)
SSHHostKeyChangeRequest exposes:
endpointHostendpointPortremoteIdentificationstoredHostKeyreceivedHostKey
SSHHostKeyStoreRequest exposes:
endpointHostendpointPortremoteIdentificationexpectedStoredHostKeytrustedHostKeymatchesExpectedStoredHostKey(_:)
SSHHostKeyChangeDecision exposes:
rejectreplaceStoredHostKey
SSHHostKeyTrustStore exposes:
lookupHostKey(endpointHost:endpointPort:)storeHostKey(_:)decisionForChangedHostKey(_:)
SSHConnection
SSHConnection is the public connection wrapper returned by connect(configuration:) and passed into withConnection(...).
Public members:
| Member | Type | Meaning |
|---|---|---|
metadata | SSHConnectionMetadata | Handshake and trust details for the active connection |
stateEvents | SSHConnectionStateEventSequence | Incremental connection-state stream for transport state, backend-provided path, viability, and better-path details, liveness, close, and loss transitions |
currentState() | async -> SSHConnectionStateSnapshot | Read the latest connection-state snapshot without consuming the event stream |
close() | async | Close the SSH connection explicitly and invalidate every wrapper created from it |
execute(_:environment:) | async throws -> SSHExecResult | Execute a one-shot remote command, optionally with SSH env requests sent before exec |
openExec(_:environment:) | async throws -> SSHSession | Open a streamed exec session on a session channel, optionally with SSH env requests |
openShell(pseudoTerminalRequest:environment:) | async throws -> SSHSession | Open a PTY-backed shell session, optionally with SSH env requests sent before shell startup |
openSubsystem(_:environment:) | async throws -> SSHSession | Open a named subsystem on a session channel, optionally with SSH env requests sent before subsystem startup |
openSFTP(clientVersion:) | async throws -> SFTPClient | Start an SFTP client on a subsystem channel |
receiveSCPFile(_:maximumFileSize:) | async throws -> SSHSCPReceivedFile | Receive one regular file through the legacy SCP protocol and buffer it in memory |
sendSCPFile(_:remotePath:fileName:permissions:) | async throws -> SSHSCPTransferResult | Send one in-memory regular file through the legacy SCP protocol |
downloadSCPFile(_:to:maximumFileSize:) | async throws -> SSHSCPTransferResult | Receive one SCP file and write it to a local file URL |
uploadSCPFile(from:to:fileName:permissions:) | async throws -> SSHSCPTransferResult | Read one local file URL and send it through the legacy SCP protocol |
openDirectTCPIPChannel(targetHost:targetPort:originatorAddress:originatorPort:) | async throws -> SSHDirectTCPIPChannel | Open a raw direct-tcpip forwarding channel |
openDirectStreamLocalChannel(socketPath:originatorAddress:originatorPort:) | async throws -> SSHDirectStreamLocalChannel | Open a raw OpenSSH [email protected] channel |
withLocalPortForwarding(targetHost:targetPort:localHost:localPort:_:) | async throws -> Result | Start a closure-scoped local listener |
withDynamicPortForwarding(localHost:localPort:socks5Authentication:_:) | async throws -> Result | Start a closure-scoped local SOCKS proxy |
withRemotePortForwardListener(remoteHost:remotePort:_:) | async throws -> Result | Start a closure-scoped remote listener and accept raw incoming forwarded-tcpip channels |
withRemoteStreamLocalForwardListener(socketPath:_:) | async throws -> Result | Start a closure-scoped remote Unix socket listener and accept raw incoming [email protected] channels |
withRemotePortForwarding(localPort:remoteHost:remotePort:localHost:_:) | async throws -> Result | Start a closure-scoped remote listener and bridge it back to one local TCP endpoint |
SCP Transfer Types
SSHSCPReceivedFile is returned by receiveSCPFile(...).
Fields:
remotePathfileNamepermissionsbyteCountcontentsexitStatus
SSHSCPTransferResult is returned by sendSCPFile(...), downloadSCPFile(...), and uploadSCPFile(...).
Fields:
remotePathfileNamebyteCountexitStatus
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:
endpointHostendpointPortusernameclientIdentificationremoteIdentificationpreIdentificationLinesauthenticationBannershostKeyAlgorithmhostKeyFingerprintSHA256hostKeyTrustMethod
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
.lostwithout waiting for the next shell, SFTP, or forwarding operation - the sequence finishes after
.lostor.closed - applications use this sequence as the input for reconnect and channel-recreation policy
SSHConnectionStateEvent
SSHConnectionStateEvent contains:
triggersnapshot
trigger tells you why the snapshot changed. snapshot carries the latest classified connection state plus the most recent observed network details.
SSHConnectionStateSnapshot
Snapshot fields:
statetransportStatenetworkPathisTransportViablebetterPathAvailabledetail
state is one of:
.ready.degraded.lost.closed
SSHConnectionNetworkPath
Path fields:
statusavailableInterfacesisExpensiveisConstrainedsupportsIPv4supportsIPv6
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:
standardOutputstandardErrorexitStatusexitSignaldidReceiveEOF
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:
namevalue
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:
| Method | Meaning |
|---|---|
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 |
events | Consume 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:
SSHSessionOutputmirrors the transcript fields ofSSHExecResultSSHSessionEventexposes.standardOutput,.standardError,.exitStatus,.exitSignal, and.endOfFileresizePseudoTerminal(...)is mainly useful for PTY-backed shells opened throughopenShell(...); plain exec sessions currently do not allocate a PTY automaticallysendSignal(_:)sends RFC 4254signalrequests such asTERMorINT; the server may ignore them, and there is no reply message for this request- remote
exit-signalrequests now surface throughSSHSessionEvent.exitSignal(...)and theexitSignalfield on collected outputs - choose one output reader per session:
nextEvent()/events,readStandardOutputChunk(), orcollectOutputUntilClose() - when a streaming reader reaches
nil, Traversio releases the channel's buffered output state - cancelling
collectOutputUntilClose()now triggers a best-effortchannel-closebefore the task surfacesCancellationError - cancelling
for try await event in session.eventsor breaking out of that loop before channel close now triggers that same best-effortchannel-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:
localChannelIDremoteChannelIDreceiveWindowByteCountreceiveInitialWindowByteCountsendWindowByteCountsendInitialWindowByteCountsendMaximumPacketByteCount
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:
signaldidCoreDumperrorMessagelanguageTag
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:
| Method | Meaning |
|---|---|
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 |
events | Consume 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:
SSHDirectTCPIPChannelOutputcontainsdataanddidReceiveEOFSSHTCPIPChannelEventexposes.dataand.endOfFile- for one consistent incremental view, use
nextEvent()/eventsas the single read style on that channel readChunk(),nextEvent()/events, andcollectDataUntilClose()each own a distinct buffering mode- when
readChunk()or an event reader reachesnil, Traversio releases the channel's buffered output state - cancelling
for try await event in channel.eventsor breaking out of that loop before channel close now attempts a best-effortchannel-close
SSHDirectStreamLocalChannel
SSHDirectStreamLocalChannel is the expert forwarding wrapper for a raw OpenSSH [email protected] channel.
Public methods:
| Method | Meaning |
|---|---|
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 |
events | Consume 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:
SSHDirectStreamLocalChannelOutputcontainsdataanddidReceiveEOFSSHStreamLocalChannelEventexposes.dataand.endOfFileSSHStreamLocalChannelEventSequenceis the event-stream wrapper- use one read style per channel:
readChunk(),nextEvent()/events, orcollectDataUntilClose() - 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:
localHostlocalPorttargetHosttargetPort
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
SSHConnectionor poison unrelated sessions/SFTP work
SSHDynamicPortForward
SSHDynamicPortForward describes the currently bound local SOCKS listener inside withDynamicPortForwarding(...).
Fields:
localHostlocalPort
Supporting current auth type:
SSHSOCKS5ProxyAuthentication.noneSSHSOCKS5ProxyAuthentication.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
SSHConnectionor poison unrelated sessions/SFTP work
SSHRemotePortForward
SSHRemotePortForward describes the currently active remote listener inside withRemotePortForwarding(...).
Fields:
localHostlocalPortremoteHostremotePort
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
withConnectionandwithLocalPortForwarding(...) - if you request
remotePort: 0, Traversio reports the allocated remote port throughSSHRemotePortForward.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-forwardon scope exit, Traversio closes the parentSSHConnectionbefore 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:
remoteHostremotePort
Public methods:
| Method | Meaning |
|---|---|
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 throughSSHRemotePortForwardListener.remotePort - pending accepts are canceled before Traversio sends
cancel-tcpip-forwardon scope exit - if the server rejects that shutdown request, Traversio closes the parent
SSHConnectionand 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:
| Method | Meaning |
|---|---|
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
SSHConnectionand surfaces the request failure to preserve listener-cleanup semantics - incoming
forwarded-tcpipand[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:
listeningHostlisteningPortoriginatorHostoriginatorPort
Public methods:
| Method | Meaning |
|---|---|
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 |
events | Consume 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:
SSHForwardedTCPIPChannelOutputcontainsdataanddidReceiveEOFSSHTCPIPChannelEventexposes.dataand.endOfFile- for one consistent incremental view, use
nextEvent()/eventsas the single read style on that channel readChunk(),nextEvent()/events, andcollectDataUntilClose()each own a distinct buffering mode- when
readChunk()or an event reader reachesnil, Traversio releases the channel's buffered output state - cancelling
for try await event in channel.eventsor breaking out of that loop before channel close now attempts a best-effortchannel-close
SSHForwardedStreamLocalChannel
SSHForwardedStreamLocalChannel is the wrapper for one incoming streamlocal connection accepted through SSHRemoteStreamLocalForwardListener.accept().
Fields:
socketPath
Public methods:
| Method | Meaning |
|---|---|
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 |
events | Consume 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:
SSHForwardedStreamLocalChannelOutputcontainsdataanddidReceiveEOFSSHStreamLocalChannelEventexposes.dataand.endOfFile- use one read style per channel:
readChunk(),nextEvent()/events, orcollectDataUntilClose() - when a streaming reader reaches
nil, Traversio releases the channel's buffered output state - cancelling
for try await event in channel.eventsor breaking out of that loop before channel close attempts a best-effortchannel-close
SSHPseudoTerminalRequest
Use this type when opening a shell with custom PTY settings.
Fields:
terminalTypecharacterWidthcharacterHeightpixelWidthpixelHeightencodedTerminalModes
Convenience:
SSHPseudoTerminalRequest.default
SFTPClient
SFTPClient is the public file-transfer wrapper.
Public methods:
| Method | Meaning |
|---|---|
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:
| Method | Meaning |
|---|---|
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:
SFTPFileHandleSSHSFTPFileHandleErrorSSHSFTPFileChunkSSHSFTPFileChunkSequenceSSHSFTPVersionExchangeSSHSFTPExtensionSSHSFTPNameEntrySSHSFTPOpenFileFlagsSSHSFTPDirectoryTransferSummarySSHSFTPDirectoryTransferErrorSSHSFTPLocalFileTransferErrorSSHSFTPResumeDownloadResultSSHSFTPResumeUploadResultSSHSFTPResumeErrorSSHSFTPTransferProgressSSHSFTPTransferContinuationHandlerSSHSFTPFileAttributesSSHSFTPFileSystemAttributesSSHSFTPFileSystemFlags
SSHSFTPDirectoryTransferSummary and SSHSFTPDirectoryTransferError
downloadDirectory(...) and uploadDirectory(...) return SSHSFTPDirectoryTransferSummary.
Current members:
bytesTransferredfilesTransferreddirectoriesTraversedskippedEntries
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()andstopAccessingSecurityScopedResource()
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:
pathstartingOffsetbytesDownloadedtotalBytesdatadidResumefinalOffset
resumeUploadFile(...) returns SSHSFTPResumeUploadResult.
Current members:
pathstartingOffsetbytesUploadedtotalBytesdidResumefinalOffset
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:
operationbytesTransferredtotalBytesfractionCompleted
Current behavior:
- reads report cumulative
bytesTransferred resumeDownloadFile(...)reports cumulative read progress against the full remote length- writes report cumulative
bytesTransferredplustotalBytes downloadDirectory(...)anduploadDirectory(...)report cumulative transferred bytes across the whole tree and currently leavetotalBytesasnilfractionCompletedis available whentotalBytesis known
SSHSFTPTransferContinuationHandler
SSHSFTPTransferContinuationHandler is the public callback shape used by local-file and recursive directory helpers.
Current behavior:
- return
trueto continue transfer work - return
falseto stop withCancellationError - 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:
SSHConnectionFailureSSHConnectionFailureStageSSHConnectionFailureCodeSSHConnectionFailureCallbackSourceSSHConnectionFailureCallbackDetailsSSHCallbackFailureDiagnosticProvidingSSHConnectionFailureDiagnosticsSSHNegotiatedTransportAlgorithmsSSHRemoteDisconnectSSHRemoteDebugMessageSSHOperationFailureSSHOperationFailureScopeSSHOperationFailureCodeSSHOperationFailureDiagnosticsSSHSFTPStatusDetailsSSHSFTPStatusCodeSSHClientLogRecorderSSHClientLogRecorderSnapshot
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