
Self-contained SSH honeypot for capturing attacker interactions and turning them into structured security intelligence.
SSHintel is a lightweight SSH honeypot built using Python and Paramiko. It simulates a fake Linux shell to log unauthorized access attempts, capture credentials, and analyze attacker behavior in a controlled environment.
ls, cd, pwd, cat, echo, grep, find, tree, head, tail, wc, stat, ps, df, free, env, id, whoami, etc.)--tarpit mode to slow down attackers with delayed outputSSHintel is not a full Bash/Linux shell. It simulates a believable subset of common commands to gather attacker telemetry. Commands are dispatched by a lightweight registry; adding a command means adding a small handler function.
pip install -r requirements.txt
Note: The SSH host key is automatically generated on first run. No manual key generation needed.
Run the honeypot with a specific port, username, and password:
python3 run.py serve --port 2222 --username user1 --password pass123
Default port is
2222and host is0.0.0.0.
The honeypot automatically generates an SSH host key at static/server.key on first run if one doesn't exist. No manual key generation is needed.
To enable tarpit mode:
python3 run.py serve --port 2222 --username user1 --password pass123 --tarpit
To disable SQLite telemetry (JSONL only):
python3 run.py serve --port 2222 --username user1 --password pass123 --no-db
SSHintel guards against resource exhaustion from many concurrent connections or connections kept alive indefinitely. These are configurable via the CLI:
Example:
python3 run.py serve --port 2222 --username user1 --password pass123 \
--max-connections 25 --auth-timeout 30 --session-idle-timeout 300
When too many connections are open, the extra connection is closed immediately and a connection_rejected security event (with reason: connection_limit) is written to the JSONL log. A stalled authentication is recorded as a disconnect with reason: auth_timeout; an idle shell ends with reason: idle_timeout.
Tarpit mode intentionally sends output slowly to keep an attacker engaged, so the tarpit banner loop is not subject to the inactivity timeout — but tarpit sessions do count against the connection limit.
SSHintel includes a local web dashboard that visualizes the security telemetry stored in SQLite.
python3 run.py dashboard
Then open http://localhost:5000 in your browser.
The dashboard reads from the SQLite database at
data/sshintel.dbby default. Start the honeypot first so telemetry is being captured, then launch the dashboard to watch it populate.
Click a session ID (or navigate to /session/<session_id>) to open the session investigation view, which reconstructs a single attack chronologically:
$ command view of everything the attacker typed, with working directories# Terminal 1: start the honeypot
python3 run.py serve --port 2222 --username user1 --password pass123
# Terminal 2: start the dashboard
python3 run.py dashboard
# Terminal 3: simulate an attacker
ssh user1@localhost -p 2222
# (run some commands, then exit)
Then open http://localhost:5000 to inspect the captured activity.
The dashboard supports live telemetry — it polls the honeypot every 2 seconds and updates automatically. New sessions, commands, and events appear in real-time without refreshing the page. A live indicator (● Live) shows the connection status.
Open a second terminal and try connecting:
ssh user1@localhost -p 2222
If the credentials match, you’ll be dropped into the emulated shell.
To remove stale SSH fingerprints:
notepad "%USERPROFILE%\.ssh\known_hosts"
Delete the relevant line containing
localhostor the honeypot's IP.
creds_loggerfunnel_loggerlog_files/events.jsonl as JSON Lines (JSONL) — one valid JSON object per lineEach JSONL event includes a UTC ISO-8601 timestamp, an event_type, a unique session_id, and the source_ip. Connection, authentication attempts/results, command execution, tarpit activation, and disconnects are all recorded as structured events.
Each incoming SSH connection is tracked as an independent session with its own session_id. A session records the source IP, connect/disconnect times, the authentication outcome, and the connection duration, and every event generated within that connection carries the same session_id (so authentication attempts, commands, and disconnects can be tied back to a single connection). Sessions are isolated per connection — no state is shared between concurrent clients.
Every session also receives its own isolated, in-memory fake filesystem — the simulated filesystem is created fresh for each connection and cleaned up when the connection ends. Files, directories, and the working directory created or changed by one attacker are never visible to another attacker connected at the same time. The entire filesystem is simulated in Python memory and never touches the real host filesystem.
Current event_type values: connect, auth_attempt, auth_success, auth_failure, command, disconnect, connection_rejected, tarpit.
SSHintel/
├── honeypot/ # Core honeypot logic
│ ├── __init__.py
│ ├── main.py # Accept loop + connection limiting
│ ├── handlers.py # SSH transport setup + emulated shell
│ ├── server.py # Paramiko server interface (auth)
│ ├── session.py # Per-connection session tracking
│ ├── fs.py # In-memory fake filesystem (isolated per session)
│ ├── shell.py # Fake shell: command registry + dispatcher
│ ├── limits.py # Thread-safe concurrent connection limiting
│ ├── logger.py # JSONL event logging + SQLite bridge
│ └── telemetry_store.py # SQLite telemetry store + query layer
│
├── dashboard/ # Local web dashboard
│ ├── app.py # Flask application + API routes
│ ├── templates/
│ │ ├── index.html # Main dashboard template
│ │ └── session.html # Session investigation template
│ └── static/
│ ├── style.css # Dashboard styles
│ ├── dashboard.js # Main dashboard JS (live updates)
│ └── session.js # Session investigation JS
│
├── log_files/ # Runtime logs (git-ignored)
│ ├── creds_audits.log # Credential attempts
│ ├── cmd_audits.log # Command audit trail
│ └── events.jsonl # Structured JSONL security events
│
├── data/ # SQLite database (git-ignored)
│ └── sshintel.db
│
├── static/ # SSH host key (auto-generated)
│ └── server.key
│
├── .github/workflows/ # CI configuration
│ └── tests.yml
│
├── Dockerfile
├── README.md
├── requirements.txt # Runtime dependencies (paramiko, flask)
├── requirements-dev.txt # Test dependencies (pytest, pytest-cov)
└── run.py # CLI entrypoint
If you prefer to run the honeypot in a containerized environment, you can use the included Dockerfile.
docker build -t sshintel .
This creates a Docker image named
sshintel.
docker run -p 2222:2222 sshintel
This will:
static/server.key (if it doesn't already exist)2222 with default credentials:username: user1, password: pass123Open a second terminal and connect via SSH:
ssh user1@localhost -p 2222
You’ll be dropped into the simulated shell if the credentials match.
To stop the container:
docker ps # Find the container ID
docker stop <container_id>
To remove the image:
docker rmi sshintel
You can also export the image using
docker save -o sshintel.tar sshinteland load it later withdocker load -i sshintel.tar.
This project is licensed under the MIT License.
| Flag | Default | Purpose |
|---|
--max-connections | 50 | Maximum simultaneous active connections; extra connections are rejected and logged as a connection_rejected event |
--auth-timeout | 60 (s) | Time allowed to complete the SSH handshake/authentication; stalled clients are disconnected |
--session-idle-timeout | 300 (s) | Inactivity timeout for an authenticated shell; an idle session is ended, but an actively-typing attacker is never killed |