stacks/ts-sftp
publicClone
Push over the same URL. A password will not work: create a token under access tokens and use it in place of one.
- .config
- .github
- .vscode
- bin
- docs
- src
- test
- .editorconfig 147 B
- .gitattributes 12 B
- .gitignore 122 B
- build.ts 329 B
- bun.lock 21.3 KB
- bunfig.toml 107 B
- CHANGELOG.md 1.7 KB
- CLAUDE.md 1.0 KB
- deps.yaml 32 B
- LICENSE.md 1.1 KB
- package.json 3.6 KB
- pantry.lock 164 B
- README.md 4.3 KB
- tsconfig.json 627 B
ts-sftp
An SFTP server for Bun.
The SSH transport, key exchange, public key authentication, and the SFTP
subsystem are implemented in TypeScript on top of node:crypto — nothing
native is compiled, and the whole thing runs anywhere Bun runs. The protocol
implementation pulls in nothing at all; the only package installed alongside it
is @stacksjs/clapp, which the CLI uses
for argument parsing and which has no dependencies of its own.
Features
- SSH transport —
curve25519-sha256key exchange, Ed25519 host keys,aes256-gcm@openssh.comencryption, and OpenSSH's strict KEX extension - Public key auth — Ed25519 and RSA (
rsa-sha2-256/512) user keys, read straight fromauthorized_keyslines - SFTP v3 — what every client speaks, including the OpenSSH
sftpcommand, FileZilla, Cyberduck, and WinSCP - Per-user roots — each user is chrooted to their own directory, with
..resolved inside that namespace so it cannot climb out - Pluggable storage — implement
SftpFileSystemto serve something other than local disk (object storage, a database, a virtual tree) - Read-only mode — per server or per user
- Backpressure-aware — a peer that stops reading is hung up on rather than buffered until the process runs out of memory
Install
bun add ts-sftpQuick start
Generate a host key and serve a directory:
bunx ts-sftp keygen --out ./host_key
bunx ts-sftp serve --root ./uploads --host-key ./host_key --user deploy:./deploy.pubThen connect with any client:
sftp -P 2222 -i ~/.ssh/id_ed25519 deploy@localhostLibrary
import { SftpServer } from 'ts-sftp'
const server = new SftpServer({
port: 2222,
hostKeys: [await Bun.file('./host_key').text()],
users: {
deploy: {
publicKeys: ['ssh-ed25519 AAAAC3Nz... deploy@example.com'],
root: './uploads',
},
viewer: {
publicKeys: [await Bun.file('./viewer.pub').text()],
root: './uploads',
readOnly: true,
},
},
})
const { port } = server.listen()
console.log(`listening on ${port}`)Custom authentication
authenticate runs after the signature has been verified, so a true return
means the key is genuine as well as accepted:
const server = new SftpServer({
authenticate: async ({ username, method, publicKey }) => {
if (method !== 'publickey' || !publicKey) return false
return await isEnabled(username, publicKey.comment)
},
})Custom storage
Any object implementing SftpFileSystem can back a session — the built-in
LocalFileSystem is one implementation, not a requirement:
import type { SftpFileSystem } from 'ts-sftp'
import { SftpServer } from 'ts-sftp'
const server = new SftpServer({
createFileSystem: ({ username }): SftpFileSystem => bucketBackedFileSystem(username),
})CLI
ts-sftp serve [options] Serve a directory over SFTP
ts-sftp keygen [options] Generate an Ed25519 host key
Serve options:
--config <path> Config file (default: sftp.config.ts, if present)
--port <port> Port to listen on (default: 2222)
--host <address> Address to bind (default: 0.0.0.0)
--root <dir> Directory to serve (default: the working directory)
--host-key <path> Host key file. Generated in memory when omitted
--user <name>:<keys path> Grant a user access using an authorized_keys file. Repeatable
--read-only Reject every write
--verbose Log each connection and requestConfig file
// sftp.config.ts
import { defineConfig } from 'ts-sftp'
export default defineConfig({
port: 2222,
root: './uploads',
users: {
deploy: { publicKeys: ['ssh-ed25519 AAAAC3Nz... deploy@example.com'] },
},
})What it does not do
- No shells, no
exec, no port forwarding — file transfer only, by design - No password-by-default: passwords work if you configure one, keys are the path everything else assumes
- No SFTP v4-v6 extensions; version 3 is what clients negotiate in practice
Testing
The suite includes end-to-end tests that drive the server with the system's own
OpenSSH sftp client, covering uploads, downloads, directory operations, and
the chroot boundary.
bun testLicense
MIT