npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

node-ftp-honeypot

v2.0.0

Published

FTP honeypot: logs every credential and command, quarantines uploads, serves bait files with honeytokens

Readme

📡 node-ftp-honeypot

Tests license

An FTP honeypot written in Node.js, on top of ftp-srv.

Bots scan the internet for open FTP servers, try default credentials and drop files: web shells, miners, botnet droppers. node-ftp-honeypot lets every one of them in, writes down everything they do, and keeps what they upload in quarantine.

  • 🔑 Every login works, and every username / password tried is logged
  • 📦 Uploads are quarantined with a sha256, never stored where other clients can see them
  • 📝 Every connection and command goes to a daily JSON lines log
  • 🎣 Bait files with honeytokens: a fake wp-config.php.bak and an old SQL dump. When a bot logs in with a password it could only have found there, you know it took the bait
  • 🛡️ Nothing real to steal or break: downloads limited to the bait, no deletes, no active mode, size and quota limits

Sibling project: express-honeypot, the same idea for web RFI/LFI bots 🍯

🐳 Quick start with Docker

docker run -d -p 21:2121 -p 50000-50100:50000-50100 -e PASV_URL=<your public ip> -v pandora:/data ghcr.io/christophe77/node-ftp-honeypot

Or with the provided docker-compose.yml (read only filesystem, no capabilities, non root user). Events and uploads end up in the /data volume.

On Docker Desktop (Windows / macOS), every client shows up with the IP of the Docker gateway. Run it on a Linux host to see the real attacker IPs.

🧩 As a library

npm install node-ftp-honeypot
const { createFtpHoneypot } = require("node-ftp-honeypot");

const honeypot = createFtpHoneypot({
  pasvUrl: "203.0.113.10",
  onEvent: (event) => {
    if (event.type === "login" && event.honeytoken) {
      console.warn(`🚨 ${event.ip} came back with the password from the bait files`);
    }
  },
});

await honeypot.start();

Every option of the configuration table below is available, in camelCase (maxFileBytes, ignoreIps, honeytokens, baitDir...).

🛠️ From source

git clone https://github.com/christophe77/node-ftp-honeypot
cd node-ftp-honeypot
npm install
npm start

The honeypot listens on port 2121. To expose it as a real FTP server, forward port 21 to it rather than running Node as root:

sudo iptables -t nat -A PREROUTING -p tcp --dport 21 -j REDIRECT --to-port 2121

Also open the passive port range (50000 to 50100 by default) and set PASV_URL to your public IP.

Configuration

| Variable | Default | Description | | --- | --- | --- | | FTP_PORT | 2121 | Control port | | FTP_HOST | 0.0.0.0 | Listening address | | PASV_URL | none | Public IP or hostname announced in passive mode. Set it in production | | PASV_MIN / PASV_MAX | 50000 / 50100 | Passive port range | | DATA_DIR | pandora-box | Where events and uploads are stored | | BAIT_DIR | black-box/ftp | Read only directory shown to clients | | FTP_GREETING | Welcome to VOFTP a very open FTP Server. | Banner | | MAX_FILE_MB | 10 | Max size of one upload | | MAX_TOTAL_MB | 1024 | Max size of all uploads together | | MAX_CONNECTIONS | 100 | Max simultaneous connections | | MAX_CONNECTIONS_PER_IP | 5 | Max simultaneous connections per IP | | IDLE_TIMEOUT_MS | 60000 | Idle connections are closed after this delay | | IGNORE_IPS | none (127.0.0.1 in Docker) | Comma separated IPs that are served but not logged (your own IP, monitoring) | | HONEYTOKENS | the passwords of the default bait | Comma separated passwords that flag a login as honeytoken: true |

What you get

pandora-box/
  events/2026-10-09.jsonl
  uploads/2026-10-09/203.0.113.7/3fa2c1d9e8b0-shell.php

One JSON object per line:

{"ts":"2026-10-09T08:12:01.120Z","type":"login","session":"…","ip":"203.0.113.7","username":"admin","password":"admin123"}
{"ts":"2026-10-09T08:12:01.410Z","type":"upload","session":"…","ip":"203.0.113.7","clientPath":"/shell.php","file":"2026-10-09/203.0.113.7/3fa2c1d9e8b0-shell.php","size":2048,"sha256":"3fa2c1d9e8b0…"}

Event types: connect, connection_refused, login, command, download, upload, upload_rejected, disconnect, client_error, server_error.

🎣 Bait and honeytokens

The directory clients see is black-box/ftp: a README.txt, a wp-config.php.bak and backup/db_backup_2019.sql, with fake credentials inside. Downloads are allowed there, and only there, and each one is logged as a download event.

The passwords in those files are listed in HONEYTOKENS. They exist nowhere else, so a login using one of them is flagged honeytoken: true: that bot downloaded the bait, parsed it, and came back to try what it found.

Use your own bait with BAIT_DIR, and list its passwords in HONEYTOKENS.

The sha256 is ready to be looked up on VirusTotal or MalwareBazaar.

Allowed commands

USER PASS SYST FEAT OPTS NOOP QUIT PWD CWD CDUP TYPE MODE STRU PASV EPSV LIST NLST SIZE RETR STOR

RETR only works on the bait files. Everything else is refused, including DELE, RNFR, MKD, and PORT / EPRT (active mode would let anyone make the honeypot connect to any address, the classic FTP bounce attack).

⚠️ Safety

Uploaded files are real malware. They are stored as plain files and never executed, but do not open them on your machine, and keep the data directory away from anything that could serve or run them.

License

MIT