DEV Community

Johnson
Johnson

Posted on

Debugging Filestash 'Invalid Account': How Response Time Led Me to a Swapped Config Field

The Problem

Filestash's passthrough auth returns "Invalid account" on login. The backend is configured to forward credentials to the local SSH server (OpenSSH on the same machine). SSH works fine when tested directly. Password auth is enabled.

SYST INFO [auth] status=failed user=myuser backend=sftp err=Invalid+account
HTTP 307 POST  4.7ms /api/session/auth/?label=sftp
Enter fullscreen mode Exit fullscreen mode

Environment:

  • Filestash running in Docker (bridge network 172.20.0.0/16)
  • SSH server on host: 192.168.1.x:22, listening on 0.0.0.0:22
  • UFW active on host
  • identity_provider.type = passthrough, attribute_mapping.related_backend = sftp

Diagnostic Step 1: Read the Response Time

Before touching config, the response time is the first clue.

Time What it means
< 5ms Pre-connection failure — params missing/invalid, DNS failure
~5–50ms TCP connection refused (immediate RST)
100–500ms SSH handshake + auth failure (wrong password)
3000ms+ TCP timeout (UFW DROP, no route)

4.7ms → we never attempted a TCP connection. The error is thrown before ssh.Dial().

In Filestash's Go SFTP backend (plg_backend_sftp.go), ErrNotValid ("Invalid account") is returned whenever:

  1. hostname is empty after decryption
  2. ssh.Dial() fails for any reason

At 4.7ms, it's case 1.


Diagnostic Step 2: Test Container Network Access

Even though the fast response time ruled out a network issue causing this failure, understanding the network matters for the full fix:

# From inside the Filestash container
docker exec filestash curl --connect-timeout 3 telnet://192.168.1.x:22
# → Timed out after 3002ms

docker exec filestash curl --connect-timeout 3 telnet://172.20.0.1:22
# → Timed out after 3002ms
Enter fullscreen mode Exit fullscreen mode

UFW is blocking Docker's subnet from SSH. This won't cause "Invalid account" (too slow), but it means even after fixing the config, we'll need to address networking.


Diagnostic Step 3: Read the Actual Config

The SFTP params are AES-256-GCM encrypted in config.json. The admin API (GET /admin/api/config) returns decrypted values — but only if you're the browser.

Initial curl attempt:

curl -b admin_cookies.txt http://localhost:8334/admin/api/config
# → {"status": "error", "message": "Not Allowed"}
Enter fullscreen mode Exit fullscreen mode

Check the Filestash JS source (lib/ajax.js):

opts.headers["X-Requested-With"] = "XmlHttpRequest";
Enter fullscreen mode Exit fullscreen mode

All Filestash AJAX requests include this header. Without it, admin endpoints return 403.

curl -b admin_cookies.txt \
  -H 'X-Requested-With: XmlHttpRequest' \
  http://localhost:8334/admin/api/config
Enter fullscreen mode Exit fullscreen mode

Now the decrypted config returns. The SFTP params:

{
  "sftp": {
    "type": "sftp",
    "hostname": "myserver",
    "username": "{{ .user }}",
    "password": "{{ .password }}",
    "path": "192.168.1.x",
    "port": "22"
  }
}
Enter fullscreen mode Exit fullscreen mode

hostname = "myserver" (machine hostname, not resolvable inside Docker container)
path = "192.168.1.x" (IP address, entered in the wrong field)

The fields were swapped during browser configuration. DNS lookup for the machine hostname fails instantly inside the container → "Invalid account" at 4.7ms. ✅ Confirmed.


The Fix

Two issues to address:

Fix 1: Correct the SFTP params

POST the corrected config via admin API:

import json, subprocess

# Read current admin config
r = subprocess.run([
    'curl', '-s', '-b', 'admin_cookies.txt',
    '-H', 'X-Requested-With: XmlHttpRequest',
    'http://localhost:8334/admin/api/config'
], capture_output=True, text=True)
cfg = json.loads(r.stdout)['result']

# strip formObj metadata wrappers (Filestash admin API format)
def strip_metadata(obj):
    if isinstance(obj, dict):
        if 'value' in obj and 'label' in obj:
            return obj['value']
        return {k: strip_metadata(v) for k, v in obj.items()}
    return obj

clean = strip_metadata(cfg)

# Fix the swapped fields
sftp_params = json.loads(clean['middleware']['attribute_mapping']['params'])
sftp_params['sftp']['hostname'] = '127.0.0.1'  # SSH server on the host
sftp_params['sftp']['path'] = '/home/myuser'    # initial directory
clean['middleware']['attribute_mapping']['params'] = json.dumps(sftp_params)

# Restore connections (they're not in admin config, come from public config)
clean['connections'] = [{'type': 'sftp', 'label': 'sftp'}]

subprocess.run([
    'curl', '-s', '-b', 'admin_cookies.txt',
    '-H', 'X-Requested-With: XmlHttpRequest',
    '-H', 'Content-Type: application/json',
    '-X', 'POST', '-d', json.dumps(clean),
    'http://localhost:8334/admin/api/config'
])
Enter fullscreen mode Exit fullscreen mode

Note: Always restore connections when POSTing admin config — the admin config endpoint does not include connections (they come from the public /api/config endpoint). If you don't add them back, the login form disappears.

Fix 2: Fix container networking

With hostname = 127.0.0.1, the container needs to reach the host's SSH. Two options:

Option A — UFW rule (requires sudo):

sudo ufw allow from 172.25.0.0/16 to any port 22
Enter fullscreen mode Exit fullscreen mode

Option B — Host networking (no sudo, cleaner):

# docker-compose.yml
services:
  filestash:
    image: filestash/filestash
    container_name: filestash
    network_mode: host        # remove ports: mapping, not needed with host networking
    volumes:
      - filestash_data:/app/data/state
    restart: unless-stopped

volumes:
  filestash_data:
Enter fullscreen mode Exit fullscreen mode
cd ~/filestash && docker compose down && docker compose up -d
Enter fullscreen mode Exit fullscreen mode

With host networking, the Filestash process runs on the host's network stack. 127.0.0.1:22 is the host's SSH server. No UFW rules needed.


Verification

After the fix:

SYST INFO [auth] status=failed user=myuser backend=sftp err=Invalid+account
HTTP 307 POST  53ms /api/session/auth/?label=sftp
Enter fullscreen mode Exit fullscreen mode

53ms — actual SSH connection attempted (test was with wrong password, so auth failed, but the connection was made). With correct credentials:

SYST INFO [auth] status=ok user=myuser backend=sftp
HTTP 302 POST  91ms /api/session/auth/?label=sftp
Enter fullscreen mode Exit fullscreen mode

Summary

Issue Symptom Cause Fix
Hostname/path swapped "Invalid account" in 4.7ms Browser form filled incorrectly — hostname="myserver", path="192.168.1.x" Swap: hostname="127.0.0.1", path="/home/myuser"
UFW blocks Docker subnet TCP timeout (3s) to host SSH UFW blocks 172.25.0.0/16 → port 22 Switch to network_mode: host

Key debugging insight: Response time tells you where the failure is. Sub-5ms = before any network I/O. This rules out 90% of the usual suspects and points directly at config/param issues.


Checklist for Filestash SFTP Passthrough Auth

  • [ ] middleware.identity_provider.type = "passthrough"
  • [ ] middleware.attribute_mapping.related_backend = "sftp"
  • [ ] attribute_mapping.params contains correct hostname (not the machine's hostname, not the path)
  • [ ] connections array is present in config (check /api/config public endpoint)
  • [ ] Container can reach hostname:port via TCP (test with curl telnet://host:port from inside container)
  • [ ] SSH server has PasswordAuthentication yes (or not explicitly disabled)
  • [ ] When calling admin API from scripts: include X-Requested-With: XmlHttpRequest header

Filestash: https://www.filestash.app — self-hosted file browser with SFTP, S3, FTP, and more.

Top comments (0)