A stable remote snapshot completes the shared validation contract

Automatically Test SFTP Interfaces: Safely Validate Delivered Files

An SFTP file is not automatically a finished data set. It may still be growing, the server identity may not be trusted, and a successful transfer says nothing about the file’s contents. Iteration 4 of ZEUS Interface Quality turns that handoff into a reproducible validation step.

The new adapter verifies the host key and key-based access, waits for a stable remote snapshot, downloads exactly one file to a temporary location, optionally checks its SHA-256 digest, and delegates content validation to the existing JSON, CSV, or fixed-width adapters. The shared contract then distinguishes VALID, INVALID, and an operational failure.

The broader idea is introduced in the main article on automated interface quality. Iteration 1 establishes JSON Schema as a local contract; Iteration 2 adds CSV and fixed-width files; Iteration 3 connects HTTP status, headers, and JSON responses. This iteration adds a secured file-transport boundary.

AI-generated illustration: a protected SFTP file is assessed at a central validation station for stability, SHA-256 integrity, and data-contract compliance.

AI-generated illustration for tiny-tool.de: a central validation station checks a protected SFTP file for a stable snapshot, integrity, and content-contract compliance.

What Iteration 4 delivers

This is a vertical slice for one expected SFTP file. It is not a directory watcher and not a complete file-transfer management system.

  • Trust: an explicit known_hosts file and a local private key are required.
  • Stability: the remote object type, size, and modification time are observed repeatedly.
  • Integrity: an optional SHA-256 digest is compared with the bytes actually downloaded.
  • Content: JSON, CSV, and fixed-width files reuse the existing tested validators.
  • Failure handling: invalid deliveries can be moved to a remote quarantine directory when enabled.
  • Automation: exit 0 means valid, exit 1 means a contract violation, and exit 2 means the check could not be completed reliably.

The SFTP profile as a local contract

The profile describes the endpoint and the rules for this run. It is local and selected with --schema or --profile, just like the earlier adapters. The local files for host-key verification, the private key, and the content profile must remain inside the profile directory.

sftp-profile.json · version 1
{
  "format": "sftp",
  "version": 1,
  "host": "sftp.example.org",
  "port": 22,
  "username": "quality-check",
  "remotePath": "/inbox/delivery.json",
  "knownHosts": "known_hosts",
  "privateKey": "id_ed25519",
  "privateKeyPassphraseEnv": "SFTP_KEY_PASSPHRASE",
  "contentFormat": "json",
  "contentProfile": "delivery.schema.json",
  "quarantineRemoteDirectory": "/quarantine",
  "stabilityChecks": 2,
  "stabilityDelaySeconds": 1,
  "maxAttempts": 2,
  "retryDelaySeconds": 1,
  "connectTimeoutSeconds": 10
}

remotePath is an absolute POSIX path to exactly one file. Dots, traversal segments, backslashes, and trailing slashes are rejected. That does not prove the file is semantically correct, but it keeps a small profile from silently being interpreted as a different path.

Host keys and private keys, with no silent exceptions

The transport uses JSch with strict known_hosts verification. The server is not accepted through an arbitrary new host-key prompt. This first SFTP version deliberately supports private-key authentication only. A passphrase may come from an environment variable; it is never written to the report or findings.

🔐 Trust boundary

An SFTP login is not a quality check. CI should provide the host key and private key through its protected environment, while the profile remains a versionable, secret-free set of rules. That is why the implementation supports neither clear-text passwords nor arbitrary paths outside the profile directory.

Why size and modification time matter

Many delivery processes write directly to the final filename. A fast download can then capture a valid-looking but incomplete snapshot. ZEUS therefore reads remote metadata before downloading and compares the object type, byte size, and modification time across multiple observations.

If the file does not settle, the adapter waits only as long as the profile allows. After the bounded attempts, the result is an operational error with exit code 2: the check could not establish a trustworthy snapshot.

Bounded stability and retry
"stabilityChecks": 2,
"stabilityDelaySeconds": 1,
"maxAttempts": 2,
"retryDelaySeconds": 1

The limits are intentional: a validation run should not wait forever or quietly turn into a polling service. A more complex intake process deserves its own orchestrator, state model, and selection rule.

SHA-256 and quarantine

After the stable snapshot is confirmed, the adapter writes the bytes to a temporary local directory. If the profile contains sha256, the digest is calculated over that downloaded file. A mismatch is reported at /remote/hash; content validation is not presented as a meaningful substitute for a failed integrity check.

Hash finding · exit code 1
{
  "status" : "INVALID",
  "valid" : false,
  "findings" : [ {
    "instancePath" : "/remote/hash",
    "schemaPath" : "/sha256",
    "keyword" : "sha256",
    "message" : "Downloaded SFTP file does not match the expected SHA-256 hash"
  } ]
}

An invalid content or hash result can optionally move the remote file into a quarantine directory. The target receives a UTC timestamp and an .invalid suffix. Valid files are not moved, and a connection or stability failure never triggers quarantine.

Reusing JSON, CSV, and fixed-width validation

The SFTP adapter does not create a second content-validation language. contentFormat selects an existing validator and contentProfile points to its local profile. This keeps transport responsibility separate from the data contract itself.

SFTP content-validation delegation
Format Local contract Report path
json JSON Schema Draft 2020-12 /content/...
csv CSV profile with header and column rules /content/...
fixed-width Position and record-length profile /content/...

A missing or mistyped JSON field therefore appears as, for example, /content/quantity. That makes it machine-readable and keeps it separate from a hash or transport failure.

The CI run without a local input file

For local file and HTTP checks, --input can be an important part of the invocation. SFTP is different: the profile identifies the remote file. The SFTP run therefore does not pretend that a second local input file is required.

🟢 The first guaranteed success

If you are following the article without an SFTP account, start with the isolated adapter test. It needs only JDK 21 and the Maven Wrapper included in the repository:

PowerShell · offline evidence
mvnw.cmd -B -Dtest=SftpValidatorTest test

This run contacts no SFTP server and ends with six successful SFTP tests. The production run against a real endpoint remains clearly marked as environment-specific below.

PowerShell · production transport against a test server
mvnw.cmd -B -Dtest=SftpValidatorIntegrationTest test

This second run starts an embedded test server and exercises the production JSch transport with host-key and private-key authentication. It needs neither Docker nor an external SFTP account.

PowerShell · validate an SFTP profile
$jar = "target/zeus-interface-quality-0.1.0-SNAPSHOT.jar"
$profile = "docs/examples/sftp-profile.json"

java -jar $jar validate --input-format sftp --schema $profile --report json
$LASTEXITCODE

Important: docs/examples/sftp-profile.json is a configuration template. For a real run, the host, username, remote path, known_hosts, private key, and content profile must match your environment. sftp.example.org is intentionally a placeholder. The automated tests do not contact that example system; they replace the transport through a controlled test port.

Results and exit codes

SFTP adapter result semantics
Case Example Outcome
VALID stable file, digest, and content contract all pass Report, exit 0
INVALID digest or JSON, CSV, or fixed-width contract fails Report, exit 1
Not runnable profile, key, connection, download, or stability failure No content report, exit 2

This distinction matters in CI. A bad delivery can stop downstream processing in a controlled way and be quarantined. An unreachable server remains an operational problem; it must not be mistaken for a bad data record.

The automated evidence

The unit tests use a small transport port and a deterministic fake. In addition, the integration test starts an embedded Apache MINA SSHD SFTP server and runs the production JSch transport against it. That exercises host-key verification, private-key login, download, and quarantine without a public SFTP server or external credentials.

🟢 Reproducible verification

In the repository, run mvnw.cmd -B -Dtest=SftpValidatorTest test to focus on the new path. The full suite now contains 76 tests after Iteration 4; two existing Windows symlink cases remain skipped when the local permission is unavailable. mvnw.cmd -B package then creates the executable JAR.

This is strong evidence for the profile and adapter logic, not an availability promise for a production endpoint. A live-server check belongs in a protected, environment-specific pipeline with managed credentials and host keys.

What is intentionally not included

  • no password authentication, OpenSSH agent, or automatic OpenSSH configuration;
  • no directory polling, globbing, or selection among multiple simultaneous deliveries;
  • no automatic archive for valid deliveries and no multi-file correlation;
  • no business rules spanning several files or transport runs;
  • no live SFTP server in the standard test suite and no production-availability claim.

These boundaries keep the iteration manageable. The adapter first proves a clear and secure one-file flow. Directory watching, delivery windows, multi-file correlation, and operational orchestration can be added as separate contracts later.

Conclusion: transport becomes part of the contract

Iteration 4 does more than add an SFTP download command. It defines which host is trusted, when a file is stable, how its integrity is checked, and which content validator owns the next step.

The evidence remains reproducible: a local profile, bounded retries, and a shared report turn an often implicit handoff into a clear CI building block. Invalid deliveries can be quarantined, while technical outages remain technical outages.

The next decisions are ready for their own iterations: directory polling, multi-file correlation, additional authentication models, or a complete operational workflow. The main article keeps the overall series in view.

Code and sources

Technical and editorial status: September 25, 2026. The examples refer to the local Iteration 4 state of the repository.

Transparency Notice


Transparency Notice:
Content published on tiny-tool.de is carefully researched, editorially reviewed, and updated regularly. Sources and quotations are provided in a transparent and traceable manner wherever possible. However, we do not guarantee the accuracy, completeness, or timeliness of the information provided. Errors cannot be ruled out.

Editorial Responsibility and Use of AI: Digital tools – including AI-based assistance systems – may be used to support research, structuring, language refinement, translation, code analysis, or visual production. Published content is editorially reviewed, revised, and approved by Guido Zeuner. Content selection, contextualisation, and publication remain under human responsibility. AI-generated output is not treated as an independent source. AI systems are not responsible authors or editors. More information about our use of AI in texts, images, videos, and digital personas is available on our AI Transparency page, which is currently available in German.

Audience Measurement (VG WORT / METIS): VG WORT tracking markers may be used to measure the reach of individual texts. As part of the METIS audience measurement process, a client ID may be generated and a so-called “METIS Session Cookie” may be set. The measurement is used to determine statistical access figures and as a basis for possible VG WORT remuneration. According to VG WORT, no personal usage profiles are created, and the measurement is not used for advertising or marketing tracking. More information is available in our Privacy Notice, which is currently available in German.

Please note: The content provided on this website is for general informational purposes only and does not constitute professional advice, including legal, tax, medical, technical, financial, or other specialist advice. Use of the content is at your own responsibility.

Advertising and Affiliate Links: Some posts may contain promotional references or affiliate links. These are identified accordingly. Clicking such links does not result in any additional cost to you; we may receive a small commission.

Trademark Notice: All brand names, logos, and product names are the property of their respective rights holders and are used solely for identification and descriptive purposes. No affiliation with the companies mentioned is implied unless explicitly stated.

External Links: This website contains links to external third-party websites. Although links are reviewed carefully, we assume no responsibility for the content of external websites. If we become aware of unlawful content, the respective links will be reviewed and removed where appropriate.