Skip to content

Improve FTP/FTPS/SFTP configuration and access controls - #232

Merged
erikraft merged 64 commits into
masterfrom
feat/ftp-sftp-configuration-improvements
Oct 16, 2026
Merged

erikraft merged 64 commits into
masterfrom
feat/ftp-sftp-configuration-improvements

Conversation

@erikraft

@erikraft erikraft commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Change FTP/SFTP default credentials to android, keep existing saved preferences authoritative, and show a QR code for the connection URI.
  • Add FTPS mode selection: Explicit FTPS is the default; Implicit FTPS is selectable and applied to both control and data listeners.
  • Add a read-only setting enforced by FTP user authorities and by an Apache MINA SSHD filesystem accessor for SFTP file writes, deletes, renames, copies, directory creation, and metadata mutations.
  • Display Wi-Fi connection state and local IP address in FTP and SFTP settings.
  • Validate that the selected document tree is a directory with read/write access and that the resulting direct path is usable by the current server implementation.
  • Add translated FTPS/read-only/network labels to all 39 localized resource directories currently present in the repository (plus the default English resources).
  • Reuse existing Apache FtpServer, Apache MINA SSHD, Android Storage Access Framework, and ZXing dependencies; no new dependencies added.

About screen — ErikrafT Wi-Fi™

  • Add a BETA chip beside the app name on the About screen.
  • Add an ErikrafT Wi-Fi™ project section with the official logo artwork adapted to the active light/dark theme, a localized description, and a button linking to https://github.com/erikraft/WiFi.
  • Add localized Wi-Fi section strings across all 39 localized resource directories.
  • Keep the Wi-Fi project link external; this does not introduce a new in-app Wi-Fi networking implementation.

Internal storage / SD card — still blocking

The picker currently uses the Storage Access Framework (SAF), but the FTP and SFTP backends still require conventional filesystem paths. Converting a tree URI to a guessed /storage/... path is not a valid access strategy under scoped storage.

To complete this requirement, the server implementations need URI-backed filesystem adapters:

  • FTP/FTPS: implement Apache FtpServer's FileSystemFactory / FileSystemView / FtpFile contract using Android document URIs and ContentResolver streams.
  • SFTP: implement the compatible filesystem/path layer required by Apache MINA SSHD; an accessor that only blocks writes is not enough to make arbitrary SAF providers accessible.
  • Preserve the selected tree URI and persisted read/write grants; do not treat a display path returned by a helper as proof that the service can access the tree.
  • Apply read-only restrictions at the server boundary and reject unsupported provider operations rather than silently falling back to a different directory.

This is intentionally not marked as complete yet. Internal storage and removable SD-card trees must both be verified with actual uploads, downloads, directory listing, rename/delete where allowed, and read-only rejection.

CI correction

The previous CI run (38957085550) failed to compile because Apache FtpServer 1.2.1 does not provide org.apache.ftpserver.usermanager.impl.ReadPermission. The latest commit removes that invalid import and leaves the authority list without WritePermission when read-only mode is enabled. The new commit still needs a fresh CI result.

Validation

  • Reviewed the documented extension points for Apache FtpServer and Apache MINA SSHD.
  • Added localized XML resources for the newly introduced FTPS mode, read-only, Wi-Fi state, IP-address labels, and ErikrafT Wi-Fi About section.
  • No local Gradle build, lint, device test, or real FTP/FTPS/SFTP transfer test was run in this environment. Keep this PR in Draft until CI and manual device verification confirm compilation and runtime behavior.
  • No FTP/FTPS/SFTP integration tests were added to CI; manual testing on device remains expected.

Proposed Android version

  • Bump the proposed Android release from v10.1.9 (versionCode 32) to v10.1.10 (versionCode 33).
  • Update ReleaseContractTest to enforce the new version name/code pair.
  • This is a proposed next version only; no Git tag, GitHub Release, APK/AAB, or store release was created.
  • CI must validate the version contract and compile before the PR can be considered ready.

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c1ef0931-8043-4ef1-b742-4e4c87ba26c4
📥 Commits

Reviewing files that changed from the base of the PR and between 5d23659 and 12049f8.

📒 Files selected for processing: 88
  • app/build.gradle
  • app/src/main/java/com/erikraft/drop/AboutActivity.java
  • app/src/main/java/com/erikraft/drop/FtpServerService.java
  • app/src/main/java/com/erikraft/drop/FtpSettingsActivity.java
  • app/src/main/java/com/erikraft/drop/SftpServerService.java
  • app/src/main/java/com/erikraft/drop/SftpSettingsActivity.java
  • app/src/main/res/drawable/ic_erikraft_wifi.xml
  • app/src/main/res/values-af-rZA/ftp_sftp.xml
  • app/src/main/res/values-af-rZA/wifi_about.xml
  • app/src/main/res/values-ar-rSA/ftp_sftp.xml
  • app/src/main/res/values-ar-rSA/wifi_about.xml
  • app/src/main/res/values-bg-rBG/ftp_sftp.xml
  • app/src/main/res/values-bg-rBG/wifi_about.xml
  • app/src/main/res/values-ca-rES/ftp_sftp.xml
  • app/src/main/res/values-ca-rES/wifi_about.xml
  • app/src/main/res/values-cs-rCZ/ftp_sftp.xml
  • app/src/main/res/values-cs-rCZ/wifi_about.xml
  • app/src/main/res/values-da-rDK/ftp_sftp.xml
  • app/src/main/res/values-da-rDK/wifi_about.xml
  • app/src/main/res/values-de-rDE/ftp_sftp.xml
  • app/src/main/res/values-de-rDE/wifi_about.xml
  • app/src/main/res/values-el-rGR/ftp_sftp.xml
  • app/src/main/res/values-el-rGR/wifi_about.xml
  • app/src/main/res/values-es-rES/ftp_sftp.xml
  • app/src/main/res/values-es-rES/wifi_about.xml
  • app/src/main/res/values-fi-rFI/ftp_sftp.xml
  • app/src/main/res/values-fi-rFI/wifi_about.xml
  • app/src/main/res/values-fr-rFR/ftp_sftp.xml
  • app/src/main/res/values-fr-rFR/wifi_about.xml
  • app/src/main/res/values-hi-rIN/ftp_sftp.xml
  • app/src/main/res/values-hi-rIN/wifi_about.xml
  • app/src/main/res/values-hr-rHR/ftp_sftp.xml
  • app/src/main/res/values-hr-rHR/wifi_about.xml
  • app/src/main/res/values-hu-rHU/ftp_sftp.xml
  • app/src/main/res/values-hu-rHU/wifi_about.xml
  • app/src/main/res/values-in-rID/ftp_sftp.xml
  • app/src/main/res/values-in-rID/wifi_about.xml
  • app/src/main/res/values-it-rIT/ftp_sftp.xml
  • app/src/main/res/values-it-rIT/wifi_about.xml
  • app/src/main/res/values-iw-rIL/ftp_sftp.xml
  • app/src/main/res/values-iw-rIL/wifi_about.xml
  • app/src/main/res/values-ja-rJP/ftp_sftp.xml
  • app/src/main/res/values-ja-rJP/wifi_about.xml
  • app/src/main/res/values-ko-rKR/ftp_sftp.xml
  • app/src/main/res/values-ko-rKR/wifi_about.xml
  • app/src/main/res/values-mk-rMK/ftp_sftp.xml
  • app/src/main/res/values-mk-rMK/wifi_about.xml
  • app/src/main/res/values-ne-rNP/ftp_sftp.xml
  • app/src/main/res/values-ne-rNP/wifi_about.xml
  • app/src/main/res/values-nl-rNL/ftp_sftp.xml
  • app/src/main/res/values-nl-rNL/wifi_about.xml
  • app/src/main/res/values-no-rNO/ftp_sftp.xml
  • app/src/main/res/values-no-rNO/wifi_about.xml
  • app/src/main/res/values-pl-rPL/ftp_sftp.xml
  • app/src/main/res/values-pl-rPL/wifi_about.xml
  • app/src/main/res/values-pt-rBR/ftp_sftp.xml
  • app/src/main/res/values-pt-rBR/wifi_about.xml
  • app/src/main/res/values-pt-rPT/ftp_sftp.xml
  • app/src/main/res/values-pt-rPT/wifi_about.xml
  • app/src/main/res/values-ro-rRO/ftp_sftp.xml
  • app/src/main/res/values-ro-rRO/wifi_about.xml
  • app/src/main/res/values-ru-rRU/ftp_sftp.xml
  • app/src/main/res/values-ru-rRU/wifi_about.xml
  • app/src/main/res/values-sk-rSK/ftp_sftp.xml
  • app/src/main/res/values-sk-rSK/wifi_about.xml
  • app/src/main/res/values-sl-rSI/ftp_sftp.xml
  • app/src/main/res/values-sl-rSI/wifi_about.xml
  • app/src/main/res/values-sr-rRS/ftp_sftp.xml
  • app/src/main/res/values-sr-rRS/wifi_about.xml
  • app/src/main/res/values-sr-rSP/ftp_sftp.xml
  • app/src/main/res/values-sr-rSP/wifi_about.xml
  • app/src/main/res/values-sr/ftp_sftp.xml
  • app/src/main/res/values-sr/wifi_about.xml
  • app/src/main/res/values-sv-rSE/ftp_sftp.xml
  • app/src/main/res/values-sv-rSE/wifi_about.xml
  • app/src/main/res/values-tr-rTR/ftp_sftp.xml
  • app/src/main/res/values-tr-rTR/wifi_about.xml
  • app/src/main/res/values-uk-rUA/ftp_sftp.xml
  • app/src/main/res/values-uk-rUA/wifi_about.xml
  • app/src/main/res/values-vi-rVN/ftp_sftp.xml
  • app/src/main/res/values-vi-rVN/wifi_about.xml
  • app/src/main/res/values-zh-rCN/ftp_sftp.xml
  • app/src/main/res/values-zh-rCN/wifi_about.xml
  • app/src/main/res/values-zh-rTW/ftp_sftp.xml
  • app/src/main/res/values-zh-rTW/wifi_about.xml
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values/wifi_about.xml
  • app/src/test/java/com/erikraft/drop/ReleaseContractTest.java
+-----------------+
| Shell we dance? |
+-----------------+
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@erikraft erikraft changed the title Improve FTP/SFTP defaults and show connection QR codes Improve FTP/FTPS/SFTP configuration and access controls Oct 16, 2026
@erikraft
erikraft marked this pull request as ready for review October 16, 2026 20:39
@strix-security

strix-security Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Strix Security Review

1 open security finding on this PR:

Review summary

This PR improves FTP/FTPS/SFTP configuration (FTPS mode selection, read-only enforcement, network-state display, QR codes, path validation, localization) but introduces a security regression in the default server configuration. The diff changes the default FTP/FTPS/SFTP credentials from admin/erikraft to trivially guessable android/android, flips the FTPS (TLS) default from enabled to disabled (plaintext transport), and flips the anonymous-login default from off to on. Since the FTP and SFTP servers bind to all local interfaces and default to writable access, any peer on the local network can authenticate with the well-known default credential and gain read/write access to the device's exposed directory, with credentials transmitted in cleartext over FTP. This was validated independently and filed as a single HIGH-severity finding (CWE-798) with actionable remediation that re-enables TLS by default, disables anonymous access by default, and prevents the server from running with an unset/guessable credential. The read-only SFTP accessor, connection QR-code generation, and SAF directory-path validation changes were reviewed and found to be sound hardening improvements.

Fixed the findings? re-run the review, or tag @strix-security in a PR comment to run a fresh review.

Updated for 12049f8.


Reviewed by Strix
Re-run review · Configure security review settings

@erikraft
erikraft merged commit c782bba into master Oct 16, 2026
6 of 8 checks passed

@strix-security strix-security Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strix flagged a new security finding below. See the pinned summary comment for the full PR status.

Comment on lines +102 to +107
String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "android");
String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "android");
boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), true);
boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), false);
boolean implicitFtps = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
boolean readOnly = prefs.getBoolean(getString(R.string.pref_ftp_read_only), false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Insecure FTP/FTPS/SFTP default configuration: guessable "android"/"android" credentials and TLS disabled by default expose device storage on the local network

Severity: HIGH · CWE-798

PR #232 changes the default FTP/FTPS/SFTP credentials from admin/erikraft to trivially guessable android/android, and flips the FTPS (TLS) default from true to false. Because the FTP and SFTP servers bind to all local interfaces, default to writable access (readOnly=false), and serve a directory of the device's storage, any device on the same local network can authenticate with the well-known android/android credential and read or write the exposed directory. For FTP the credential and all transferred data are now transmitted in cleartext. This is a security regression versus the prior defaults.

Suggested change
String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "android");
String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "android");
boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), true);
boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), false);
boolean implicitFtps = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
boolean readOnly = prefs.getBoolean(getString(R.string.pref_ftp_read_only), false);
String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "android");
String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "");
if (password.isEmpty()) {
throw new IllegalStateException("A senha do servidor FTP deve ser definida nas configurações.");
}
boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), false);
boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), true);
boolean implicitFtps = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
boolean readOnly = prefs.getBoolean(getString(R.string.pref_ftp_read_only), false);
Prompt to fix with AI
This is a security vulnerability found during a code review.

Vulnerability: Insecure FTP/FTPS/SFTP default configuration: guessable "android"/"android" credentials and TLS disabled by default expose device storage on the local network
Severity: HIGH
CWE: CWE-798

PR #232 changes the default FTP/FTPS/SFTP credentials from `admin`/`erikraft` to trivially guessable `android`/`android`, and flips the FTPS (TLS) default from `true` to `false`. Because the FTP and SFTP servers bind to all local interfaces, default to writable access (`readOnly=false`), and serve a directory of the device's storage, any device on the same local network can authenticate with the well-known `android`/`android` credential and read or write the exposed directory. For FTP the credential and all transferred data are now transmitted in cleartext. This is a security regression versus the prior defaults.

Location: app/src/main/java/com/erikraft/drop/FtpServerService.java:102-107
Context: FTP service insecure defaults (credential, TLS off, anonymous on)
```
// Before:
            String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "android");
            String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "android");
            boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), true);
            boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), false);
            boolean implicitFtps = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
            boolean readOnly = prefs.getBoolean(getString(R.string.pref_ftp_read_only), false);
// After:
            String username = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_username), ""), "android");
            String password = valueOrDefault(prefs.getString(getString(R.string.pref_ftp_password), ""), "");
            if (password.isEmpty()) {
                throw new IllegalStateException("A senha do servidor FTP deve ser definida nas configurações.");
            }
            boolean anonymous = prefs.getBoolean(getString(R.string.pref_ftp_anonymous), false);
            boolean ftps = prefs.getBoolean(getString(R.string.pref_ftp_ftps), true);
            boolean implicitFtps = prefs.getBoolean(getString(R.string.pref_ftp_ftps_implicit), false);
            boolean readOnly = prefs.getBoolean(getString(R.string.pref_ftp_read_only), false);
```

Location: app/src/main/java/com/erikraft/drop/SftpServerService.java:105-110
Context: SFTP service insecure default credential
```
// Before:
            String username = valueOrDefault(
                    prefs.getString(getString(R.string.pref_sftp_username), ""), "android");
            String password = valueOrDefault(
                    prefs.getString(getString(R.string.pref_sftp_password), ""), "android");
            String configuredHome = prefs.getString(getString(R.string.pref_sftp_save_location), "");
            boolean readOnly = prefs.getBoolean(getString(R.string.pref_sftp_read_only), false);
// After:
            String username = valueOrDefault(
                    prefs.getString(getString(R.string.pref_sftp_username), ""), "android");
            String password = valueOrDefault(
                    prefs.getString(getString(R.string.pref_sftp_password), ""), "");
            if (password.isEmpty()) {
                throw new IllegalStateException("A senha do servidor SFTP deve ser definida nas configurações.");
            }
            String configuredHome = prefs.getString(getString(R.string.pref_sftp_save_location), "");
            boolean readOnly = prefs.getBoolean(getString(R.string.pref_sftp_read_only), false);
```

How to fix:
Restore secure defaults by requiring the user to set a non-default password (or generate a strong random credential) before the server can start, keeping FTPS/TLS enabled by default (`true`), and keeping anonymous login disabled by default (`false`). Prefer not to ship any hard-coded fallback credential; if a fallback must exist, generate a per-installation random token rather than a shared static value. Display the current credential to the user through the existing QR code / settings UI so it remains discoverable without weakening security.

Please fix this vulnerability. If you propose a fix, make it concise and minimal.

React 👍 / 👎 to tune Strix for this repo. A repo collaborator (or the PR author) can resolve this thread to dismiss the finding.

Comment on lines 105 to +110
String username = valueOrDefault(
prefs.getString(getString(R.string.pref_sftp_username), ""), "admin");
prefs.getString(getString(R.string.pref_sftp_username), ""), "android");
String password = valueOrDefault(
prefs.getString(getString(R.string.pref_sftp_password), ""), "admin");
prefs.getString(getString(R.string.pref_sftp_password), ""), "android");
String configuredHome = prefs.getString(getString(R.string.pref_sftp_save_location), "");
boolean readOnly = prefs.getBoolean(getString(R.string.pref_sftp_read_only), false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SFTP service insecure default credential

Suggested change
String username = valueOrDefault(
prefs.getString(getString(R.string.pref_sftp_username), ""), "admin");
prefs.getString(getString(R.string.pref_sftp_username), ""), "android");
String password = valueOrDefault(
prefs.getString(getString(R.string.pref_sftp_password), ""), "admin");
prefs.getString(getString(R.string.pref_sftp_password), ""), "android");
String configuredHome = prefs.getString(getString(R.string.pref_sftp_save_location), "");
boolean readOnly = prefs.getBoolean(getString(R.string.pref_sftp_read_only), false);
String username = valueOrDefault(
prefs.getString(getString(R.string.pref_sftp_username), ""), "android");
String password = valueOrDefault(
prefs.getString(getString(R.string.pref_sftp_password), ""), "");
if (password.isEmpty()) {
throw new IllegalStateException("A senha do servidor SFTP deve ser definida nas configurações.");
}
String configuredHome = prefs.getString(getString(R.string.pref_sftp_save_location), "");
boolean readOnly = prefs.getBoolean(getString(R.string.pref_sftp_read_only), false);

Copy link
Copy Markdown
Owner Author

The Strix HIGH finding is being addressed in a separate security PR: #234. It migrates insecure legacy defaults, enables FTPS by default, disables anonymous FTP by default, generates per-install random passwords when legacy credentials are weak, and rejects weak credentials at both FTP/FTPS and SFTP server boundaries. PR #234 is currently Draft while CI and manual Android validation are pending.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant