Security
Stylus SFTP Server provides multiple layers of security: cryptographic algorithm configuration, host key management, IP-based access control, geographic blocking, connection rate limiting, account lockout, multi-factor authentication (MFA), the AI & MCP token surface, path traversal prevention, and license enforcement. This chapter covers each mechanism and its configuration.
SSH Algorithm Configuration
Ciphers, MACs, and key-exchange algorithms are configurable via allow-lists in the
<security> block of sftp-server.xml. The default
configuration ships with a modern-only set — no CBC ciphers and no SHA-1 MACs.
The server also supports post-quantum key exchange via
mlkem768x25519-sha256.
If an allow-list is left empty, built-in defaults apply. These include legacy algorithms and are not recommended for production.
Unknown or unsupported algorithm names are logged at WARN level and
silently skipped — the server continues to start with the remaining valid
algorithms.
Default Ciphers
| Cipher | Notes |
|---|---|
chacha20-poly1305@openssh.com |
AEAD stream cipher; preferred |
aes256-gcm@openssh.com |
AEAD block cipher |
aes128-gcm@openssh.com |
AEAD block cipher |
aes256-ctr |
Counter mode; widely supported |
aes192-ctr |
Counter mode |
aes128-ctr |
Counter mode |
Default MACs
| MAC | Notes |
|---|---|
hmac-sha2-256-etm@openssh.com |
Encrypt-then-MAC; preferred |
hmac-sha2-512-etm@openssh.com |
Encrypt-then-MAC |
hmac-sha2-256 |
Standard HMAC |
hmac-sha2-512 |
Standard HMAC |
Default Key Exchange Algorithms
| Algorithm | Notes |
|---|---|
mlkem768x25519-sha256 |
Post-quantum hybrid key exchange |
curve25519-sha256 |
Modern elliptic-curve DH |
ecdh-sha2-nistp256 |
NIST P-256 curve |
ecdh-sha2-nistp384 |
NIST P-384 curve |
ecdh-sha2-nistp521 |
NIST P-521 curve |
diffie-hellman-group14-sha256 |
DH group 14 (2048-bit) |
diffie-hellman-group16-sha512 |
DH group 16 (4096-bit) |
diffie-hellman-group18-sha512 |
DH group 18 (8192-bit) |
Legacy Algorithms (Disabled by Default)
The following algorithms are disabled by default due to known weaknesses. They can
be re-enabled by adding them to the appropriate allow-list in
sftp-server.xml if required for backward compatibility with older
clients.
| Algorithm | Type | Vulnerability |
|---|---|---|
aes128-cbc |
Cipher | CBC padding-oracle vulnerability |
aes192-cbc |
Cipher | CBC padding-oracle vulnerability |
aes256-cbc |
Cipher | CBC padding-oracle vulnerability |
hmac-sha1 |
MAC | SHA-1 weakness |
hmac-sha1-etm@openssh.com |
MAC | SHA-1 weakness |
Host Keys
On first startup, Stylus SFTP Server automatically generates SSH host key pairs for all three supported key types:
- RSA (3072-bit or higher)
- ECDSA (P-256)
- Ed25519
Host key files are stored in the conf/ directory by default. The
paths are configurable in sftp-server.xml.
IP Block List
Static IP addresses and CIDR ranges can be blocked via the
<block-list> element in sftp-server.xml. Both IPv4
and IPv6 are supported. Blocked clients are disconnected immediately
before the SSH handshake begins, minimizing resource consumption.
<block-list>
<entry>10.0.0.0/8</entry>
<entry>192.168.1.100</entry>
<entry>2001:db8::/32</entry>
</block-list>
Connection Rate Limiting
The connection rate limiter tracks connections per source IP using a sliding window. When a client exceeds the allowed threshold, its IP is automatically blacklisted for a configurable duration.
| Setting | Description | Default |
|---|---|---|
max-connections |
Maximum connections allowed within the interval. Set to 0 to disable rate limiting. |
— |
interval-seconds |
Length of the sliding window in seconds. | 60 |
blacklist-duration-minutes |
Duration (in minutes) for which an offending IP is auto-blacklisted. | 30 |
Auto-blacklisted IPs are treated identically to entries in the static block list — connections are refused before the SSH handshake.
max-connections to 0 disables rate limiting
entirely. The static block list remains active regardless of this setting.
GeoIP Country Blocking
Stylus SFTP Server can restrict connections by country of origin using a MaxMind
GeoIP database. The server supports both .mmdb (binary) and
.csv formats.
Setup
- Obtain a GeoIP database file from MaxMind (GeoLite2 Country or GeoIP2 Country).
- Place the
.mmdbor.csvfile in thegeodb/folder under the data directory. - Configure the blocking mode and country list in
sftp-server.xml.
Configuration
<geo-blocking>
<mode>allow</mode>
<countries>US, CA, GB</countries>
</geo-blocking>
| Setting | Description |
|---|---|
mode |
allow (whitelist — only listed countries permitted) or deny (blacklist — listed countries blocked) |
countries |
Comma-separated ISO 3166-1 alpha-2 country codes (e.g., US, CA, GB, DE) |
CSV Format
If you do not have a MaxMind .mmdb file, you can provide a plain CSV
file with three columns: ip_from, ip_to, and
country_code.
ip_from,ip_to,country_code
"16777216","16777471","AU"
"16777472","16778239","CN"
"16778240","16779263","AU"
| Column | Description |
|---|---|
ip_from |
Start of IP range. Numeric (e.g. 16777216) or dotted-decimal (e.g. 1.0.0.0). |
ip_to |
End of IP range (inclusive). Same format as ip_from. |
country_code |
ISO 3166-1 alpha-2 country code (e.g. US, GB, DE). Must be exactly 2 characters. |
Format rules:
- First row is treated as a header and skipped if it contains the word "country".
- Lines starting with
#are treated as comments and ignored. - Empty lines are ignored.
- Values may be optionally quoted with double quotes.
- Rows with fewer than 3 columns or invalid IP values are silently skipped.
ip_from, ip_to, country_code
columns (reorder or rename if needed).
How It Works
- On startup, the GeoIP database file is imported into the
sftp_geo_ipdatabase table. - IP lookups use an in-memory binary search for O(log n) performance.
- A
WatchServicemonitors thegeodb/folder — dropping a new database file triggers automatic re-import with no restart required.
geodb/ folder. The server detects the change and
re-imports automatically.
Account Lockout
To protect against brute-force password attacks, Stylus SFTP Server tracks consecutive authentication failures per account. After the configured threshold is exceeded, the account is locked for a specified duration.
<password-policy> block is present. Set
lockout-threshold to 0 to disable it.
<password-policy>
<lockout-threshold>5</lockout-threshold>
<lockout-duration-minutes>30</lockout-duration-minutes>
</password-policy>
| Setting | Description | Default |
|---|---|---|
lockout-threshold |
Number of consecutive failed authentication attempts before lockout. | 5 |
lockout-duration-minutes |
Duration (in minutes) the account remains locked. | 30 |
Lockout state is tracked in the sftp_lockout database table, so it
survives server restarts. A successful authentication resets the failure counter.
Administrator Unlock
An administrator can unlock a locked account immediately using the CLI tool:
admin unlock-user <username>
Multi-Factor Authentication (MFA)
TOTP-based multi-factor authentication adds a second verification step to
password authentication. Configure MFA in the <mfa> block
of sftp-server.xml:
MFA per protocol
The MFA policy is enforced on every protocol that supports interactive login, but the challenge-response shape differs by protocol:
| Protocol | Behavior when MFA is required |
|---|---|
| Web File Transfer Portal | Two-step form — username + password on the login card, then a code prompt after a successful password. TOTP or a recovery code accepted. Self-service enrollment available from the Portal header. |
| Web Admin Console | Same two-step flow. Governed by
<admin-required> below — even when the
SFTP <policy> is optional, admin
MFA can be enforced independently. |
| SFTP | Uses the SSH keyboard-interactive method. After a valid password the server issues a second prompt for the TOTP code. Public-key logins bypass MFA entirely (the key is already a second factor). |
| FTP / FTPS | The FTP command sequence has no native second-factor prompt.
When MFA is required for the user, the login command is rejected
with a message directing the operator to log in via the Portal
first. Automated FTPS clients should use a dedicated
service-account with MFA excluded (typically via a group whose
mfa_required flag is off). |
| WebDAV | HTTP Basic Auth carries only a single credential, so WebDAV clients cannot answer a TOTP prompt. WebDAV honors MFA the same way FTP does — the connection is refused when the user's effective policy requires MFA. Use a per-account read-only service credential for automated WebDAV mounts. |
<mfa>
<policy>optional</policy>
<issuer>Stylus SFTP Server</issuer>
<window-size>1</window-size>
<recovery-codes>8</recovery-codes>
<admin-required>false</admin-required>
</mfa>
| Element | Description | Default |
|---|---|---|
<policy> |
MFA enforcement level: disabled, optional,
or required. |
disabled |
<issuer> |
Display name shown in authenticator apps (e.g., Google Authenticator) alongside the account label. | Stylus SFTP Server |
<window-size> |
Number of 30-second time steps to accept before and after the
current step. A value of 1 means codes valid from
−30s to +30s (a 90-second window total). |
1 |
<recovery-codes> |
Number of one-time recovery codes generated per enrollment. | 8 |
<admin-required> |
Whether web admin console users must also enroll in MFA. | false |
Enrollment and authenticator labels
SFTP users self-enroll from the File Transfer Portal header.
Admin-console users enroll from the Admin Users
tab of the Web Admin console: select the account and choose the enroll (shield)
action to display the QR code and one-time recovery codes, then confirm with a
code from the authenticator app to activate. The same operations are available
from the CLI — mfa-admin-enroll,
mfa-admin-disable, and mfa-admin-status for admin
accounts, and mfa-enroll / mfa-reset /
mfa-disable / mfa-status for SFTP users.
Stylus SFTP Server (sftp.example.com). If you administer
more than one Stylus SFTP Server instance, each enrollment therefore appears under
a distinct entry instead of colliding as identical
Stylus SFTP Server / admin rows. The host portion
is derived from the address you used to reach the console, so enroll from the
hostname you normally use for that server.
Database Tables
MFA state is stored in four auto-created database tables:
| Table | Purpose |
|---|---|
sftp_user_mfa |
TOTP secret and enrollment status for SFTP users. |
sftp_mfa_recovery |
BCrypt-hashed recovery codes for SFTP users. |
sftp_admin_mfa |
TOTP secret and enrollment status for admin users. |
sftp_admin_mfa_recovery |
BCrypt-hashed recovery codes for admin users. |
Audit Events
All MFA operations generate audit events that are recorded by every enabled audit sink. See the Audit Trail chapter for the full event list.
optional policy to allow users to enroll at their
own pace. Switch to required after all users have enrolled.
Use the mfa-status admin command to check enrollment progress.
AI & MCP Token Surface
When AI Integration is enabled, the Knowledge Base exposes a Model Context Protocol (MCP) endpoint that AI assistants authenticate to with bearer tokens, entirely separate from the SFTP/FTPS/Portal login providers. This section summarizes the security properties of that surface; the operational walkthrough lives in Knowledge Base → Connecting AI Assistants.
- Disabled by default. The endpoint returns
503 Service Unavailableuntil an administrator turns AI Integration on. No token, however valid, reaches the Knowledge Base while the master switch is off. - Two token kinds. A personal access token (PAT),
prefixed
ssskb_, is minted by a signed-in user and shown once; or an OAuth 2.1 browser sign-in issues short-lived access tokens with rotating refresh tokens. OAuth 2.1 with mandatory PKCE is the default for the downloadable client configurations. - Hashed at rest. Tokens are stored as SHA-256 hashes, never in clear text. A leaked database row cannot be replayed as a token.
- Scoped. A token carries either
readorread,writescope; a read-only token can never author or publish content. - Bound to the account and revocable. A token acts as the user who created it and inherits that user's Knowledge Base role. Disabling the account, or revoking the token from the AI Integration tab, cuts off access immediately. Refresh-token reuse revokes the entire token family.
- Attributed. Every page an assistant creates or edits is committed with a “generated by AI agent” note in its version history.
Path Traversal Prevention
All filesystem operations enforce strict path confinement. After resolving a requested path (including symbolic links), the server verifies that the resolved path starts with the user's home directory:
resolved.startsWith(home)
Any attempt to escape the home directory — whether via ../
sequences, symbolic links, or absolute paths — is denied.
Additionally, the UsernameValidator rejects usernames containing
.. sequences, preventing directory traversal attacks during home
directory creation.
The Web File Transfer Portal applies the same path confinement checks, ensuring consistent security across both SFTP and browser-based access.
Activation Key
Stylus SFTP Server requires a valid activation key to operate. The key is cryptographically signed and verified at startup and periodically at runtime.
| Property | Value |
|---|---|
| Signature algorithm | ECDSA P-256 (SHA256withECDSA) |
| Maximum validity | 366 days |
| Hot-reload interval | Hourly — no restart needed for key renewal |
| Expiry warning | Logged 30 days before expiration |
| Audit event | LICENSE_REJECTED on key missing, expired, tampered, or corrupt |
Place the activation.key file in the root of the data directory
(${STYLUS_SFTPSERVER_DATA}/activation.key).
LICENSE_REJECTED audit event is emitted before shutdown. The hourly
runtime enforcer also checks key validity — if the key expires or the file
is removed while the server is running, it will shut down after emitting the
audit event.
Database Encryption at Rest
The internal H2 database is encrypted with AES (Advanced Encryption Standard) from the moment it is created. The database file on disk contains no readable data — table names, usernames, password hashes, audit records, and all other content are stored as AES-encrypted ciphertext.
How It Works
H2 supports transparent file-level encryption via the CIPHER=AES
parameter in the JDBC connection URL. When enabled, H2 encrypts every page
written to the .mv.db file using a file encryption
password that is separate from the SQL authentication password.
| Password | Purpose | Stored In |
|---|---|---|
| File encryption password | Encrypts the database file on disk (AES). Required to open
the .mv.db file at all. |
credentials.p12 as db.cipher-password |
| SQL user password | Authenticates SQL connections (standard H2 authentication). | credentials.p12 as db.password |
Both passwords are randomly generated during installation (24 and 32 alphanumeric characters respectively) and stored in the PKCS12 credential store. They are never displayed, logged, or written to configuration files.
What This Protects Against
- Stolen database files — if an attacker copies the
.mv.dbfile from the server, they cannot read its contents without the file encryption password. The file header showsH2encryptbut no data is readable. - Disk forensics — deleted data within the database file is also encrypted. No cleartext fragments remain on disk.
- Backup exposure — database backups are encrypted
at rest. An intercepted backup is useless without access to
credentials.p12.
JDBC URL
The installer automatically adds ;CIPHER=AES to the JDBC URL
in sftp-server.xml and admin-console.xml:
jdbc:h2:${STYLUS_SFTPSERVER_DATA}/db/sftpdb;AUTO_SERVER=TRUE;CIPHER=AES
At connection time, the server reads both passwords from the credential
store and composes them in the format H2 expects:
filePassword userPassword (space-separated). This is handled
automatically — no manual configuration is needed.
credentials.p12 or master.key is lost, the
encrypted database cannot be recovered. Ensure these files
are included in your backup strategy. See
Credential Store for details.
Credential Store
Stylus SFTP Server stores sensitive credentials (database passwords, LDAP bind passwords, SMTP passwords) in a PKCS12 keystore file rather than in plain text inside XML configuration files.
Files
| File | Purpose |
|---|---|
conf/credentials.p12 |
PKCS12 keystore containing encrypted credentials (DB username, DB password, LDAP bind password, SMTP password, keystore password). |
conf/master.key |
Password that unlocks credentials.p12. Can also contain
a cloud vault URI instead of the
actual password. |
Both files are created automatically during installation. The installer generates
random passwords and sets the database password via ALTER USER. On
upgrade, existing plain-text passwords are migrated from the XML files into the
keystore.
credentials.p12 does not exist, the server falls back to
reading passwords from the XML configuration files as before. No manual migration
is required.
Well-Known Credential Keys
| Key | Description |
|---|---|
db.username | Database username |
db.password | Database password |
ldap.bind-password | LDAP bind password |
smtp.password | SMTP authentication password |
keystore.password | FTP/TLS keystore password |
Windows Group ACL
On Windows, the installer can protect master.key and
credentials.p12 with NTFS access control lists (ACLs) tied to a
Windows security group. This allows multiple accounts (the service account, the
administrator, etc.) to access the credentials without requiring individual
per-file ACL entries.
Installer Options
| Option | Description |
|---|---|
| Use an existing group | Select a local or Active Directory group from the tree. The
installer applies ACLs using this group and adds the current user
and the service account (NT AUTHORITY\LOCAL SERVICE)
as members. |
| Create a new group | Creates a local Windows group with the specified name, adds the current user and service account, and applies ACLs. |
| Skip | No group-based ACL. Credentials are protected by the default NTFS inheritance from the data directory. |
ACL Applied
When a group is selected or created, the installer runs:
icacls master.key /inheritance:r
icacls master.key /grant "GroupName:(R)"
icacls master.key /grant "BUILTIN\Administrators:(F)"
This removes inherited permissions, grants read access to the group, and grants
full control to local administrators. The same ACL is applied to
credentials.p12.
net localgroup GroupName AccountName /add or
/delete. No file ACL changes are needed.
Cloud Vault Integration
Instead of storing the master key password on disk, it can be stored in a
cloud secret management service. When vault integration is enabled, the
master.key file contains a vault URI instead of the actual
password. At startup, the server resolves the URI to retrieve the password.
How It Works
starts
master.key
vault: ?
provider API
from vault
as password
credentials.p12
Figure: Credential resolution flow at server startup.
The master.key file is read once. If it contains a vault: URI,
the password is fetched from the cloud provider using the VM's managed identity.
Otherwise, the file content is used directly as the keystore password.
Supported Providers
| Provider | URI Format | Authentication |
|---|---|---|
| Azure Key Vault | vault:azure:https://myvault.vault.azure.net/secrets/name |
Managed identity via Azure IMDS |
| AWS Secrets Manager | vault:aws:us-east-1:secret-name |
EC2 instance role via IMDSv2 + SigV4 |
| Google Cloud Secret Manager | vault:gcp:project-id/secret-name |
Compute Engine service account via metadata server |
Setup
- During installation, the installer generates a random master key password and displays it on the Cloud Vault wizard page.
- Copy the password and create a secret in your vault provider with this value.
- Enter the vault URI in the installer and click Verify to confirm the round-trip.
- The installer writes the vault URI into
master.keyinstead of the plain password.
Manual Configuration
To enable vault integration on an existing installation, replace the contents
of conf/master.key with the vault URI:
vault:azure:https://myvault.vault.azure.net/secrets/sss-master-key
The server detects the vault: prefix at startup and resolves the
secret automatically.
GCP Version Pinning
For Google Cloud, append a version number to access a specific secret version
instead of latest:
vault:gcp:my-project-123/sss-master-key/3