Attacker identity engine for SSH honeypot telemetry. Aka: it makes the bots think they hit a real prod box, then puts their entire playbook on blast.
ShardLure clusters SSH bots by playbook fingerprint (OpenSSH journal lines) or HASSH (Cowrie sessions), not just IP. Same actor on three IPs? Still one actor. Different actor with the same address? Different rows. The vibe is "username taste profile, not who's-at-the-door." It ships with a VPS installer, Cowrie integration, live ingest, a forensic TUI, and a web dashboard that spins a globe at you.
attacker -> port 22 (Cowrie) -> JSON/journal ingest -> SQLite actors -> dashboard
you -> port 2222 (SSH) -> real admin access via keys/Tailscale
- Features
- Setup Guide
- Local Development
- Commands
- Configuration
- Deployment
- Persona And Bait
- IP Reputation Enrichment
- Threat Intel Sharing (MalwareBazaar)
- Architecture
- Security Notes
- Troubleshooting
- Uninstall
- Roadmap
- Dual ingest: OpenSSH journal exports and Cowrie JSON logs. No vendor lock-in, no SaaS dashboard reading your shame.
- Actor clustering: journal actors by source IP, Cowrie actors by HASSH. Botnets get sorted by their vibe (HASSH + username corpus), not just where their NAT slingshot lands.
- Intent classification: probe, proxy, deploy, mixed, or unknown. The "deploy" ones are the spicy ones — that's curl-bash-into-tmp energy.
- Live mode: tails Cowrie + journal straight into a globe dashboard. Real-time slay.
- VPS bootstrap: installs Cowrie, moves real SSH to a private port, writes systemd units, starts everything. One command, no yak shaving.
- Stealth persona: Ubuntu-style banner, fake
prod-app-server-01hostname, regenerated host keys so you don't get fingerprinted as "obvious honeypot #4892." - Bait files: fake
.env, AWS creds, DB creds, deploy keys, nginx site. Looks legit, is poison. - Deploy-safe sync: tar-over-SSH because
scpof Go/Python sources mysteriously turns them into UTF-16. We do not gaslight you about this — see Troubleshooting. - Incremental Cowrie ingest: tracks file offset + inode, so a 100MB cowrie.json doesn't get re-scanned every 5 seconds. Your I/O thanks us.
- Idempotent everything: re-running ingest dedupes events instead of duping them. Past you can't bully present you.
- Dragon theme: purpose-built SOC dashboard with sidebar navigation, Chakra Petch typography, blood-red/molten-gold palette, flat panels, sharp geometry. No glass-morphism — this is a wartime console.
- Dashboard widgets: threat-level gauge, attack geography, brute-force radar, top credentials, live attack timeline. All fed by real-time API polling.
- One-click MalwareBazaar upload: share captured payloads to abuse.ch directly from the payload inspector modal. No CLI required.
- Seven-provider IP enrichment: look up any attacker IP against AbuseIPDB, VirusTotal, GreyNoise, Shodan, AlienVault OTX, IPQualityScore, and IPinfo in parallel — normalized verdict + score + tags, cached 24h. Two work with no API key.
- Persistent geo cache: IP geolocation results are stored in SQLite and survive restarts. No more "resolving…" on every page load.
A complete walkthrough for standing up ShardLure on a fresh Ubuntu/Debian VPS (it also supports dnf/yum and pacman hosts). Budget ~10 minutes, most of it Cowrie's pip install.
Keep your current SSH session open until you've verified the new admin port works. The installer moves real SSH off port 22 so Cowrie can squat there.
You don't need to set up keys yourself first — the installer walks you through it. It is paranoid on your behalf:
- If no SSH key is found on the server, it pauses and lets you paste your public key in, installs it into the right account with correct permissions, and only then moves SSH. (No key, no paste → it aborts without touching sshd, so you can't lock yourself out.)
- After moving SSH it pauses at a verify gate: open a second terminal,
confirm
ssh -p <admin-port>works, and only then typeyesto continue. - The original sshd config is backed up and auto-rolled-back if the new one
fails
sshd -t.
Print your laptop's public key when prompted (make one first if needed):
ssh-keygen -t ed25519 # only if you don't already have a key
cat ~/.ssh/id_ed25519.pub # paste this when the installer asksgit clone https://github.com/hett-patell/shardlure.git
cd shardlureTransferring sources yourself? Use the tar-pipe deploy (
make deploy/bash scripts/push-sources.sh arm), notscp— somescp/editor pipelines silently re-encode Go/Python to UTF-16 and the build dies with "null bytes". See Troubleshooting.
sudo python3 scripts/shardlure.py runIt walks you through everything, in order:
- Intro + confirm — shows what it's about to change and waits for your OK.
- Install dependencies (git, python venv toolchain, authbind, Go) via your distro's package manager.
- Prompt for three ports (validated; defaults shown below).
- Detect your admin IPs — Tailscale IP + your current SSH client IP are
auto-added to
admin_ips(so you never classify yourself as an attacker); you can add more. - Check / install your SSH key — if the server has no key yet, it pauses and lets you paste your public key in, installing it with correct perms.
- Move real SSH to the admin port via a drop-in at
/etc/ssh/sshd_config.d/99-shardlure-admin.conf(key-only, root password login disabled). The original config is backed up to/etc/ssh/sshd_config.shardlure-bak. - Verify gate — pauses for you to confirm
ssh -p <admin-port>works in a second terminal before going further (typeyesto continue,abortto stop). - Create the
cowriesystem user, clone + build Cowrie into/var/lib/shardlure/cowrie, apply the stealth persona, regenerate host keys, and plant bait files. - Build the
shardlurebinary to/usr/local/bin/shardlure. - Write
/var/lib/shardlure/shardlure.yaml. - Open firewall ports (only if
ufwis already active). - Install + start two systemd services:
cowrie.serviceandshardlure-live.service.
| Setting | Default | Purpose |
|---|---|---|
| Honeypot port | 22 |
Cowrie listener for attackers |
| Admin SSH port | 2222 |
Real SSH, key-only |
| Dashboard port | 8080 |
Live dashboard |
From a second terminal, confirm the new admin port works:
ssh -p 2222 user@your-vps # use whatever admin port you choseOnly close your original session once that succeeds. (If something went wrong, the original sshd was auto-restored — but verify anyway.)
sudo python3 scripts/shardlure.py status
systemctl status cowrie shardlure-live
journalctl -u shardlure-live -f # watch live ingestOpen the dashboard at http://<tailscale-ip>:8080. Keep 8080 off the public
internet — port 22 is the bait, your dashboard is not bait, do not get those
confused. Tailscale or an SSH tunnel (ssh -L 8080:127.0.0.1:8080 ...) is the
right way to reach it.
For defense in depth, set a token the dashboard requires on every request:
# Add to the shardlure-live systemd unit's [Service] section, then daemon-reload + restart:
Environment=SHARDLURE_DASH_TOKEN=your-long-random-tokenThen reach the dashboard with ?token=… or an Authorization: Bearer … header.
Add provider API keys as Environment= lines in the shardlure-live unit (see
IP Reputation Enrichment and
Threat Intel Sharing). Two enrichment
providers (Shodan, GreyNoise) work with no key at all.
If setup was interrupted after Cowrie/SSH changes:
sudo python3 scripts/shardlure.py finishsudo python3 scripts/shardlure.py plant-baitVerify from the honeypot shell:
ls -la /opt/app/
cat /opt/app/.envgit clone https://github.com/hett-patell/shardlure.git
cd shardlure
go mod tidy
make build
./shardlure ingest journal testdata/sample.journal --replace
./shardlure ingest cowrie /var/lib/shardlure/cowrie/var/log/cowrie/cowrie.json --replace
./shardlure actors
./shardlure actor show 188.84.0.25
./shardlure dashboard
./shardlure web :8080 --tailscale
./shardlure live :8080 --cowrie=/path/cowrie.json --tailscaleThe binary can also launch the VPS wrapper:
sudo ./shardlure run| Command | Description |
|---|---|
ingest journal <file> [--replace] |
Parse journal auth lines and build actors |
ingest cowrie <file> [--replace] |
Parse Cowrie JSON logs and build actors |
actors [--limit=N] |
List actors by last seen |
actor show <id|ip> |
Show one actor profile |
dashboard, dash, tui |
Open the forensic TUI |
web [:8080] [--tailscale] |
Serve the web dashboard |
live [:8080] [--cowrie=PATH] [--interval=5s] [--no-journal] [--tailscale] |
Run live ingest and dashboard |
run |
Start the VPS wrapper |
status |
Print event and actor counts |
ioc |
Export a small IOC slice |
share bazaar [--dry-run] [--limit N] [--sha SHA] [--since DURATION] [--anonymous] [--status] |
Upload captured payloads to MalwareBazaar (abuse.ch) |
version |
Print version |
| Command | Description |
|---|---|
run, setup |
Full VPS bootstrap |
finish |
Resume after partial setup |
plant-bait, bait |
Inject bait files into Cowrie's virtual filesystem |
start, stop, status |
Manage services |
uninstall [--purge] |
Reverse the install: restore SSH, remove services + binary. --purge also deletes the data dir and cowrie user. See Uninstall. |
GitHub Actions runs on push and pull request:
go mod verifygo vet ./...go test -coverprofile=coverage.out ./...go build -o shardlure ./cmd/shardlure
The installer writes /var/lib/shardlure/shardlure.yaml. You can also copy shardlure.yaml.example:
data_dir: /var/lib/shardlure
admin_ips:
- 100.x.x.x # Tailscale IP or trusted admin workstation
ssh:
admin_port: 2222
honeypot_port: 22
dashboard:
port: 8080
# Change these to your VPS/operator location for the globe origin.
home_lat: 19.0760
home_lon: 72.8777
home_city: Mumbai
home_country: India
home_cc: IN
journal:
unit: ssh
cowrie:
home: /var/lib/shardlure/cowrie
json_log: /var/lib/shardlure/cowrie/var/log/cowrie/cowrie.jsonUse -config /path/shardlure.yaml or SHARDLURE_CONFIG to override the path.
Do not commit your real config. admin_ips may reveal private network details such as Tailscale IPs.
Use tar pipe deployment instead of direct scp for source files:
make deploy
# or
bash scripts/push-sources.sh armOn the VPS:
cd ~/ShardLure/shardlure
bash scripts/fix-go-sources.sh
sudo cp /tmp/shardlure /usr/local/bin/shardlure
sudo python3 scripts/shardlure.py finishsudo journalctl -u ssh -S "30 days ago" -o short-iso --no-pager \
| grep -E 'Invalid user|Failed password|Failed publickey|Accepted ' \
> ~/journal-ssh-30d.log
shardlure ingest journal ~/journal-ssh-30d.log --replaceinstall/persona/ includes a simple production-server disguise and decoy files.
| Virtual path | Bait |
|---|---|
/opt/app/.env |
Fake DB, Redis, JWT, Stripe, and AWS values |
/opt/app/config/database.yml |
Fake Postgres credentials |
/home/ubuntu/.aws/credentials |
Fake AWS deploy profile |
/home/deploy/.ssh/id_rsa |
Fake deploy key |
/var/backups/nightly/db_credentials.txt |
Fake backup credentials |
All credentials are intentionally fake. Regenerate bait values per deployment so multiple honeypots are not fingerprinted by identical decoys.
The intel console's enrichment panel looks up any attacker IP against multiple threat-intel providers in parallel, normalizes each into a verdict (malicious / suspicious / benign / unknown) + score + tags, and caches results for 24h in SQLite so you don't burn free-tier rate limits. Every provider is opt-in via an environment variable; missing keys render as "not configured" rather than failing the panel. Two providers are keyless and work out of the box.
| Provider | Env var | Key? | Signal |
|---|---|---|---|
| AbuseIPDB | SHARDLURE_ABUSEIPDB_KEY |
yes | Abuse-confidence score + report history |
| VirusTotal | SHARDLURE_VT_KEY |
yes | Multi-engine detections |
| GreyNoise (community) | SHARDLURE_GREYNOISE_KEY |
no (optional) | Internet-noise classification |
| Shodan InternetDB | — | no | Open ports, CPEs, known CVEs, host tags |
| AlienVault OTX | SHARDLURE_OTX_KEY |
yes | Community pulse reputation + campaign tags |
| IPQualityScore | SHARDLURE_IPQS_KEY |
yes | Fraud score + proxy / VPN / TOR / bot flags |
| IPinfo | SHARDLURE_IPINFO_KEY |
yes | ASN / org / geo + privacy (hosting/proxy/vpn/tor) flags |
Set whichever keys you have (in your shell or the systemd unit) before running
web or live. Results are fail-open: a provider that errors or lacks a key
never blocks the others.
shardlure share bazaar submits captured payloads to abuse.ch MalwareBazaar. Each upload is automatically classified (ELF arch, static-vs-dynamic linkage, scripting language, and a short list of well-known family fingerprints — RedTail, Mirai, Komari, Traffmonetizer, XMRig, c3pool) and tagged. abuse.ch's server-side analysis (YARA, ClamAV, telfhash) then bolts on the heavy-duty signatures.
Setup
-
Sign up at https://auth.abuse.ch/ and copy your Auth-Key.
-
Edit
shardlure.yaml:intel: bazaar: api_key: "your-auth-key-here" tags: ["shardlure", "honeypot"] max_bytes: 33554432 # 32 MiB freshness_days: 10 # abuse.ch fair-use: fresh samples only
-
Dry-run first to inspect what would ship:
shardlure share bazaar --dry-run --limit 10
-
When the output looks right, drop
--dry-run:shardlure share bazaar --limit 10
Re-running is safe: every sha256 we successfully submit (including file_already_known responses) is recorded in bazaar_uploads and skipped on the next run.
You can also share payloads from the web dashboard: open the payload inspector modal on any captured artifact and click Share to MalwareBazaar. Set SHARDLURE_BAZAAR_KEY in your environment or systemd unit for this to work. The Red Team tab's MalwareBazaar panel shows upload history, family classification, and pending counts.
Flags
| Flag | Default | Meaning |
|---|---|---|
--dry-run |
false | print classification + destination without POSTing |
--limit N |
10 | cap per-run uploads (0 = unbounded) |
--sha SHA |
– | upload only this specific sample (bypasses freshness) |
--since DUR |
240h | only consider artifacts captured within this window |
--anonymous |
false | submit without attribution to your account |
--status |
– | list past uploads from bazaar_uploads instead of uploading |
Why MalwareBazaar? It's the de-facto sharing hub for honeypot-captured Linux malware. Their submission policy (no PUPs/adware, no file infectors, samples must be <10 days old) is enforced both client-side (the share subcommand respects freshness_days) and server-side. Repeated violations get accounts banned — see internal/intel/bazaar/client.go for the fatal-status handling that halts the run on user_blacklisted.
+-------------+ +--------------+ +-------------+
| Cowrie :22 | --> | cowrie.json | --> | |
+-------------+ +--------------+ | SQLite |
| actors | --> web :8080
+-------------+ +--------------+ | events | TUI
| journal ssh | --> | tail/seed | --> | |
+-------------+ +--------------+ +-------------+
- Journal actors:
journal:<src_ip>, clustered by username playbook (their attempted-username distribution is their personality). - Cowrie actors:
cowrie:<hassh>, clustered by HASSH fingerprint (TLS-but-for-SSH client identity hash). - Admin IPs: explicitly excluded from clustering so you don't accidentally classify yourself as a "fast_dictionary_spray" actor.
- Verify
ssh -p 2222 user@hostin a second terminal before closing your original session. "I'll fix it in the morning" SSH stories never end well. - Keep the dashboard on Tailscale or another private network. Exposing the dashboard to the internet is what we call self-doxxing.
- Set
SHARDLURE_DASH_TOKENfor dashboard defense in depth (constant-time compared, sent asAuthorization: Bearer …orX-ShardLure-Token). - External geolocation is opt-in (
SHARDLURE_GEO_HTTP=1). UseSHARDLURE_IPAPI_KEYfor HTTPS lookups via ip-api Pro (recommended). Plaintexthttp://ip-api.comonly if you also setSHARDLURE_GEO_INSECURE_HTTP=1(leaks attacker IPs to the network path). - Cowrie SSH host keys are regenerated during install so you don't share a fingerprint with every other lazy honeypot on Shodan.
- Keep bait credentials fake. Yes really. Do not get clever and put "almost real" creds in there.
- The SQLite database is chmod'd to
0600automatically — it can contain attacker-supplied passwords, which sometimes overlap with their actually reused real ones. Treat the file like a loaded gun.
Symptoms (your editor/transfer pipeline silently re-encoded your source files):
SyntaxError: null bytescannot execute binary fileunexpected NUL in input
This is not a you-problem, it's a tooling-problem. Fix:
bash scripts/push-sources.sh armAvoid direct scp for Go/Python sources in this project. We are not in our scp era.
Ensure cowrie.cfg points at the real Cowrie data path:
data_path = /var/lib/shardlure/cowrie/src/cowrie/data
state_path = /var/lib/shardlure/cowrie/var/lib/cowrie
[shell]
filesystem = /var/lib/shardlure/cowrie/src/cowrie/data/fs.pickleThis is harmless on rerun. do_load indicates the file content was loaded.
fsctl does not implement ls. Verify bait through the honeypot shell or inspect honeyfs:
ls /var/lib/shardlure/cowrie/honeyfs/opt/app/
Tearing ShardLure down means undoing everything the installer did: restoring
real SSH to port 22, removing the two systemd services and the binary, and
(optionally) deleting Cowrie, the captured-intel database, and the cowrie
user. There are two ways to do it — the built-in command (recommended) and a
fully manual walkthrough if you'd rather see every step.
The same golden rule as install: SSH gets reconfigured. Keep a working session open and verify connectivity on the restored port before you log out. The uninstaller restores SSH first, before touching anything else, precisely so a half-finished teardown can't strand you.
# Stops + removes services and the binary, restores the original sshd_config,
# removes the authbind/ufw rules — but KEEPS your data (DB, evidence, config):
sudo python3 scripts/shardlure.py uninstall
# Same, but ALSO delete /var/lib/shardlure (Cowrie clone, captured payloads,
# the SQLite intel DB) and remove the 'cowrie' system user:
sudo python3 scripts/shardlure.py uninstall --purgeWhat it does, in order (SSH first, on purpose):
- Restore SSH — copy
/etc/ssh/sshd_config.shardlure-bakback over/etc/ssh/sshd_config, delete the99-shardlure-admin.confdrop-in, runsshd -t, and only then reload. If the validated config fails the test it aborts the reload rather than risk your running sshd. - Remove services — stop, disable, and delete
cowrie.serviceandshardlure-live.service;daemon-reload. - Remove the binary —
/usr/local/bin/shardlure. - Remove the authbind byport file (only created when the honeypot port is < 1024).
- Firewall — delete the honeypot and dashboard
ufw allowrules. The admin SSH rule is left in place on purpose so you can't lock yourself out. - Data — kept by default; deleted only with
--purge(along with thecowrieuser).
Custom ports? The firewall/authbind cleanup needs to know which ports you used. If you didn't install on the defaults (22/2222/8080), pass them via environment variables so the right rules are removed (SSH restore + service + binary removal are port-independent and always correct regardless):
sudo SHARDLURE_HONEYPOT_PORT=22 SHARDLURE_ADMIN_PORT=2222 SHARDLURE_DASH_PORT=8080 \ python3 scripts/shardlure.py uninstall --purge
After it finishes, verify SSH from a second terminal on the restored port (by default 22 once Cowrie is gone) before closing your session.
Do this if the script isn't available or you want to inspect each step. Run as root.
# 1. Restore real SSH FIRST (so you can't get locked out).
sudo cp /etc/ssh/sshd_config.shardlure-bak /etc/ssh/sshd_config # if the backup exists
sudo rm -f /etc/ssh/sshd_config.d/99-shardlure-admin.conf
sudo sshd -t && sudo systemctl reload ssh # only reload if -t passes
# No backup? Re-enable the original Port line by hand in /etc/ssh/sshd_config,
# then `sudo sshd -t && sudo systemctl reload ssh`. VERIFY in a 2nd terminal.
# 2. Stop, disable, and remove the systemd services.
sudo systemctl stop shardlure-live.service cowrie.service
sudo systemctl disable shardlure-live.service cowrie.service
sudo rm -f /etc/systemd/system/shardlure-live.service /etc/systemd/system/cowrie.service
sudo systemctl daemon-reload
sudo systemctl reset-failed
# 3. Remove the binary.
sudo rm -f /usr/local/bin/shardlure
# 4. Remove the authbind byport file (only if your honeypot port was < 1024).
sudo rm -f /etc/authbind/byport/22 # use your honeypot port
# 5. Revert firewall rules (skip the admin SSH port!).
sudo ufw delete allow 22/tcp # honeypot port
sudo ufw delete allow 8080/tcp # dashboard port
# Leave the admin SSH port (e.g. 2222) allowed until you've confirmed SSH on 22.
# 6. Delete Cowrie, captured intel, and the cowrie user (DESTRUCTIVE — skip to keep data).
sudo rm -rf /var/lib/shardlure
sudo userdel -r cowrie
apt/dnf/pacmanpackages installed as dependencies (git, python venv, authbind, Go, build tools) are not removed by either method — they're shared system packages and removing them could break unrelated things. Remove them yourself only if you're sure nothing else needs them.
- Installed OS packages (see above).
- Any extra
ufwrules you added by hand. - Without
--purge:/var/lib/shardlureand thecowrieuser — your captured intel survives so you can archive it. Treat the SQLite DB like a loaded gun (it holds attacker-supplied passwords) andshred/securely delete it when done.
- Cowrie JSON ingest (HASSH, commands, payloads)
- Stealth persona and bait file planting
- Live journal tail and Cowrie append ingest
- Configurable dashboard home point
- Incremental Cowrie ingest (offset + inode tracking, no more O(file) per tick)
- Idempotent journal append (dedup against existing events)
- Graceful shutdown on SIGINT/SIGTERM (so Ctrl-C is no longer a war crime)
- DB chmod 0600 + sshd-config auto-rollback on failed reload
- MalwareBazaar payload sharing (CLI + one-click dashboard upload)
- Dragon theme — full SOC dashboard redesign with sidebar nav
- Dashboard widgets: threat gauge, geography, credentials, brute-force radar, live timeline
- Persistent geo cache (SQLite-backed, survives restarts)
- MalwareBazaar dashboard widget (stats + upload history + family classification)
- Incremental cowrie actor rebuild (per-tick cost is O(touched), not O(all history))
- Seven IP-reputation providers (AbuseIPDB, VirusTotal, GreyNoise, Shodan, OTX, IPQualityScore, IPinfo)
- Dashboard auth token forwarded on every request + cross-page navigation
- One-command uninstall (
uninstall [--purge]) with SSH-restore-first safety - Full-window analytics — MITRE/TTP/IOC/graph/deobf cover the whole selected window (not a recent sample), with capped widgets disclosing "N of M"
- GeoLite2 MMDB enrichment (escape the ip-api.com rate limits arc)
- Real-time WebSocket feed (current dashboard polls every 5s, which is fine but mid)
Q: Does this make my server safer? A: Marginally. It moves real SSH to a private port (good) and runs a fake one (interesting). The main value is intel: you learn what botnets are doing to boxes that look like yours.
Q: Will I get cool maps? A: Yes. There is a globe. It rotates. Blood-red arcs converge on your home point like you're in a 2007 hacker movie. The intel console has a threat gauge, brute-force radar, and attack geography panel.
Q: Is this production-ready? A: It's "single-VPS, one-operator, runs-on-my-laptop" ready. If you want a fleet, you'll want to front the SQLite with something less single-writer. PRs welcome.
Q: Why Go + Python? A: Cowrie is Python. The ingest, classifier, and dashboard are Go because parsing a million journal lines in Python on a 1-vCPU droplet is suffering. The Python is only the installer.
Q: The bait files. Are they convincing? A: They're convincing to bots. A human auditing them for 30 seconds would clock the fake Stripe keys. That's fine — bots are the customer here.
MIT - see LICENSE.
| Repo | What it does |
|---|---|
| ShardLure | SSH honeypot + threat-intel dashboard |
| ShardC2 | Red-team C2 framework in Go |
| ShardFlow | Layer-2 LAN workbench (ARP, drop, throttle) |
| ShardShell | PHP post-exploitation shell |
| ShardPass | Minimal TOTP authenticator (Chrome MV3) |
| ShardPet | Pixel-Pokémon browser extension |