A FileZilla password isn't encrypted, it's base64. And it might be in your web root.

Open FileZilla's sitemanager.xml and find the <Pass> element. It looks scrambled. It isn't. The contents are base64, and base64 is an encoding, not a cipher. echo cGFzc3dvcmQ= | base64 -d gives you back password in the time it takes to hit enter. Whoever wrote that file format decided the saved FTP password didn't need protecting, because the file was never supposed to leave your machine.

But it does leave. These files have a way of ending up in a public web root, sitting next to index.html, answering an anonymous GET. And this isn't your .env. There's a separate article for that one. This is the other pile, the credential-bearing config files you forget you have: the deploy tool's saved login, the framework's parameters file, the registry auth blob. Each one is a host, a username, and a password a stranger can just download.

Here's what each one hands over.

The deploy tool remembers your FTP login

FileZilla writes your saved connections to sitemanager.xml, and the recent ones to recentservers.xml. Each entry is a host, a port, a username, and that base64 <Pass>. The VS Code and Sublime SFTP extensions do the same thing in sftp.json, .config/sftp.json, or sftp-config-alt.json, except those store the deploy target's password in cleartext, no encoding fig leaf at all. People commit these by reflex, the whole project folder gets deployed, and now the credentials for the server you deploy to are readable from the server you deploy to.

request
GET /sitemanager.xml HTTP/1.1 Host: yoursite.com
response
HTTP/1.1 200 OK
Content-Type: text/xml
<FileZilla3><Servers><Server>
<Host>ftp.yoursite.com</Host><User>deploy</User>
<Pass encoding="base64">cGFzc3dvcmQ=</Pass>
Host, user, and a base64 password. base64 -d turns the last line into the plaintext login in one command.

The SFTP configs are worse only in that they skip the encoding. The result is identical: a stranger gets the host you push to and the credential to push there. From there they can drop a web shell into the same directory the legit deploy lands in, and now they own the running site too.

The framework wrote the database password into a config file

PHP frameworks scatter credentials across config files that have no reason to be web-served and frequently are anyway.

$ scan yoursite.com
probing yoursite.com from outside, no credentials...
/tomcat-users.xmlTomcat manager password in plaintext, manager-gui role
/app/config/parameters.ymlSymfony database_password and app secret, cleartext
/app/etc/env.phpMagento DB password plus crypt.key
/auth.jsonComposer private-registry credentials
/.dockercfgDocker registry login, base64 user:pass
5 exposures visible to anyone. None required a login.
Five different tools, one shared mistake: a host-plus-credential file served to anyone who asks.

tomcat-users.xml (also at conf/tomcat-users.xml) is the sharpest of these. It stores the Tomcat manager password in plaintext, and if the entry carries the manager-gui role, the chain writes itself: read the password, log into the manager app, upload a WAR file, get remote code execution on the box. Plaintext password to RCE in four steps, and step one is a GET request.

Symfony 2 and 3 keep database_password and the application secret in app/config/parameters.yml (or config/parameters.yml), both in the clear. That's your database login and your session-signing secret in one file. Magento 2's app/etc/env.php is the one that keeps people up at night: it holds the database password and the crypt.key, and crypt.key is what decrypts every encrypted column in the database, including stored payment tokens. The file leaks the key that unlocks everything the key was supposed to protect.

The registry login is a base64 blob too

auth.json (and .composer/auth.json) is Composer's credential store for private package registries and VCS hosts. It holds whatever you authenticate with in cleartext: http-basic username and password, github-oauth tokens, gitlab-token, bearer tokens. Read it and a stranger can pull your private packages, which often means reading your proprietary source.

.dockercfg is the legacy Docker version of the same idea, a flat JSON map of registry to a base64 auth field. Same trick as FileZilla, same one-line undo.

$ yoursite.com
$ curl -sI yoursite.com/.dockercfg | head -1
HTTP/2 200
$ curl -s yoursite.com/.dockercfg
{"registry.example.com":{"auth":"dXNlcjpwYXNz","email":"[email protected]"}}
$ echo dXNlcjpwYXNz | base64 -d
user:pass
The auth field is base64(user:pass). Decoding it is the same move that opens the FileZilla password.

Why they end up downloadable

Always the same root cause, and it's the one behind the leaked cloud and infra config files too: the deploy shipped more than the app. A COPY . . in a Dockerfile bakes the whole project, config files included, into the image. An rsync without an exclude list copies everything. A git pull on the server drops the committed sftp.json and auth.json right into the document root. The web server doesn't know these files are sensitive. It sees a static file next to index.html and returns the bytes, 200, no questions.

The fix is two layers. Stop deploying these files at all: your web root should hold the built application and nothing else, with sftp.json, auth.json, .dockercfg, parameters.yml, env.php, and the FileZilla XMLs on a deny-and-exclude list. Then, as a backstop, make the server refuse them even if one slips through.

# the whole project folder, deployed wholesale
/var/www/html/
sitemanager.xml          <-- base64 FTP password
auth.json                <-- registry tokens
app/etc/env.php          <-- DB password + crypt.key
.vscode/sftp.json        <-- deploy host + password
The deny rule is the safety net. The real fix is never deploying these into the served directory.

Reading it from outside

Whether any of these answers an anonymous GET is something a stranger settles by requesting the path, and that's exactly what SurfaceCheckr does, from outside, with no login. Each check is gated on the file's own content signature: the FileZilla3 markup, the Composer http-basic/github-oauth keys, the Tomcat manager-gui role string, the Symfony database_password line, the Magento crypt.key shape, the .dockercfg auth field, the SFTP host-and-credential pair. A 200 alone doesn't trip them, so the soft-404 catch-all that returns 200 for everything won't produce a false hit. It reads enough to confirm the file is genuinely there and stops. It never logs in and never uses the credential it found, because the whole point is to see the deploy the way the public sees it, which is the one angle you can't get from inside your own server. This isn't a pentest. It's the question on the doormat: is the file there for anyone who asks?

And if any of these was ever reachable, the credential in it is compromised. Not "might be." Treat the FTP login, the registry token, the database password, the Magento crypt.key, all of it, as already stolen, and rotate. The file has been public for as long as it's been deployed, and a crawler has had the same thirty seconds you just spent reading this.

Find it before someone else does.

Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.