Under Construction: The site is currently being updated. That means its NOT FINISHED AND IS BEING BUILT!!!!!!
Docs / Daemon / SFTP System
Daemon

SFTP System

Native TypeScript SFTP server for container file access over SSH.

SFTP System

AirLink runs a pure TypeScript SFTP server as part of the daemon process. The previous architecture used an atmoz/sftp Docker sidecar; this has been fully replaced by a native SSH server built on the ssh2 library.

Architecture

Aspect Detail
Protocol SFTP over SSH-2
Library ssh2 (pure JS, no native deps)
Host key Ed25519, auto-generated
Deployment Daemon process, no container isolation
Replacement Replaced atmoz/sftp sidecar

The server handles SSH transport and the SFTP subsystem directly. No external processes, no Docker, no privilege escalation.

Components

sftpServer.ts

Manages the SSH server lifecycle:

  • Listens on a configurable port
  • Generates and loads an Ed25519 host key from storage/sftp_host_ed25519
  • Creates SSH server instances per connection
  • Handles session creation and teardown
  • Emits connection/disconnection events for activity tracking

sftpAuth.ts

Handles credentials and authentication:

  • Generates per-session credentials with format alsftp_<sha256_hex_16> as username
  • Produces a 24-byte random password per session
  • Validates incoming SSH auth against active session credentials
  • Tracks activity events (connect, disconnect, reads, writes, etc.)
  • Enforces one-session-per-server: new credentials revoke any previous active session
  • Runs periodic cleanup every hour to expire stale sessions

sftpSubsystem.ts

Full SFTP protocol handler:

  • Implements the SFTP packet framing layer
  • Maps SSH_FXP_* request types to filesystem operations
  • Enforces path jail and security checks before every operation
  • Returns proper SFTP status codes on success and failure

Supported Operations

Operation SSH_FXP Type Description
OPEN SSH_FXP_OPEN Open a file for read, write, or append
READ SSH_FXP_READ Read bytes from an open file handle
WRITE SSH_FXP_WRITE Write bytes to an open file handle
CLOSE SSH_FXP_CLOSE Close an open file or directory handle
OPENDIR SSH_FXP_OPENDIR Open a directory for listing
READDIR SSH_FXP_READDIR Read directory entries from an open dir handle
STAT SSH_FXP_STAT Get file attributes (follows symlinks)
LSTAT SSH_FXP_LSTAT Get file attributes (does not follow symlinks)
FSTAT SSH_FXP_FSTAT Get attributes for an open file handle
REMOVE SSH_FXP_REMOVE Delete a file
RMDIR SSH_FXP_RMDIR Remove an empty directory
MKDIR SSH_FXP_MKDIR Create a new directory
RENAME SSH_FXP_RENAME Rename or move a file/directory
REALPATH SSH_FXP_REALPATH Resolve a path to its canonical form
SETSTAT SSH_FXP_SETSTAT Set file attributes on a path
FSETSTAT SSH_FXP_FSETSTAT Set file attributes on an open handle

Session Management

Credential Format

  • Username: alsftp_<first_16_hex_chars_of_sha256>
  • Password: 24 random bytes, base64url encoded
  • Each credential set is tied to a single server ID

Lifecycle

Parameter Value
Session TTL 24 hours
Max active sessions 1 per server
Cleanup interval Every hour
Host key type Ed25519
Host key path storage/sftp_host_ed25519

When new credentials are generated for a server, any existing active session for that server is immediately revoked. The old credentials stop working on the next auth attempt.

The host key is generated once on first startup if it does not exist. It persists across daemon restarts so clients are not prompted about host key changes.

Activity Events

Events are tracked per server and buffered for consumption by the panel.

Event Description
connect SSH connection established
disconnect SSH connection closed
write File write operation
read File read operation
remove File or directory deleted
rename File or directory renamed
mkdir Directory created
readdir Directory listing read

Events are buffered up to a maximum of 500 per server. The panel consumes these via polling or subscription. Buffer overflow drops oldest events.

Security

Path Jail

Every filesystem operation goes through jailPath() which resolves the target path and confirms it falls within the session's designated root directory. Symlink traversal is checked via realpathSync() to prevent escape.

Secure File Open

Kernel Version Method Description
>= 5.6 openat2 FFI Uses RESOLVE_BENEATH and RESOLVE_NO_MAGICLINKS for atomic path resolution
< 5.6 O_NOFOLLOW fallback Opens file without following symlinks, relies on jailPath pre-check

The openat2 approach prevents TOCTOU (time-of-check-time-of-use) races by resolving and opening the file in a single kernel syscall. The fallback path uses O_NOFOLLOW to block symlink following but does not fully eliminate the race window.

Security Summary

  • Path jail enforced before every operation
  • Symlink escape prevented by realpath check
  • TOCTOU minimized via openat2 on modern kernels
  • One session per server limits blast radius
  • Credentials are non-reusable (tied to session, revoked on replacement)

API Endpoints

POST /sftp/credentials

Generate new SFTP credentials for a server. Revokes any existing active session for that server.

Response:

{
  "username": "alsftp_a1b2c3d4e5f67890",
  "password": "xK9m...",
  "host": "sftp.example.com",
  "port": 2222,
  "rootDir": "/data/servers/abc",
  "expiresAt": "2026-01-16T12:00:00Z"
}

DELETE /sftp/credentials

Revoke the active SFTP credentials for a server. The existing session is terminated and the credentials stop working immediately.

Response:

{
  "revoked": true
}

GET /sftp/status

Return active SFTP session status for all servers or a specific server.

Response:

{
  "sessions": [
    {
      "serverId": "abc",
      "username": "alsftp_a1b2c3d4e5f67890",
      "active": true,
      "connectedAt": "2026-01-15T12:00:00Z",
      "lastActivity": "2026-01-15T14:30:00Z",
      "expiresAt": "2026-01-16T12:00:00Z"
    }
  ]
}

GET /sftp/activity

Return buffered activity events for a server.

Query Parameters:

Parameter Description
serverId Server ID to fetch events for
limit Max events to return (default 100, max 500)

Response:

{
  "events": [
    {
      "type": "read",
      "path": "/data/files/log.txt",
      "bytes": 4096,
      "timestamp": "2026-01-15T14:30:00Z"
    }
  ]
}