CX · Reference guidesftp
PlaygroundDownloadsAboutv0.18.0-pre.1

sftp

SSH file transfer: an SSH session to one peer, its host key verified against bound known-hosts rows before any credential is used and never trusted on first use, and the seven-verb file surface of file_surface.md (list, stat, get, put, rename, delete, mkdir) over the SFTP v3 subsystem, with resumable transfers, size and time bounds and a bound root no path and no link escapes. Every decision is a pure function of its arguments; the session and the seven verbs are impure and gated by the net capability. The transport is libssh2 behind the V fork with mbedTLS its crypto backend.

sftp:resolve

[$sftp:resolve] -> string — Resolve a path against the bound root, lexically and normalizing; a path that resolves outside the root is refused CXER6705, and an absolute path is held to the same confinement.

      [?lib 'cx-platform/sftp']
[$sftp:resolve '/srv/drop' 'in/../../etc/shadow']
    
      cx-err:CXER6705
    

sftp:resolve-pair

[$sftp:resolve-pair] -> element — Resolve both paths of a rename against the root, answering [pair from= to=]; either side escaping is CXER6705.

      [?lib 'cx-platform/sftp']
[$sftp:resolve-pair '/srv/drop' 'in/orders-1.csv' '../../tmp/orders-1.csv']
    
      cx-err:CXER6705
    

sftp:verify-host-key

[$sftp:verify-host-key] -> bool — Verify a presented [host-key algorithm= fingerprint=] against the bound known-hosts rows for the host and port: true, CXER6702 for no row (never a first-use pin), or CXER6701 naming the expected and presented fingerprints (never a re-pin).

      [?lib 'cx-platform/sftp']
[$sftp:verify-host-key [known-hosts] 'drop.example.test' 22
  [host-key algorithm=ssh-ed25519 fingerprint='sha2-256:9f2b47c1']]
    
      cx-err:CXER6702
    

sftp:check-binding

[$sftp:check-binding] -> element — Report a binding's findings — no root (CXER6717), no known-hosts handle, no host — as [findings]; a credential VALUE in the binding is refused CXER6310.

      [?lib 'cx-platform/sftp']
[$sftp:check-binding
  [binding host='drop.example.test' port=22 root='/srv/drop'
           known-hosts='drop-known-hosts' auth=public-key
           private-key='-----BEGIN OPENSSH PRIVATE KEY-----']]
    
      cx-err:CXER6310
    

sftp:check-name

[$sftp:check-name] -> string — Answer a name that is one path segment; a separator, CR, LF or NUL is refused CXER6707 where the name enters.

      [?lib 'cx-platform/sftp']
[$sftp:check-name 'orders-1.csv\nX-Injected: 1']
    
      cx-err:CXER6707
    

sftp:check-bounds

[$sftp:check-bounds] -> bool — Check an observation against the bounds, every bound inclusive: entries (CXER6713), octets seen (CXER6714), and with an instant the duration and the stall floor (CXER6715).

      [?lib 'cx-platform/sftp']
[$sftp:check-bounds
  [bounds max-entries=10000 max-bytes=4294967296 max-seconds=3600 stall-seconds=60]
  [observed entries=10001]]
    
      cx-err:CXER6713
    

sftp:bind-bounds

[$sftp:bind-bounds] -> element — Answer the effective bounds, each the lower of the ceiling and the deployment's; a deployment raising a bound is refused CXER6713.

      [?lib 'cx-platform/sftp']
[$sftp:bind-bounds
  [ceiling max-entries=10000 max-bytes=4294967296 max-seconds=3600 stall-seconds=60]
  [bounds max-entries=1000000]]
    
      cx-err:CXER6713
    

sftp:classify

[$sftp:classify] -> any — Place an SFTP [status] in the file surface's nine classes: a completion for OK and EOF, otherwise the class's err carrying class= and retryable=; an unclassified status is :protocol and not retryable.

      [?lib 'cx-platform/sftp']
[$sftp:classify [status code=3 verb=get message='Permission denied']]
    
      cx-err:CXER6709
    

sftp:plan-transfer

[$sftp:plan-transfer] -> element — Plan a transfer from the request and the partial already held: offset, remaining, resumed, complete; a partial longer than the source, one with nothing to compare, or one whose address is not the source's is CXER6716.

      [?lib 'cx-platform/sftp']
[$sftp:plan-transfer
  [request path='in/orders-1.csv' size=2048 address='sha2-256:ab12cd34']
  [partial bytes=1024 address='sha2-256:99999999']]
    
      cx-err:CXER6716
    

sftp:read-listing

[$sftp:read-listing] -> element — Read the peer's [names] as a complete [listing]: every name checked, every link's target resolved against the root, a refusal never a silently dropped entry.

      [?lib 'cx-platform/sftp']
[$sftp:read-listing '/srv/drop'
  [names
    [attrs name='orders-1.csv' type=:regular size=2048 mtime='2026-09-14T09:00:00Z']
    [attrs name='ok\r\nDELE *' type=:regular size=1 mtime='2026-09-14T09:00:00Z']]]
    
      cx-err:CXER6707
    

sftp:read-entry

[$sftp:read-entry] -> element — Read one path's [attrs] as the [entry] of what the entry IS — a link is a :link with its target checked against the root.

sftp:open

[$sftp:open] -> element — Open a session for a binding: dial, verify the presented host key against the known-hosts rows the caller hands in opts, and only then authenticate with the resolved credential (opts credential:, passphrase:) and start the subsystem. Idempotent per binding.

      [?lib 'cx-platform/sftp']
[$sftp:open [binding host='drop.example.test' port=22 root='/srv/drop'
                     known-hosts='drop-known-hosts' auth=public-key credential='drop-key']]
    
      cx-err:CXER0271
    

sftp:close

[$sftp:close] -> null — Close a session (idempotent).

sftp:list

[$sftp:list] -> element — List one directory beneath the root as a complete, bounded [listing]; a directory over the entry bound is refused CXER6713 after reading one entry past it.

      [?lib 'cx-platform/sftp']
[$sftp:list [sftp-session] 'in']
    
      cx-err:CXER0271
    

sftp:stat

[$sftp:stat] -> element — Answer the [entry] for one path by LSTAT — what the entry is, never what a link points at.

sftp:get

[$sftp:get] -> element — Transfer a file from the peer, resumably (opts partial:, address:), bounded by octets, seconds and the stall floor; answers the [transfer] with the octets as its [data]; a session lost mid-read is CXER6700 carrying the [partial] a resume starts from.

      [?lib 'cx-platform/sftp']
[$sftp:get [sftp-session] 'in/orders-1.csv']
    
      cx-err:CXER0271
    

sftp:put

[$sftp:put] -> element — Transfer octets to the peer, resumably; a fresh put never overwrites (an existing path is CXER6710) and a resume checks the far side's size against the partial first.

      [?lib 'cx-platform/sftp']
[$sftp:put [sftp-session] 'out/orders-1.csv' [source path='in/orders-1.csv']]
    
      cx-err:CXER0271
    

sftp:rename

[$sftp:rename] -> element — Rename one path to another, both resolved against the root; never over an existing target.

sftp:delete

[$sftp:delete] -> element — Delete one file or one empty directory (the kind is stat's answer); never recursive, and an absent path is CXER6708.

      [?lib 'cx-platform/sftp']
[$sftp:delete [sftp-session] 'in/orders-1.csv']
    
      cx-err:CXER0271
    

sftp:mkdir

[$sftp:mkdir] -> element — Make one directory, one segment; no implicit parents.

sftp:test-server

[$sftp:test-server] -> element — Boot the in-process SSH/SFTP v3 test server on 127.0.0.1 over an in-memory tree of [user], [dir], [file] and [link] rows (with stall-at=, drop-at= and status= faults), answering its port, host-key algorithm and fingerprint and each key user's generated private key.

sftp:test-server-stop

[$sftp:test-server-stop] -> null — Stop a test server and close its connections (idempotent).

sftp:test-server-file

[$sftp:test-server-file] -> any — Answer the octets a test server's tree holds at a path, or absence.