Audit Trail
Every session and file-transfer event is recorded by the audit subsystem. Four audit sinks can run simultaneously — each captures the same events independently. Enable any combination to match your compliance and operational monitoring requirements.
Audit Sinks
All audit sinks are configured inside the <audit> element
of sftp-server.xml. Each sink has an enabled
attribute that can be toggled without removing the configuration block.
1. Log Sink
The log sink writes audit events to the SLF4J logger
com.ivitechnologies.stylussftpserver.audit at
INFO level. It is enabled by default and requires
no additional configuration.
<log enabled="true"/>
To route audit log entries to a separate file, add a dedicated Log4j 2
appender in log4j2.xml that targets the
com.ivitechnologies.stylussftpserver.audit logger:
<Logger name="com.ivitechnologies.stylussftpserver.audit"
level="INFO" additivity="false">
<AppenderRef ref="AuditFile"/>
</Logger>
RollingFileAppender with size- or time-based rotation
to prevent audit log files from growing indefinitely.
2. JDBC Sink
The JDBC sink writes events asynchronously to the sftp_audit
database table via a LinkedBlockingQueue with a configurable
capacity (default 10,000). Events are batched for throughput. The table is
auto-created on first start.
<jdbc enabled="true">
<driver-class-name>org.h2.Driver</driver-class-name>
<url>jdbc:h2:${STYLUS_SFTPSERVER_DATA}/db/sftpdb;AUTO_SERVER=TRUE</url>
<username>sa</username>
<password>secret</password>
<max-pool-size>5</max-pool-size>
<queue-capacity>10000</queue-capacity>
</jdbc>
| Element | Description | Default |
|---|---|---|
<driver-class-name> |
Fully qualified JDBC driver class. | org.h2.Driver |
<url> |
JDBC connection URL. Supports ${STYLUS_SFTPSERVER_DATA} macro. |
— |
<username> |
Database username. | sa |
<password> |
Database password. | — |
<max-pool-size> |
Maximum HikariCP connection pool size. | 5 |
<queue-capacity> |
Maximum number of events buffered before blocking. | 10000 |
3. Syslog Sink (RFC 5424 UDP)
The syslog sink sends fire-and-forget UDP packets to a central syslog collector such as Splunk, ELK, or QRadar. Overhead is near-zero (approximately 10 microseconds per event).
<syslog enabled="true">
<host>siem.example.com</host>
<port>514</port>
<facility>LOCAL0</facility>
</syslog>
| Element | Description | Default |
|---|---|---|
<host> |
Hostname or IP address of the syslog collector. | — |
<port> |
UDP port number. | 514 |
<facility> |
Syslog facility: LOCAL0 through LOCAL7. |
LOCAL0 |
Each message follows RFC 5424 format with structured data
in a [sss@0] SD element containing all audit record fields.
Severity mapping:
- INFO (6) — successful operations (authentication, upload, download)
- WARNING (4) — failures and denied operations
4. Webhook Sink
The webhook sink sends fire-and-forget HTTP POST requests via the JDK
HttpClient. You can configure multiple webhooks, each with
its own URL, event filter, and output format.
<webhooks>
<webhook enabled="true">
<url>https://hooks.slack.com/services/T00/B00/xxx</url>
<events>UPLOAD_COMPLETE, AUTH_FAILURE</events>
<format>text</format>
<template>SSS ${event}: ${filename} by ${username}</template>
</webhook>
</webhooks>
Formats
| Format | Content-Type | Description |
|---|---|---|
json |
application/json |
Structured JSON object with all event fields. Suitable for APIs and automation pipelines. |
text |
application/json |
Template-rendered message wrapped in {"text":"..."}. Suitable for Slack and Microsoft Teams incoming webhooks. |
Template Variables
| Variable | Description |
|---|---|
${event} |
Event type (e.g., UPLOAD_COMPLETE) |
${timestamp} |
ISO 8601 timestamp |
${username} |
Authenticated username |
${remoteIp} |
Client IP address |
${filename} |
File path (uploads and downloads) |
${bytes} |
File size in bytes |
${success} |
true or false |
${detail} |
Additional detail message |
HMAC Signing
To verify that webhook payloads originate from Stylus SFTP Server, configure
a shared secret. The server computes an HMAC-SHA256 signature over the
request body and sends it in the X-SSS-Signature HTTP header.
The receiving endpoint can recompute the HMAC and compare.
<webhook enabled="true">
<url>https://api.example.com/sftp-events</url>
<events>UPLOAD_COMPLETE, DOWNLOAD_COMPLETE</events>
<format>json</format>
<secret>my-shared-secret</secret>
</webhook>
Event Types
The table below lists the protocol-triggered event types — authentication, transfer, session, MFA, quota. Admin-triggered events (org / group / folder mutations, license administration, auth-orchestrator diagnostics) also flow through the audit stream but are catalogued exhaustively in Appendix B, which stays authoritative as new events are added.
| Event | Description | Severity |
|---|---|---|
AUTH_SUCCESS |
Successful authentication | INFO |
AUTH_FAILURE |
Failed authentication attempt | WARNING |
AUTH_ACCOUNT_LOCKED |
Account locked after consecutive failure threshold | WARNING |
SESSION_CONNECT |
Client connected | INFO |
SESSION_CLOSED |
Client disconnected | INFO |
UPLOAD_COMPLETE |
File upload committed (staging renamed to final) | INFO |
UPLOAD_ABORTED |
Upload cancelled by client | WARNING |
UPLOAD_REJECTED |
Upload denied (quota exceeded, read-only access, or error) | WARNING |
DOWNLOAD_COMPLETE |
File download completed | INFO |
LICENSE_REJECTED |
Invalid, expired, or missing activation key | WARNING |
MFA_SUCCESS |
TOTP verification succeeded | INFO |
MFA_FAILURE |
TOTP verification failed (wrong code) | WARNING |
MFA_ENROLLED |
User completed MFA enrollment | INFO |
MFA_DISABLED |
MFA disabled for a user (by admin or self-service) | INFO |
MFA_RECOVERY_USED |
A one-time recovery code was consumed during MFA verification | INFO |
USER_CREATED |
An SFTP end-user account was created by an administrator | INFO |
USER_UPDATED |
An SFTP end-user property changed (enable/disable, password,
home path, quota, email, read-only flag, public keys, unlock).
The detail column identifies which property changed. |
INFO |
USER_DELETED |
An SFTP end-user account was deleted by an administrator | INFO |
ADMIN_USER_CREATED |
An admin-console operator account was created | INFO |
ADMIN_USER_UPDATED |
An admin-console operator property changed (password, email, enable/disable) | INFO |
ADMIN_USER_DELETED |
An admin-console operator account was deleted | INFO |
ORG_CREATED, ORG_UPDATED,
ORG_DELETED |
An organization was created, edited, or removed.
The username column carries the org's name. |
INFO |
GROUP_CREATED, GROUP_UPDATED,
GROUP_DELETED |
A group was created, edited, or removed. The
username column carries the group's name;
detail names the parent organization. |
INFO |
FOLDER_CREATED, FOLDER_UPDATED,
FOLDER_DELETED |
A virtual folder was created, edited, or removed.
The filename column carries the logical
folder path. |
INFO |
MEMBERSHIP_ADDED, MEMBERSHIP_REMOVED |
A user was added to or removed from a group. The
username column identifies the affected
user; detail names the group and org. |
INFO |
Audit Record Fields
Every audit event carries the same set of fields, regardless of which sink receives it:
| Field | Type | Description |
|---|---|---|
| Event type | String | One of the event types listed above. |
| Timestamp | Timestamp | When the event occurred, stored as UTC. The Web and Swing admin surfaces render it in the operator's local timezone; JDBC / syslog / webhook sinks emit UTC. |
| Executed by | String | Admin username that triggered the mutation, or null for
protocol-triggered events (uploads, downloads, session events).
Populated by the Web filter, Swing frame, and CLI when they call
AdminEngine mutations. |
| Username | String | The authenticated username, or null for pre-auth events. |
| Remote IP | String | Client IP address (IPv4 or IPv6, up to 45 characters). |
| Filename | String | File path for upload/download events. |
| File size | Long | Size in bytes for upload/download events. |
| Success | Boolean | true for successful operations, false for failures. |
| Detail | String | Additional context (e.g., failure reason, quota detail). |
sftp_audit Table Schema
When the JDBC sink is enabled, the sftp_audit table is
auto-created on first start with the following schema:
CREATE TABLE sftp_audit (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
occurred_at TIMESTAMP NOT NULL,
event_type VARCHAR(32) NOT NULL,
username VARCHAR(255),
remote_ip VARCHAR(45),
filename VARCHAR(1000),
bytes BIGINT,
success SMALLINT NOT NULL,
detail VARCHAR(2000),
executed_by VARCHAR(255)
)
CREATE TABLE sftp_audit_to_organization (
audit_id BIGINT NOT NULL,
org_id BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_sftp_audit_to_organization PRIMARY KEY (audit_id, org_id),
CONSTRAINT fk_sftp_audit_to_organization_org
FOREIGN KEY (org_id) REFERENCES sftp_organizations (org_id) ON DELETE CASCADE
)
All timestamps are stored as UTC. The application supplies each
occurred_at value as a UTC instant when the event is recorded, and
the install-time service registration sets -Duser.timezone=UTC so
the JVM agrees; the admin surfaces convert to the operator's local timezone at
display time.
The sftp_audit_to_organization link table records which
organizations are relevant to each event. A single event can be tagged with
multiple organizations — a Force5-employed operator acting on Austin
Energy's folder produces an event linked to both orgs, so the Audit view can
surface it under either tenant's filter. Pre-identity-model rows have no
links and surface only in unfiltered queries.
| Column | Type | Description |
|---|---|---|
id |
BIGINT IDENTITY |
Auto-generated surrogate primary key. |
event_type |
VARCHAR(32) |
Event type code (e.g., AUTH_SUCCESS). |
occurred_at |
TIMESTAMP |
Event timestamp (UTC), supplied by the application when the event is recorded. |
username |
VARCHAR(255) |
Authenticated username. Nullable for pre-auth events. |
remote_ip |
VARCHAR(45) |
Client IP address (supports full IPv6 notation). |
filename |
VARCHAR(1000) |
File path for file-transfer events. |
bytes |
BIGINT |
File size in bytes. Nullable. |
success |
SMALLINT |
1 = success, 0 = failure. |
detail |
VARCHAR(2000) |
Free-text detail message. Nullable. |
occurred_at, event_type, and
username are created automatically, so audit-viewer queries stay
fast even on high-volume servers.