Plugins¶
Plugins are what drive service emulation and determine the level of interaction possible with a given service. Plugins can either be based on TCP or UDP protocols. Plugins also determine what events and data are logged.
Events¶
For TCP plugins, all connection (CONNECT) events, data received (RX) events, and data transmitted (TX) events are logged.
For UDP plugins, all data received (RX) events and data transmitted (TX) events are logged. With UDP there are no CONNECT events.
Either TCP or UDP plugins may also log information (INFO) events. These events contain specific data of interest, typically extracted from an RX event. For example, usernames and passwords may be captured in an INFO event.
A few INFO events have the same shape in every plugin that emits them, so one query selects them across plugins. Each carries event_type and schema_version (currently 1) next to the fields below. A list with nothing in it is omitted from the event.
| Event | Fields | Description |
|---|---|---|
cryptomining_indicators |
pools, wallets (each address and chain: bitcoin, ethereum, monero, tron or unknown), miners, source_field, truncated |
Mining pool endpoints, wallet addresses and miner program names found in attacker-supplied text. source_field names where they were found. truncated is true only when the 50 ms scan budget expires; input is also cut at 65,536 characters without setting it. |
dropper_indicators |
tools, urls, hosts, filenames, source_field, truncated |
A download tool together with a URL, or a /dev/tcp/ or /dev/udp/ host, found in attacker-supplied text. |
info_event_cap_reached |
none | Marker sent once per connection when the per-connection INFO event limit is hit. Only Docker_tcp, Electrum_tcp, BitcoinP2P_tcp, CgminerAPI_tcp, ClaymoreAPI_tcp and XMRigAPI_tcp emit it. |
crypto_recon_command |
command, pattern, truncated |
A shell command that searches for wallets or keys. Emitted by SSH_tcp and Telnet_tcp. |
crypto_lure_access |
path, command, lure_kind, truncated |
A shell command that reads, copies, archives, encodes or sends a crypto lure file. Emitted by SSH_tcp and Telnet_tcp while a crypto node persona is active. |
crypto_lure_request |
target, path, lure_kind |
An HTTP request for a crypto lure file such as /.env or /wallet.dat. Emitted by HTTP_tcp. |
jsonrpc_probe |
path, methods, batch_size |
A JSON-RPC request to a well-known RPC path. Emitted by HTTP_tcp. |
An indicator event larger than 64 KiB is shortened and carries payload_truncated: true. Indicator events exist to carry attacker infrastructure, so only the agent's own IP address is scrubbed from them. Mining pool hosts, dropper URLs, IP addresses and wallet addresses given by the attacker are preserved.
Plugin Configuration Files¶
Many plugins support an optional JSON configuration file that customizes the plugin's behavior. To use one, reference the file from the service definition with the config key:
config = /etc/honeydb/example.json
All plugin configuration keys are optional. If no config key is present, the plugin runs with its defaults. If the file cannot be read or parsed, the agent logs a warning and the plugin runs with its defaults.
The agent packages install only agent.conf and services.conf; no per-service JSON file is installed. Create the file yourself.
Several newer plugins also take options directly in their service section of services.conf, such as persona, transport, token_policy and auth_mode. These are documented in each plugin's Config table under Service section keys. Where a setting can be given in both places, the service section wins (for example persona, max_body and auth_mode).
For the blockchain and cryptocurrency plugins, the JSON file is a partial document. It contains only the keys you want to change, and it is merged over the plugin's built-in document rather than replacing it. The one exception is HTTP_tcp, where a custom configuration replaces the builtin server.json. How far the merge goes differs by plugin:
| Behavior | Plugins | Effect of the keys in your file |
|---|---|---|
| Deep merge | BeaconAPI_tcp, IPFS_tcp, LND_tcp, SubstrateRPC_tcp |
Objects are merged key by key at any depth, so you can change one nested value. Lists and scalar values replace the built-in value. |
| Top-level replace | BitcoinP2P_tcp, CgminerAPI_tcp, ClaymoreAPI_tcp, Electrum_tcp, XMRigAPI_tcp |
Each top-level key you give replaces the built-in value whole. A limits object you give replaces the built-in limits; limit keys you leave out use the plugin's default. personas is merged by name, and a persona you name replaces that persona whole. BitcoinP2P_tcp requires every key of a persona, so a partial one is rejected and the built-in fallback is used. CgminerAPI_tcp and ClaymoreAPI_tcp accept a partial persona and fill the gaps from code defaults, not from the bundled persona of that name. |
| Named keys only | EVMRPC_tcp, SolanaRPC_tcp |
limits is merged key by key and seed is replaced. personas is merged by name, and a persona you name replaces that persona whole. A top-level persona key is honoured (the service section's persona wins); other top-level keys are ignored. |
| Named keys only | BitcoinRPC_tcp, MoneroRPC_tcp |
limits (and, for MoneroRPC_tcp, chain) is merged key by key; seed and weak_credentials are replaced. personas is merged by name, and the keys you give for a persona replace the same keys of that persona while the rest are kept. A top-level persona key is honoured (the service section's persona wins); other top-level keys are ignored. |
| Per-key replace | Docker_tcp |
The plugin has built-in defaults and no persona document. Each recognized key you give replaces its default. An invalid value is logged and the default is used. |
"Merged over the built-in document" does not mean a bad value is repaired. In SubstrateRPC_tcp, one unknown top-level key makes the whole document fall back to the built-in fallback persona. In IPFS_tcp, any type error replaces the whole merged document with the fallback. In BeaconAPI_tcp, invalid chain values are silently dropped to their defaults. Persona names are lower-cased when the service option is read but looked up case-sensitively in the JSON file, so use lowercase persona names.
Plugins List¶
The blockchain and cryptocurrency plugins emulate nodes, wallets, miners and related services that attackers scan the Internet for: BeaconAPI_tcp, BitcoinP2P_tcp, BitcoinRPC_tcp, CgminerAPI_tcp, ClaymoreAPI_tcp, Docker_tcp, Electrum_tcp, EVMRPC_tcp, IPFS_tcp, LND_tcp, MoneroRPC_tcp, SolanaRPC_tcp, SubstrateRPC_tcp and XMRigAPI_tcp. All of them are disabled (enabled = No) in the services.conf that ships with the agent, and they are intended for agents with an Internet-facing address. They require HoneyDB Agent 2.10.0 or later; an older agent does not know these plugin names. The SSH_tcp, Telnet_tcp and HTTP_tcp plugins also gained crypto-related options in 2.10.0, described in their entries below.
BeaconAPI_tcp¶
A low interaction TCP service that emulates the HTTP APIs of an Ethereum staking operator: a consensus-layer beacon node (Beacon API), a validator client's Keymanager API, a Web3Signer remote signer, and an execution client's JWT-authenticated Engine API. One persona is served per service section, and responses reproduce the status line, header order and JSON key order of captured client builds. The synthesized mainnet chain advances with the wall clock and is always synced. Keystore imports and deletes, fee-recipient and withdrawal changes, voluntary exits, signing requests, pool submissions, bearer tokens and JWTs are captured and answered with fabricated results. Nothing is signed, executed or relayed, no outbound connection is made, and per-source session state is kept in memory only.
Example service configuration:
[BeaconAPI]
plugin = BeaconAPI_tcp
low_port = tcp:5052
port = tcp:5052
description = Ethereum beacon node REST API (Lighthouse).
enabled = Yes
persona = beacon
flavor = lighthouse
Default sections (all ship with enabled = No):
| Section | Port | Persona | Notes |
|---|---|---|---|
BeaconAPI |
5052 | beacon |
flavor = lighthouse. |
BeaconAPI.keymanager |
5062 | keymanager |
flavor = lighthouse, token_policy = accept_any. |
BeaconAPI.web3signer |
9000 | web3signer |
Web3Signer in eth2 mode. |
BeaconAPI.engine |
8551 | engine |
Execution client authrpc, JWT. |
Leave tls = No: the real services are plain HTTP, usually behind a reverse proxy. Each section runs one persona, so enable only the sections you want exposed.
Config¶
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | beacon |
API to emulate: beacon, keymanager, web3signer or engine. An invalid value falls back to beacon and logs a warning. |
flavor |
string | lighthouse |
Client to impersonate. beacon accepts lighthouse, prysm, teku and nimbus; keymanager accepts lighthouse, prysm and teku. Ignored by web3signer and engine. An invalid value falls back to lighthouse. |
token_policy |
string | accept_any |
Keymanager bearer-token handling: accept_any accepts any non-empty bearer token, so imports, deletes and fee-recipient changes are captured; reject refuses every token with the client's 401 or 403. Only the keymanager persona uses it. |
lure_validator_count |
int | 8 |
Number of synthetic active validators, 1 to 64. A value outside the range is replaced by the default, not clamped. |
head_lag_slots |
int | 0 |
How many slots the synthesized head sits behind the wall-clock slot, 0 to 32. A value outside the range is replaced by the default. |
max_body |
int | 1048576 |
Request body cap in bytes, 1024 to 1073741824. Overrides limits.max_request_bytes from the JSON file. A value outside the range is replaced by the cap already in effect. |
config |
string | none | Path to a JSON file merged over the built-in document. |
Option values for persona, flavor and token_policy are case-insensitive.
JSON file keys:
The file is a partial document containing only the keys to change. It is deep-merged over the built-in document: objects merge key by key and any other value replaces the built-in one. The built-in document is part of the agent; no JSON file ships in packages.
| Key | Type | Default | Description |
|---|---|---|---|
seed |
string | null |
A non-empty string pins the identity seed that the synthetic keys and identities derive from. |
limits |
object | see below | Transport, state and event bounds. Each value must be a positive number; invalid values keep the default and unknown names are ignored. |
chain, flavors, peer_agents, public_shapes |
object or list | built-in | Captured chain parameters and per-client response templates. They can be overridden, but are not intended to be tuned. |
Keys in limits (name and default):
| Group | Keys |
|---|---|
| Request transport | max_request_bytes 1048576, max_header_bytes 65536, max_chunk_line_bytes 1024, idle_timeout_seconds 30, request_deadline_seconds 60, max_requests_per_connection 1000, max_requests_per_read 16, max_unflushed_bytes 4194304, max_tx_event_bytes 65536, max_stream_seconds 300 |
| Connections and state | max_streams_per_source 2, max_connections 512, max_connections_per_source 32, max_sessions 4096, max_engaged_sessions 1024, session_ttl_seconds 86400, max_session_keys 64, max_list_items 256, max_validators_per_query 64, max_root_lookback_slots 8192 |
| Large responses | large_responses_per_source_per_minute 4, large_responses_per_minute 20 |
| Events | max_routine_events_per_connection 64, max_high_value_events_per_connection 256, max_capture_events_per_batch 32, max_rpc_call_events_per_batch 16, max_rpc_call_summaries 64, high_value_events_per_source_per_minute 120, routine_events_per_source_per_minute 240, high_value_events_per_second 40, routine_events_per_second 80, max_rate_entries 4096, credential_events_per_source_per_minute 30, credential_events_per_second 50, credential_repeat_window_seconds 60, info_field_max_chars 2048 |
max_streams_per_source and max_stream_seconds bound the Server-Sent Events streams that the beacon persona serves. large_response_bytes and max_secret_chars are accepted but not read; secrets are always truncated at 1024 characters.
INFO Events¶
Every event carries event_type, schema_version, persona and flavor. Tokens, passwords and signatures are recorded in full, bounded to 1024 characters. Attacker infrastructure such as fee-recipient addresses and remote-signer URLs is preserved.
| Event | Description |
|---|---|
api_request |
Per-connection summary of each method path requested: query, user agent, Accept, whether the route is known, a map of status codes to counts, and a request count. |
bearer_token_presented |
A bearer token sent to the Keymanager API, with path, method, whether it was accepted, the policy, and a repeat count. |
engine_auth_attempt |
A JWT presented to the Engine API: the token, decoded header and claims, the JSON-RPC method, and the outcome. No token can verify. |
signing_request |
A Web3Signer signing request: public key, signing type, slot or epoch, signing root, fork version, and whether the key is known. |
keystore_import_attempt |
A keystore import: public keys, passwords, slashing-protection data and per-key statuses. |
keystore_delete_attempt |
A keystore or remote-key delete: kind, public keys and statuses. |
remote_key_import_attempt |
A remote-signer key import: public keys and the attacker's signer URLs. |
fee_recipient_change_attempt |
A fee-recipient change by Keymanager, proposer preparation or validator registration: validator, address, gas limit and signature. |
validator_setting_change_attempt |
A gas limit or graffiti change for a validator. |
voluntary_exit_attempt |
A voluntary exit through the pool or the Keymanager: validator, epoch and signature. |
withdrawal_change_attempt |
A BLS-to-execution withdrawal credential change: validator, source key, target address and signature. |
pool_submission |
A block, attestation or other pool submission: kind, count, slots, validator indices and a truncated body. |
events_dropped |
Marker emitted when volume limits dropped events: scope, event class and count dropped. |
cve_attempt |
Canonical CVE event for a detected exploit attempt (see below). |
CVEs Detected¶
- CVE-2021-44228 (Log4Shell,
log4shell_jndi): a JNDI lookup in the request target, any header value or the body. Teku and Web3Signer are Java products.
BitcoinP2P_tcp¶
A low to medium interaction TCP service that emulates a Bitcoin peer-to-peer node. It completes the version/verack handshake as Bitcoin Core, Litecoin Core or Dogecoin Core and records what the peer reveals: its version message, announced and pushed transactions and blocks, address gossip and requests. After the handshake it answers ping, answers getheaders with an empty headers message, answers getdata with notfound for the types in the persona's notfound_types, and sends getheaders and getdata requests of its own (bounded). On the dogecoin persona it also sends reject for an obsolete or duplicate version, and mempool and filter* commands close the connection when the persona's bloom_disconnect is true. It serves no blocks, headers, transactions or addresses, never answers getaddr, never relays anything and makes no outbound connections. BIP324 version 2 encrypted transport is not implemented: a version 2 attempt is recorded and the connection is closed without a reply.
Example service configuration:
[BitcoinP2P]
plugin = BitcoinP2P_tcp
low_port = tcp:8333
port = tcp:8333
description = Bitcoin Core P2P node.
enabled = Yes
persona = bitcoin
Default sections:
| Section | Port | Persona | Notes |
|---|---|---|---|
BitcoinP2P |
8333 | bitcoin |
Bitcoin Core 31.1.0 (/Satoshi:31.1.0/, protocol 70016). |
BitcoinP2P.litecoin |
9333 | litecoin |
Litecoin Core 0.21.5.8 (/LitecoinCore:0.21.5.8/, protocol 70017). |
BitcoinP2P.dogecoin |
22556 | dogecoin |
Dogecoin Core 1.14.9 (/Shibetoshi:1.14.9/, protocol 70015). |
The three default ports do not conflict.
Config¶
Service section keys
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | default_persona from the JSON document ("bitcoin") |
One of bitcoin, litecoin, dogecoin (lower-cased), or a persona you add in the JSON file. An unknown or invalid persona logs a warning and falls back to default_persona, then to a built-in Bitcoin Core persona; the plugin still starts. |
config |
path | none | Optional JSON file of overrides. |
The JSON file is a partial document containing only the keys to change, merged over the built-in document. The merge is shallow at the top level: a top-level key that you supply replaces the built-in value as a whole, except that personas is merged by name (a persona with the same name as a built-in one is replaced whole). A supplied limits object therefore replaces the built-in limits object; limits you omit take the built-in default value. No file ships in packages.
JSON file keys
| Key | Type | Default | Description |
|---|---|---|---|
default_persona |
string | "bitcoin" |
Persona used when the service section sets none, and the fallback for an unusable persona. |
limits |
object | see below | Resource and event limits. An invalid value logs a warning and keeps the default; values are truncated to integers. |
personas |
object | bitcoin, litecoin, dogecoin |
Persona name to persona definition. |
A persona definition has these keys: port, magic (4 bytes, hex), protocol_version, min_peer_version, user_agent, services, first_bytes (v2_sniff or legacy), max_message_bytes, wtxidrelay, addrv2, post_verack, sendheaders_after_headers, getheaders_version, feefilter, bloom_disconnect, reject_messages, reject_obsolete_text, notfound_types, auxpow, mweb, cve_oversized_header, genesis, block1, anchor (height, time, hash), block_interval_seconds, max_money and address (p2pkh, p2sh, hrp). Every key is mandatory: a partial persona is rejected and the built-in fallback is used. Persona names are matched in lower case, so use a lower-case name for a persona you add.
Limits (all under limits)
| Key | Default | Description |
|---|---|---|
handshake_timeout_seconds |
60 |
Close a peer that has not completed the handshake. |
idle_timeout_seconds |
600 |
Close a peer that sends nothing. |
max_connection_seconds |
3600 |
Maximum connection lifetime. |
ping_interval_seconds |
120 |
Interval of the plugin's own pings. |
max_connections |
64 |
Concurrent connections. |
max_connections_per_source |
4 |
Concurrent connections per source address. |
max_buffered_payload_bytes |
1048576 |
Largest message held in memory; larger ones are streamed and discarded. |
max_parsed_bytes |
8388608 |
tx and block bytes parsed per connection. |
max_block_txs |
4000 |
Blocks with more transactions are summarized. |
max_messages |
20000 |
Messages per connection before it is closed. |
max_info_events |
64 |
INFO events per connection (the peer_request summary is additional). |
info_events_per_source_per_minute |
120 |
INFO events per source per minute. |
summaries_per_source_per_minute |
30 |
peer_request summaries per source per minute. |
info_field_max_chars |
2048 |
Cap on bounded text fields in INFO events. |
max_rx_log_events |
512 |
RX log events per connection. |
max_tx_log_events |
512 |
TX log events per connection. |
max_rx_log_bytes |
65536 |
Bytes of one message kept in the RX log. |
max_txids |
32 |
Transaction IDs listed in one tx_announce. |
max_outputs |
16 |
Outputs listed in one tx_relay. |
max_addr_sample |
16 |
Entries sampled in one addr_gossip. |
max_hashes |
8 |
Hashes listed in one block_announce. |
max_getdata_echo |
64 |
Entries echoed in one notfound. |
max_getdata_request |
50 |
Transactions requested per getdata. |
max_tx_requests |
200 |
Transactions requested per connection. |
max_getheaders_replies |
8 |
getheaders sent in reply to block announcements. |
max_headers |
2000 |
Headers examined in one headers message. |
max_rate_entries |
4096 |
Source addresses tracked by rate limits. |
tx_request_delay_seconds |
2 |
Delay before requesting announced transactions. |
send_getheaders |
true |
Send getheaders after the handshake. |
request_announced_tx |
true |
Request announced transactions. |
INFO Events¶
Every event carries event_type and schema_version. Hashes are in display (explorer) byte order. Only the agent's own address is scrubbed; gossiped peers and the peer's own addr_from are kept as attacker infrastructure. The agent's address is zeroed in logged frames, so a logged frame's checksum may not match.
| Event | Description |
|---|---|
peer_version |
The peer's version message: protocol version, services, timestamp, user agent, start height, relay flag, nonce and addr_from when set. |
tx_announce |
Transactions announced by inv: count and the first 32 hashes with their type. |
block_announce |
Blocks announced by inv, headers or block: source, count, the first 8 hashes and, for a full block, size, previous hash, merkle root, time, bits, version and transaction count. |
tx_relay |
A pushed transaction: txid, wtxid, size, version, locktime, input and output counts, the first 16 outputs (value, script type, address), total output value and requested (whether this agent asked for it). An MWEB transaction emits a summary-only event with size and mweb. |
addr_gossip |
Address gossip: format (addr or addrv2), count and a sample of the first 16 entries. |
peer_request |
One per connection at close: transport (v1, v1_foreign_magic, v2_attempt or unknown), command counts, whether the handshake completed, duration and error counters. |
v2_transport_attempt |
The peer tried the BIP324 encrypted transport. No fields. |
unknown_command |
A message with an unrecognized command: command name and length. |
info_event_cap_reached |
Emitted once per connection when the per-connection INFO event cap is hit. |
cve_attempt |
A known exploit pattern was seen, at most once per CVE per connection. |
CVEs Detected¶
Checks run only on exactly parsed structures.
| CVE | short_name |
Trigger |
|---|---|---|
| CVE-2010-5139 | btc_value_overflow_tx |
An output value below zero or above the maximum supply, or outputs summing above it. |
| CVE-2012-2459 | btc_duplicate_tx_merkle_block |
A block whose transaction list repeats a txid. |
| CVE-2015-3641 | btc_oversized_message_header |
A message header declaring a payload above 4 MB and within 32 MiB (disabled on the litecoin persona; also requires the persona's max_message_bytes to be 4000000). |
| CVE-2018-17144 | btc_duplicate_input_block |
A block transaction spending the same outpoint twice. |
| CVE-2024-52912 | btc_version_timestamp_overflow |
A version timestamp 2^62 seconds or more from now. |
| CVE-2024-52915 | btc_inv_block_flood |
One inv with more than 500 block entries. |
| CVE-2024-52916 | btc_genesis_parent_headers |
A header chained onto genesis that is not the real block 1. |
| CVE-2024-52920 | btc_getdata_unknown_invtype |
A getdata entry of an unknown inventory type. |
| CVE-2024-52921 | btc_mutated_block |
A block whose recomputed merkle root differs from its header's, reported only when no txid repeats (CVE-2012-2459 takes precedence). |
BitcoinRPC_tcp¶
A medium interaction TCP service that emulates the JSON-RPC interface of bitcoind and five forks (Litecoin Core, Dogecoin Core, Dash Core, Bitcoin Cash Node and zcashd) over HTTP. An attacker sees a synced node with an encrypted lure wallet that holds a seeded balance. The plugin keeps a wallet session per source IP address and records every credential pair tried, wallet passphrase guesses, key export and import attempts, and the destination of attempted sends. A send builds a fabricated transaction that is never broadcast; nothing is relayed, no outbound connection is made, and no usable private key exists for any lure address. One section emulates one coin.
Example service configuration:
[BitcoinRPC]
plugin = BitcoinRPC_tcp
low_port = tcp:8332
port = tcp:8332
description = Bitcoin Core JSON-RPC node.
enabled = Yes
persona = bitcoin
Default sections:
| Section | Port | Persona | Notes |
|---|---|---|---|
BitcoinRPC |
8332 | bitcoin |
Bitcoin Core 31.1.0, descriptor wallet. |
BitcoinRPC.litecoin |
9332 | litecoin |
Litecoin Core 0.21.5.8. |
BitcoinRPC.dogecoin |
22555 | dogecoin |
Dogecoin Core 1.14.9. |
BitcoinRPC.dash |
9998 | dash |
Dash Core 23.1.8. |
BitcoinRPC.bitcoincash |
8332 | bitcoincash |
Bitcoin Cash Node 29.2.0. Uses the same port as BitcoinRPC: change one of the two ports if both are enabled. |
BitcoinRPC.zcash |
8232 | zcash |
zcashd 6.20.0, transparent wallet. |
Config¶
Service section keys
All keys are optional. Values are trimmed and choices are case-insensitive. An invalid value logs a warning and the default is used.
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | "bitcoin" |
One of bitcoin, litecoin, dogecoin, dash, bitcoincash, zcash. Precedence: this key, then persona in the JSON file, then the built-in document, then bitcoin. |
auth_policy |
string | "list" |
list, after_n_attempts or never. See below. |
auth_after_n |
int | 3 |
Failed logins from a source before after_n_attempts starts accepting it. Minimum 1. |
use_lure_credentials |
bool | yes |
Accept this agent's lure bitcoin.conf rpcuser and rpcpassword pair, the same pair shown by the SSH, Telnet and HTTP lures. Accepts yes/no, true/false, 1/0, on/off. |
passphrase_policy |
string | "after_n_attempts" |
after_n_attempts or always_fail. |
passphrase_after_n |
int | 3 |
Wallet passphrase guesses per source that are rejected (error -14) before the wallet unlocks. Minimum 0. |
config |
path | none | Optional JSON file of overrides. |
auth_policy values:
list: accepts the pairs in the built-inweak_credentialslist and, unlessuse_lure_credentials = no, the lure pair.after_n_attempts: still accepts the lure pair; once a source has failedauth_after_nlogins, every later request from it that carries credentials is accepted. The weak-credential list is not consulted under this policy.never: nothing logs in, not even the lure pair.
A refused login gets 401 after a 250 ms delay. A request with no credentials gets an immediate 401 and is not counted as a failure. With passphrase_policy = after_n_attempts, the passphrase that unlocked the wallet becomes that source's wallet passphrase; always_fail never unlocks.
JSON file keys
The JSON file is a partial document containing only the keys to change; it is merged over the built-in document, which is generated and not meant to be edited. The merge is by key: limits is merged key by key, seed and weak_credentials are replaced as a whole, and for each entry in personas the supplied fields replace the same fields of the built-in persona of that name (an unknown name adds a new persona). No file ships in packages.
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | "bitcoin" |
Persona to use when the service section sets none. |
seed |
string | "" |
Seed for wallets and chain state. An empty value derives it from the host identity; set it to pin state. |
limits |
object | see below | Resource and event limits. Invalid values log a warning and keep the default. |
weak_credentials |
list | 35 built-in pairs | List of [user, password] pairs accepted under auth_policy = list. A supplied list replaces the built-in one. |
personas |
object | built-in personas | Persona name to field overrides. |
Limits (all under limits)
| Key | Default | Description |
|---|---|---|
max_request_bytes |
1048576 |
Request body cap. |
max_header_bytes |
65536 |
Header block cap. |
max_chunk_line_bytes |
1024 |
Chunk size line cap. |
idle_timeout_seconds |
30 |
Close after this long with no data. |
request_deadline_seconds |
60 |
Whole-request deadline. |
max_requests_per_connection |
1000 |
Keep-alive request cap. |
max_requests_per_read |
16 |
Pipelined requests handled per read. |
max_unflushed_bytes |
25165824 |
Abort a client that does not read its responses. |
max_tx_event_bytes |
65536 |
Cap on transaction event data. |
max_batch |
100 |
Calls per batch; a larger batch gets one -32600. |
capture_scan_limit |
20 |
Members of an oversized batch still captured. |
max_response_bytes |
16777216 |
Encoded reply budget per request. |
large_response_bytes |
65536 |
Accepted and validated, but not read by any code path at this release. |
large_responses_per_source_per_minute |
4 |
Large replies (verbose block renders) per source per minute. |
large_responses_per_minute |
20 |
Large replies per minute, plugin-wide. |
auth_failure_delay_ms |
250 |
Delay before a refused login is answered. |
auth_repeat_window_seconds |
600 |
Window in which a repeated credential pair is counted rather than re-emitted. |
auth_events_per_source_per_minute |
300 |
rpc_auth_attempt budget per source per minute. |
auth_events_per_second |
100 |
rpc_auth_attempt budget per second, plugin-wide. |
max_sessions |
16384 |
Per-source wallet sessions kept. |
max_engaged_sessions |
2048 |
Sessions protected from eviction once a source has interacted with the wallet. |
session_ttl_seconds |
86400 |
Idle lifetime of a session. |
max_session_sends |
200 |
Sends recorded per session. |
max_session_wallets |
16 |
Wallets a session may create. |
max_raw_tx_bytes |
400000 |
Largest raw transaction decoded. |
max_tx_inputs |
2000 |
Inputs decoded per transaction. |
max_tx_outputs |
3000 |
Outputs decoded per transaction. |
block_memo_heights |
32 |
Recently rendered block heights memoized by the chain emulation. |
max_rate_entries |
8192 |
Source addresses tracked by rate limits. |
info_field_max_chars |
8192 |
Cap on bounded text fields in INFO events. |
max_rpc_call_events_per_batch |
32 |
rpc_call events per request body. |
max_capture_events_per_batch |
16 |
High-value events per request body. |
max_rpc_call_summaries |
32 |
rpc_call summaries emitted at disconnect. |
max_routine_events_per_connection |
200 |
Routine events per connection. |
max_high_value_events_per_connection |
64 |
High-value events per connection. |
routine_events_per_source_per_minute |
120 |
Routine events per source address per minute. |
high_value_events_per_source_per_minute |
60 |
High-value events per source address per minute. |
routine_events_per_second |
50 |
Routine events per second, plugin-wide. |
high_value_events_per_second |
20 |
High-value events per second, plugin-wide. |
INFO Events¶
Every event carries event_type, schema_version and coin (the persona name). Only the agent's own address is scrubbed; attacker IPs and destination addresses are preserved. Credentials, passphrases and attacker keys are logged in full, with a length cap. Amounts are decimal strings such as "0.10000000".
| Event | Description |
|---|---|
client_fingerprint |
Each new client fingerprint: user agent, path, authorization scheme, JSON-RPC version and content type. |
rpc_auth_attempt |
The first occurrence of each distinct credential pair per source (repeats are counted): scheme, username, password, missing and malformed flags, whether it was accepted, credential_source (lure, weak_list, policy, rejected or missing), path and repeat_count. A refused login also carries methods (up to 20 distinct) and call_count, the only record of calls sent before login. |
rpc_call |
Method calls of authenticated requests: method, parameter count, batch size, count and whether the method is known. |
wallet_passphrase_attempt |
walletpassphrase, walletpassphrasechange and encryptwallet: wallet, passphrase, new passphrase, timeout, attempt number and whether it was accepted. |
send_attempt |
sendtoaddress and sendmany: wallet, destinations (address, amount, validity, script type), comment, fee rate, outcome and error_code. |
raw_transaction |
sendrawtransaction, signrawtransaction* and decoderawtransaction: decoded outputs, txid, input count, whether it spends a lure output, and the raw transaction. |
key_export_attempt |
dumpprivkey, dumpwallet, listdescriptors and backupwallet: wallet, address, filename, path_traversal, whether the wallet was unlocked and, for listdescriptors, a private flag. |
key_import_attempt |
importprivkey, importdescriptors and keys passed to signrawtransaction: the key or descriptors, label and rescan flag. |
wallet_path_probe |
createwallet, loadwallet and unloadwallet: wallet name, path_traversal and passphrase. |
lure_address_query |
getaddressinfo, validateaddress, gettransaction or getrawtransaction on a lure address or the lure funding transaction. |
address_query |
The same calls on any other address or transaction. |
events_dropped |
A volume budget was exceeded: scope, class (routine, high_value or credential) and count. |
State is per source IP address and in memory: sessions expire after 24 hours idle and are lost on restart, and attackers never see each other's changes. Sends stay at zero confirmations and are never mined.
CgminerAPI_tcp¶
A low interaction TCP service that emulates the management API of a cgminer-family mining rig (Antminer, Avalon and Whatsminer). It serves one request per connection, as a text command|parameter line or a JSON object, replies with a NUL-terminated message and closes. Read commands return a plausible rig whose hashrate, temperatures, fans and uptime are seeded per agent. Privileged commands (pool changes, restarts, configuration) are accepted, though pool commands return error statuses for bad input (52 missing details, 53 invalid details, 8 no pools, 25 missing id, 26 invalid id) and setconfig answers 83 Unknown config. Pool state is remembered per source IP so a hijacked pool appears in the next pools reply. Commands, pool URLs, users, passwords, Whatsminer tokens and indicators are captured. Nothing is executed, no pool is contacted, and state is kept in memory only and lost on restart.
Example service configuration:
[CgminerAPI]
plugin = CgminerAPI_tcp
low_port = tcp:4028
port = tcp:4028
description = cgminer/bmminer mining rig management API.
enabled = Yes
persona = antminer_s9
The shipped file defines one section and ships it with enabled = No. To present a second rig on another port, add a section with the same plugin and a different persona, for example whatsminer_m30s on tcp:4029.
Personas:
| Persona | Emulates |
|---|---|
antminer_s9 |
Antminer S9 (bmminer), 3 boards of 63 chips, 13500 GH/s nominal. Default. |
antminer_s19j_pro |
Antminer S19j Pro, 3 boards of 114 chips, 104000 GH/s nominal. |
avalon_a10 |
Avalon A10 (CGMiner 4.11.1, API 3.7), MH-based rates. |
whatsminer_m30s |
Whatsminer M30S+ (btminer), JSON {"cmd": ...} commands and the encrypted write channel. |
Config¶
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | antminer_s9 |
Rig to emulate: any persona name in the loaded document, case-insensitive. An unknown name falls back to the default persona and logs a warning. |
config |
string | none | Path to a JSON file merged over the built-in document. |
JSON file keys:
The file is a partial document containing only the keys to change, merged over the document built into the agent. The merge is shallow: a top-level key you supply replaces the built-in value, except that personas is merged by persona name, so a persona you define replaces the built-in persona of the same name as a whole. A supplied limits object replaces the built-in one, and any key it omits takes its default. No JSON file ships in packages.
| Key | Type | Default | Description |
|---|---|---|---|
default_persona |
string | antminer_s9 |
Persona used when the section does not set one. |
seed |
string | "" |
Seed for the generated telemetry. Empty derives it from the agent identity, so it is stable per agent. |
personas |
object | four built-in personas | Rig definitions keyed by persona name. Persona fields include family (cgminer or whatsminer), miner_key, version, api_version, miner_version, type, description, devices, chips_per_device, nominal_ghs, frequency, fans, rate_unit and default_pools (a list of url, user, password). |
limits |
object | see below | Each value must be a positive number; an invalid value keeps its default. |
Keys in limits (name and default): max_request_bytes 8192, idle_timeout_seconds 10, max_connection_seconds 30, max_connections 256, max_connections_per_source 16, max_sources 8192, max_pools_per_source 16, source_ttl_seconds 86400, max_info_events 500, max_scans_per_connection 64, info_field_max_chars 8192.
A per-source, per-minute INFO rate gate (max_info_events times 4, bounded by max_sources) silently drops events from a very busy source.
INFO Events¶
Only the agent's own address is scrubbed; attacker pool URLs, hosts, wallets, users and passwords are preserved. Identical events are reported once per connection. The envelopes cryptomining_indicators, dropper_indicators and info_event_cap_reached are emitted as described in the Events section.
| Event | Description |
|---|---|
miner_api_command |
Every command received: command, parameter, format (text, json or encrypted) and whether it is privileged. |
pool_change_attempt |
A pool add, switch, enable, disable or remove: command, pool URL, user, password and pool index. |
api_token_attempt |
A Whatsminer API token recovered from a decrypted write that carries one: command and token. |
restart_attempt |
restart or quit on the cgminer wire; restart_btminer, reboot, power_off or factory_reset only inside a decrypted Whatsminer write. A plain reboot on the text wire is recorded only as miner_api_command with privileged false, as is every unencrypted Whatsminer command. |
cryptomining_indicators |
Shared envelope for pools, wallets and miner names found in client data. |
dropper_indicators |
Shared envelope for download tools, URLs, hosts and filenames found in client data. |
info_event_cap_reached |
Marker emitted once when the per-connection event cap (max_info_events) is reached. |
ClaymoreAPI_tcp¶
A low interaction TCP service that emulates the management API of Claymore Dual Miner, ethminer and PhoenixMiner GPU miners: JSON-RPC over raw TCP. It answers status and control methods, captures the optional password (psw or api_authorize) from each request, and decodes miner_file writes in memory to extract pool, wallet and dropper indicators. Uploaded files are never written to disk or executed, and no pool is contacted. The active pool and uploaded files are remembered per source IP in memory, so miner_getfile and the pool shown by miner_getstat1 and miner_getstat2 reflect a hijack. A miner_file write followed by a reboot is reported as CVE-2018-1000049.
Example service configuration:
[ClaymoreAPI]
plugin = ClaymoreAPI_tcp
low_port = tcp:3333
port = tcp:3333
description = Claymore/ethminer GPU miner management API.
enabled = Yes
persona = claymore
The shipped file defines one section and ships it with enabled = No. To present another miner, add a section with the same plugin and a different persona on another port (the shipped file's commented example uses ethminer on tcp:3334).
Personas:
| Persona | Emulates |
|---|---|
claymore |
Claymore Dual Miner "15.0 - ETH". One JSON request per connection, then the connection closes. Serves config.txt, epools.txt and dpools.txt. Default. |
ethminer |
ethminer 0.19.0. Persistent newline-delimited JSON-RPC 2.0. Unknown methods and miner_file return error -32601; a miner_file write is still captured and evaluated against the CVE rules first. |
phoenixminer |
PhoenixMiner "PM 6.2c - ETH" on the Claymore wire format. Serves config.txt and epools.txt. |
Unknown methods on the Claymore wire format are logged and the connection is closed without a reply. An HTTP request line on the port gets a minimal HTTP 200 page.
Config¶
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | claymore |
Miner to emulate: any persona name in the loaded document, case-insensitive. An unknown name falls back to the default persona and logs a warning. |
config |
string | none | Path to a JSON file merged over the built-in document. |
JSON file keys:
The file is a partial document containing only the keys to change, merged over the document built into the agent. The merge is shallow: a top-level key you supply replaces the built-in value, except that personas is merged by persona name, so a persona you define replaces the built-in persona of the same name as a whole. A supplied limits object replaces the built-in one, and any key it omits takes its default. No JSON file ships in packages.
| Key | Type | Default | Description |
|---|---|---|---|
default_persona |
string | claymore |
Persona used when the section does not set one. |
seed |
string | "" |
Seed for generated telemetry. Empty derives it from the agent identity. |
personas |
object | three built-in personas | Miner definitions keyed by persona name. Persona fields: family (claymore or ethminer), version_string, project_name, gpus, nominal_mhs_per_gpu, default_pool, default_wallet, reboot_hooks (filenames that trigger the CVE rule), files (filename to content, served by miner_getfile) and http_page. |
limits |
object | see below | Each value must be a positive number; an invalid value keeps its default. |
Keys in limits (name and default): max_request_bytes 65536, max_file_bytes 262144 (decoded miner_file payload cap; excess is truncated), idle_timeout_seconds 30, max_connection_seconds 120, max_connections 256, max_connections_per_source 16, max_sources 8192, max_files_per_source 8, source_ttl_seconds 86400, cve_window_seconds 300 (how long after a miner_file write a reboot completes the CVE rule), max_info_events 500, max_scans_per_connection 64, info_field_max_chars 8192, content_capture_chars 8192 (file content kept in the event). max_connections_per_source_state and max_api_request_events are accepted but never read.
A per-source, per-minute INFO rate gate (max_info_events times 4, bounded by max_sources) silently drops events from a very busy source.
INFO Events¶
Only the agent's own address is scrubbed; attacker pools, wallets, URLs and hosts are preserved. The envelopes cryptomining_indicators, dropper_indicators and info_event_cap_reached are emitted as described in the Events section; the indicator envelopes carry a source_field such as miner_file.config.txt.
| Event | Description |
|---|---|
miner_api_command |
Every method received: method, parameters (JSON, up to 2048 characters), format and whether it is privileged. |
pool_change_attempt |
An ethminer miner_addconnection, miner_setactiveconnection or miner_removeconnection: pool_url is host:port with scheme and credentials stripped, and user is everything before the last @, unsplit. The password is never emitted. |
api_password_attempt |
A password presented in a top-level psw (every persona), or in an api_authorize call, which only the ethminer persona handles; on the Claymore wire an unknown method closes the connection. |
file_write_attempt |
A miner_file write: filename, size, SHA-256, decoded content, truncation flag, and the pools, wallets and URLs found in it. |
restart_attempt |
A miner_reboot or miner_restart call. |
cryptomining_indicators |
Shared envelope for pools, wallets and miner names found in client data. |
dropper_indicators |
Shared envelope for download tools, URLs, hosts and filenames found in client data. |
cve_attempt |
Canonical CVE event, one per triggering file (see below). Its indicators carry trigger (reboot_hook or file_then_reboot), filename, size, sha256 and the extracted pools, wallets, urls and hosts. |
info_event_cap_reached |
Marker emitted once when the per-connection event cap (max_info_events) is reached. |
CVEs Detected¶
- CVE-2018-1000049 (
claymore_miner_api_rce): Claymore Dual Miner remote management API. Fires when aminer_filewrite names a reboot hook (reboot.bat,reboot.shorreboot.bash, case-insensitive, any directory prefix), or when any otherminer_filewrite is followed byminer_rebootorminer_restartfrom the same source withincve_window_seconds.
ClickhouseHTTP_tcp¶
A low interaction TCP service that emulates the ClickHouse HTTP interface. It responds to standard ClickHouse endpoints (/, /ping, /replicas_status, /play, /dashboard, /metrics), answers SQL queries with canned responses, and captures credential attempts, queries, and probes of unknown paths.
Example service configuration:
[ClickhouseHTTP]
plugin = ClickhouseHTTP_tcp
low_port = tcp:8123
port = tcp:8123
description = Clickhouse HTTP interface
enabled = Yes
config = /etc/honeydb/clickhouse.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
clickhouse_version |
string | "24.3.2.23" |
Advertised ClickHouse version string. |
clickhouse_server_display_name |
string | "ch-edge-01" |
Value for the X-ClickHouse-Server-Display-Name header. |
clickhouse_timezone |
string | "UTC" |
Value for the X-ClickHouse-Timezone header. |
auth_policy |
string | "always_succeed" |
One of "always_succeed", "always_fail", "match_list". |
credentials |
list | [["default", ""], ["admin", "admin"], ["clickhouse", "clickhouse"]] |
List of [user, password] pairs used with auth_policy: match_list. |
require_credentials |
bool | false |
When true, anonymous requests receive a 401 challenge. |
accepted_auth_schemes |
list | ["basic","header","query"] |
Subset of auth surfaces to recognize. |
enabled_endpoints |
list | ["/", "/ping", "/replicas_status", "/play", "/dashboard", "/metrics", "/js/*"] |
Glob-aware allowlist of paths to serve; others receive 404. |
force_close_per_response |
bool | false |
Close connection after every response. |
honor-xff-header |
bool | false |
Trust the X-Forwarded-For header to resolve the effective client IP. |
max_request_bytes |
int | 1048576 |
Request size cap; larger payloads receive 413. |
max_requests_per_connection |
int | 1000 |
Force Connection: close after this many requests. |
idle_timeout_seconds |
int | 60 |
Keep-alive idle timeout in seconds. |
structured_info_events |
bool | true |
Emit JSON-encoded INFO payloads; false reverts Basic auth events to legacy free-text. |
query_indicator_max_bytes |
int | 8192 |
Truncation cap for the query field in query events. |
INFO Events¶
| Event | Description |
|---|---|
http_client |
Captures the User-Agent, Content-Type, method, path, and query string of each HTTP request. |
xff_seen |
Fires when an X-Forwarded-For header is present; records the peer, XFF value, whether it was honored, and the resolved client. |
auth_attempt |
Captures an authentication attempt (auth type, username, password, success). |
query |
Captures a received ClickHouse SQL query, its delivery channel and format, and whether a canned response matched. |
unknown_path |
Captures a request to an unrecognized HTTP path (method, path, query). |
CouchDB_tcp¶
A low interaction TCP service that emulates an Apache CouchDB HTTP server. Supports two version profiles (1.x and 3.x), a realistic database and document API, Basic and cookie authentication, and detection of known CVE exploit patterns (CVE-2017-12635, CVE-2017-12636).
Example service configuration:
[CouchDB]
plugin = CouchDB_tcp
low_port = tcp:5984
port = tcp:5984
description = CouchDB HTTP interface
enabled = Yes
config = /etc/honeydb/couchdb.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
profile |
string | "1.x" |
Version profile: "1.x" or "3.x". |
couchdb_version |
string | "1.6.1" / "3.3.3" |
Advertised CouchDB version (default varies by profile). |
erlang_version |
string | "R16B03" / "24.3.4.13" |
Erlang OTP version in the Server header (default varies by profile). |
server_version |
string | — | Overrides the full Server header verbatim when set. |
vendor_name |
string | "Ubuntu" |
OS vendor name in the welcome body. |
vendor_version |
string | "22.04" |
OS vendor version in the welcome body. |
auth_policy |
string | "always_succeed" |
One of "always_succeed", "always_fail", "admin_only". |
preseed_dbs |
list | profile defaults | Override the list of databases pre-populated at connect time. |
max_request_bytes |
int | 1048576 |
Request size cap; larger payloads receive 413. |
max_dbs_per_connection |
int | 1000 |
Maximum databases that may be created in one session. |
max_docs_per_db |
int | 1000 |
Maximum documents per database per session. |
max_requests_per_connection |
int | 1000 |
Force connection close after this many requests. |
idle_timeout_seconds |
int | 60 |
Keep-alive idle timeout in seconds. |
tag_known_cves |
bool | true |
Enable CVE pattern detection events. |
structured_info_events |
bool | true |
Emit JSON-encoded INFO payloads; false reverts Basic auth events to legacy free-text. |
honor_xff_header |
bool | false |
Trust the X-Forwarded-For header to resolve the effective client IP. |
INFO Events¶
| Event | Description |
|---|---|
auth_attempt |
Captures a Basic or cookie/session login attempt (auth type, username, password, success). |
cve_attempt |
Fires when a request matches a known CouchDB CVE signature (CVE id, method, path, indicators). |
novel_path |
Captures a request to a path outside the known/profiled route set. |
db_operation |
Captures a database-level operation (create/delete/info, database name, success). |
doc_operation |
Captures a document-level operation (database, document id, success). |
DNS_tcp¶
A low interaction TCP Domain Name Service (DNS). Answers queries against a configurable synthetic zone, supports query pipelining on a single connection, detects non-DNS traffic (such as TLS handshakes against the DNS port), and can optionally engage zone transfer (AXFR) requests. Abuse patterns such as DNS tunneling, DGA-like names, and enumeration bursts are detected and logged. Rate limiting state is shared with DNS_udp, so an attacker rate-limited on UDP is also rate-limited on TCP.
Example service configuration:
[DNS.tcp]
plugin = DNS_tcp
low_port = tcp:53
port = tcp:53
description = DNS service.
enabled = Yes
config = /etc/honeydb/dns.json
Config¶
The configuration schema is identical to DNS_udp (see below) plus the TCP-specific keys in this table. Pointing both [DNS.udp] and [DNS.tcp] at the same JSON file keeps the synthetic zone and detector settings in sync.
| Key | Type | Default | Description |
|---|---|---|---|
tcp_idle_timeout_seconds |
int | 30 |
Connection closes after this many seconds of silence. |
max_request_bytes |
int | 65535 |
TCP framing cap. Messages whose length prefix exceeds this close the connection. |
axfr_policy |
string | "refused" |
One of "refused" or "engage". Engagement streams the full synthetic zone in a multi-message AXFR. |
max_axfr_engagements_per_minute |
int | 1 |
Per-source cap on AXFR engagements. 0 disables engagement entirely. |
max_axfr_messages_per_engagement |
int | 100 |
Hard cap on AXFR stream length. Larger zones are truncated. |
INFO Events¶
| Event | Description |
|---|---|
dns_query |
Captures each parsed DNS query (qname, qtype, returned rcode). |
abuse_pattern |
Fires when a query matches an abuse detector or error condition; a pattern field discriminates the type (parse_error, rate_limited, internal_error, chaos_probe, amplification probe, zone-transfer attempt, DNSSEC probe, tunneling, DGA-like apex, enumeration burst, and the TCP-only non_dns_traffic). |
DNS_udp¶
A low interaction UDP Domain Name Service (DNS). Answers queries against a configurable synthetic zone (a default example.com. zone is built in), responds to CHAOS-class fingerprint queries (version.bind, etc.), and detects abuse patterns such as DNS tunneling, DGA-like names, enumeration bursts, and amplification probes.
Example service configuration:
[DNS.udp]
plugin = DNS_udp
low_port = udp:53
port = udp:53
description = DNS service.
enabled = Yes
config = /etc/honeydb/dns.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
bind_version |
string | "8.2.2-P5" |
Fills the version.bind and version.server CHAOS responses when those aren't explicitly set in chaos. |
chaos |
dict | built-in defaults | Per-CHAOS-name TXT strings (version.bind, version.server, hostname.bind, id.server, authors.bind). |
zones |
list | built-in example.com zone | List of zone definitions (see zone schema below). |
out_of_zone_policy |
string | "nxdomain" |
Response for out-of-zone queries: "nxdomain", "refused", or "random_a". |
rate_limit |
dict | qps=10, burst=20, max_sources=10000 |
Per-source-IP rate limit. qps: 0 disables. Shared across DNS_udp and DNS_tcp. |
max_response_bytes_edns |
int | 1232 |
Response size cap when the query carries EDNS0. |
max_response_bytes_legacy |
int | 512 |
Response size cap when the query does not carry EDNS0. |
tag_known_abuse |
bool | true |
Master switch for the abuse-pattern detectors (tunneling, DGA, enumeration, etc.). |
tunneling_qname_length_threshold |
int | 80 |
Query name length that triggers tunneling detection. |
tunneling_entropy_threshold |
float | 3.8 |
Label entropy that triggers tunneling detection. |
tunneling_high_entropy_min_label_len |
int | 12 |
Minimum label length for high-entropy tunneling detection. |
dga_entropy_threshold |
float | 3.5 |
Entropy threshold for DGA-like name detection. |
dga_min_label_len |
int | 10 |
Minimum second-level label length for DGA-like name detection. |
dga_max_vowel_ratio |
float | 0.15 |
Maximum vowel ratio for DGA-like name detection. |
enum_burst_threshold |
int | 20 |
Distinct child labels under one parent that triggers enumeration detection. |
enum_burst_window_seconds |
int | 60 |
Sliding window for enumeration tracking. |
structured_info_events |
bool | true |
Set to false to revert to the original free-text INFO format. |
override_name |
bool | false |
Set to true to report the service name from the config file; otherwise DNS is used. |
Zone schema¶
Each entry in zones is a dict:
| Key | Type | Description |
|---|---|---|
apex |
string | Zone apex, e.g. "example.com.". Required. |
ttl |
int | Default TTL for records in this zone (default 3600). |
soa |
dict | SOA fields: mname, rname, serial, refresh, retry, expire, minimum. |
ns |
list or dict | NS hostnames. List = apex; dict = {label: [hostnames]}. |
a, aaaa, txt, cname, ptr |
list or dict | List = apex; dict = {label: [values]}. |
mx |
list or dict | List of {preference, exchange} dicts. |
srv |
list or dict | List of {priority, weight, port, target} dicts. |
caa |
list or dict | List of {flags, tag, value} dicts. |
Wildcard labels (*) are not supported.
INFO Events¶
| Event | Description |
|---|---|
dns_query |
Captures each parsed DNS query (qname, qtype, returned rcode). |
abuse_pattern |
Fires when a query matches an abuse detector or error condition; a pattern field discriminates the type (parse_error, rate_limited, internal_error, chaos_probe, amplification probe, zone-transfer attempt, DNSSEC probe, tunneling, DGA-like apex, enumeration burst). |
Docker_tcp¶
A low interaction TCP service that emulates an unauthenticated Docker Engine API over plain HTTP, the exposed port 2375 that cryptojacking campaigns scan for. It presents as Docker Engine 29.8.0 (API 1.56, minimum 1.40) on a synthetic Ubuntu 24.04.2 LTS host, and answers the stock docker CLI and raw HTTP tooling well enough for an attacker to ping, inspect, pull an image, create and start a container, and exec into it. Each step is recorded, including registry credentials, container commands, environment, bind mounts and Dockerfiles. Nothing is ever pulled, built, started or executed, and no outbound connection is made. Created containers, images and execs are kept per source IP in memory and expire after 3600 seconds idle. Exec returns canned output or nothing.
Example service configuration:
[Docker]
plugin = Docker_tcp
low_port = tcp:2375
port = tcp:2375
description = Low interaction Docker Engine API service.
enabled = Yes
Paths are served with or without a /v1.NN version prefix. Any image can be pulled; creating a container from an image that is neither configured nor pulled returns 404 No such image, which drives the CLI to pull it. Foreground runs exit 0 immediately with no output, and detached containers stay running. Unknown paths return {"message":"page not found"}.
Config¶
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
config |
string | none | Path to a JSON file of settings. The plugin reads no other section options. Without it the plugin runs on its built-in defaults. |
JSON file keys:
The file is a partial document containing only the keys to change; every key is optional and an omitted key keeps its built-in default. An invalid value (wrong type, out of range, a string over 256 characters) is replaced by the default and a warning is logged. If min_api_version is greater than api_version, both are reset. No JSON file ships in packages, and the file is read only when referenced by config.
| Key | Type | Default | Description |
|---|---|---|---|
docker_version |
string | 29.8.0 |
Daemon version presented. |
api_version |
string | 1.56 |
Highest API version served; a /v1.NN prefix above it is rejected. |
min_api_version |
string | 1.40 |
Lowest API version. |
accept_older_api_versions |
bool | true |
Serve prefixes below the minimum. When false, return the real "client version is too old" error. |
git_commit, go_version, build_time, containerd_version, containerd_commit, runc_version, runc_commit, init_version, init_commit, os, arch |
string | 3ce5872, go1.26.8, 2026-09-03T21:50:08.000000000+00:00, v2.3.5, 1294c24a7da8e5a793ed378161673abe94118892, 1.5.1, v1.5.1-0-g8f2685a4, 0.19.0, de40ad0, linux, amd64 |
Component details returned by /version and /info. |
kernel_version, operating_system, os_version |
string | 6.8.0-71-generic, Ubuntu 24.04.2 LTS, 24.04 |
Synthetic host details. |
ncpu, mem_total |
int | 4, 8323874816 |
Synthetic CPU count and memory in bytes. |
host_name, host_id |
string | "" |
Name and ID in /info. Empty derives a stable value per agent. |
images |
list | nginx:latest, redis:7, alpine:3.20 |
Pre-existing images, each an object with ref, created (epoch seconds) and size (bytes). |
containers |
list | one running web container |
Pre-existing containers, each an object with name, image, command and state. |
exec_outputs |
object | id and whoami |
Map of exact command line to output. Other commands return nothing. |
foreground_exit_delay_seconds |
number | 0.1 |
Delay before an attached run finishes, 0 to 3600. |
Bounds (name and default; positive integers): max_header_bytes 16384, max_body_bytes 1048576, max_rx_body_bytes 65536, max_rx_bytes_per_connection 1048576, max_body_discard_bytes 67108864, max_requests_per_connection 200, idle_timeout_seconds 60, request_timeout_seconds 30, stream_timeout_seconds 60, max_connection_seconds 600, max_connections 256, max_connections_per_source 16, max_info_events 500, max_api_request_events 100, max_scans_per_connection 64, max_sources 256, max_containers_per_source 16, max_images_per_source 32, max_execs_per_source 16, max_streams_per_container 8, state_ttl_seconds 3600.
INFO Events¶
Only the agent's own address is scrubbed; attacker pool hosts, dropper URLs, registries and registry credentials are preserved, and credentials are recorded in full. Fields the client did not send are omitted. The envelopes cryptomining_indicators, dropper_indicators and info_event_cap_reached are emitted as described in the Events section; their source_field names the origin, such as container_create.cmd, image_pull.image or build_request.dockerfile.
| Event | Description |
|---|---|
api_request |
Every request except /_ping: method, path without version prefix, query string, API version and user agent. |
image_pull |
POST /images/create: image, tag or digest, registry, import source, and the decoded X-Registry-Auth credentials. |
container_create |
POST /containers/create: image, command, entrypoint, environment, bind mounts, privileged, network and PID mode, and name. |
host_mount_attempt |
A bind mount that exposes a sensitive host path (such as /, /etc, /root, /proc, /var/run/docker.sock, /var/lib/docker or /var/spool/cron): the sensitive subset of the binds. |
exec_create |
POST /containers/{id}/exec: command, privileged and user. |
build_request |
POST /build: remote context, Dockerfile content and tags. |
cryptomining_indicators |
Shared envelope for pools, wallets and miner names found in commands, environment, images and Dockerfiles. |
dropper_indicators |
Shared envelope for download tools, URLs, hosts and filenames found in commands, environment and Dockerfiles. |
info_event_cap_reached |
Marker emitted once when the per-connection event cap (max_info_events) is reached. |
Echo_tcp¶
A low interaction TCP echo service (RFC 862). Any data sent to this service will be echoed back to the client. Received data is also checked for credential-like strings, which are captured as INFO events.
Example service configuration:
[Echo]
plugin = Echo_tcp
low_port = tcp:7
port = tcp:7
description = Echo back data received via tcp.
enabled = Yes
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
detect_scanner_fingerprints |
bool | true |
Enable scanner-attribution INFO events. |
detect_credential_like |
bool | true |
Enable credential-shape INFO events. |
event_field_max_bytes |
int | 1024 |
Per-string byte truncation cap inside event payloads. |
override_name |
bool | false |
When false, the service is reported as "Echo" to normalize naming with Echo_udp. When true, the configured service name is used. |
INFO Events¶
| Event | Description |
|---|---|
scanner_fingerprint |
Fires when received bytes match a known scanner fingerprint (scanner name, pattern id, payload size). |
credential_like_string |
Fires when received bytes contain credential-like strings. |
Echo_udp¶
A low interaction UDP echo service (RFC 862). Data sent to this service is echoed back to the client, capped at max_echo_bytes (default 64 bytes) to prevent the service from being abused as a DDoS reflection/amplification vector. Received data is also checked for credential-like strings and amplification probes.
Example service configuration:
[Echo.udp]
plugin = Echo_udp
low_port = udp:7
port = udp:7
description = Echo back data received via udp.
enabled = Yes
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
max_echo_bytes |
int | 64 |
Maximum bytes reflected back to the source. 0 = never reflect (receive-only). |
detect_scanner_fingerprints |
bool | true |
Enable scanner-attribution INFO events. |
detect_credential_like |
bool | true |
Enable credential-shape INFO events. |
detect_amplification_probe |
bool | true |
Enable amplification-probe INFO events. |
amplification_probe_size_threshold |
int | 64 |
Datagram size threshold for the oversize amplification indicator. |
amplification_probe_repeated_byte_ratio |
float | 0.80 |
Repeated-byte ratio threshold for the filler amplification indicator. |
event_field_max_bytes |
int | 1024 |
Per-string byte truncation cap inside event payloads. |
override_name |
bool | false |
When false, the service is reported as "Echo" to normalize naming with Echo_tcp. |
Note
Earlier versions of this plugin reflected the full received datagram with no size cap. To restore that behavior, set max_echo_bytes to a sufficiently large value.
INFO Events¶
| Event | Description |
|---|---|
scanner_fingerprint |
Fires when the datagram matches a known scanner fingerprint (scanner name, pattern id, payload size). |
credential_like_string |
Fires when the datagram contains credential-like strings. |
amplification_probe_suspected |
Fires when a datagram looks like a UDP amplification probe (size / repeated-byte heuristics). |
Elasticsearch_tcp¶
A low interaction TCP Elasticsearch service. Emulates the Elasticsearch REST API (root endpoint, /_nodes, /_search, /_cat/indices, /_cluster/health, and other commonly probed recon endpoints) with three selectable version profiles. Detects known CVE exploit patterns (CVE-2014-3120, CVE-2015-1427, CVE-2021-44228/Log4Shell).
Example service configuration:
[Elasticsearch]
plugin = Elasticsearch_tcp
low_port = tcp:9200
port = tcp:9200
description = Send basic elasticsearch replies.
enabled = Yes
config = /etc/honeydb/elasticsearch.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
name |
string | "Flake" |
Elasticsearch node name advertised in responses. |
cluster_name |
string | "elasticsearch" |
Cluster name advertised in responses. |
version_profile |
string | "1.x" |
Version profile: "1.x", "7.x", or "8.x". Controls the response shape for / and /_nodes. |
version_number |
string | profile default | Overrides the version.number field. |
build_hash |
string | profile default | Overrides the version.build_hash field. |
build_timestamp |
string | profile default | Overrides the version.build_timestamp (or build_date) field. |
lucene_version |
string | profile default | Overrides the version.lucene_version field. |
tagline |
string | "You Know, for Search" |
Tagline in the root response. |
tag_known_cves |
bool | true |
Enable CVE pattern detection events. |
honor-xff-header |
bool | false |
Trust the X-Forwarded-For header to resolve the effective client IP. |
Profile defaults:
| Field | 1.x |
7.x |
8.x |
|---|---|---|---|
version_number |
1.4.1 |
7.17.0 |
8.11.0 |
lucene_version |
4.10.4 |
8.11.1 |
9.7.0 |
INFO Events¶
| Event | Description |
|---|---|
user_agent |
Captures the request User-Agent header. |
credentials |
Captures HTTP Basic Auth credentials when an Authorization header is present. |
novel_path |
Captures a request to a path outside the emulated endpoint set (method, path, query). |
body_capture |
Captures the (truncated) request body. |
cve_attempt |
Fires on a known CVE pattern (CVE-2014-3120, CVE-2015-1427, Log4Shell CVE-2021-44228); records the CVE id and indicators. |
Electrum_tcp¶
A medium interaction TCP service that emulates an Electrum wallet back end: the newline-delimited JSON-RPC 2.0 protocol that Electrum wallets speak to a Fulcrum 2.1.3 or ElectrumX 2.0.0 server. Reply key order, terminators, error texts and (for ElectrumX) packet padding follow the real servers. The service holds no chain data: every script hash has a zero balance and an empty history, subscriptions never fire, and the chain tip advances with the wall clock from the same anchor as BitcoinRPC_tcp and BitcoinP2P_tcp. It captures the client name and protocol range, the script hashes and scriptPubKeys a client watches, the transactions it tries to broadcast and the peers it tries to register. Nothing is executed or relayed: a broadcast that decodes is acknowledged, remembered per source address for one hour and never sent anywhere, and the service opens no outbound connection.
Example service configuration:
[Electrum]
plugin = Electrum_tcp
low_port = tcp:50001
port = tcp:50001
description = Electrum server (Fulcrum).
enabled = Yes
persona = fulcrum
Default sections (all ship with enabled = No):
| Section | Port | Persona | Notes |
|---|---|---|---|
[Electrum] |
50001 | fulcrum |
Plain TCP. |
[Electrum.tls] |
50002 | fulcrum |
tls = Yes. Presents the plugin's own self-signed certificate (RSA-2048, O=Fulcrum, no host or address named), stored in the agent state directory as electrum-tls.cert and electrum-tls.key, not the agent's default certificate. |
The ports do not conflict with another shipped section.
Config¶
The service section is read for persona and config. The bundled persona and chain document is always loaded; a config JSON file is a partial document: it contains only the top-level keys to change and is merged over the built-in document. The merge is shallow: a top-level key in the file replaces the built-in key, except personas, which is merged per persona name. No file ships in packages; create your own.
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | fulcrum |
fulcrum (Fulcrum 2.1.3, protocol 1.4 to 1.6, CRLF terminator, server.version not required first) or electrumx (ElectrumX 2.0.0, protocol 1.4 to 1.7.0, LF terminator with padding, server.version required first, so the connection is closed otherwise). Case-insensitive. An unknown value falls back to fulcrum with a logged warning. |
tls |
string | No |
Yes wraps the port in TLS with the plugin's own certificate. |
config |
string | none | Path to a JSON file as described above. |
JSON file keys:
| Key | Type | Default | Description |
|---|---|---|---|
banner |
string | persona text | Text returned by server.banner. Truncated to 16384 characters. |
donation_address |
string | persona value | Text returned by server.donation_address. Truncated to 80 characters. |
announce_host |
string | unset | Host name reported in server.features (fulcrum persona only). It must be a host name containing a letter; an IP address is ignored with a warning. When unset, a fabricated private address is reported. |
seed |
string | derived per agent | Non-empty string that replaces the per-agent seed of the synthetic chain. |
limits |
object | see below | Any of the limits below. A key you omit keeps its default; a value that is not a positive number is ignored with a warning. |
default_persona |
string | fulcrum |
Persona used when the section has no persona. |
personas |
object | fulcrum, electrumx |
Persona definitions, merged by name. |
chain |
object | built-in | Chain anchor, genesis and real_headers. An invalid value falls back to the built-in chain. |
wallet_commands |
list | built-in | Command names that trigger wallet_rpc_probe. |
Commonly set limits keys:
| Key | Type | Default | Description |
|---|---|---|---|
max_connections |
int | 64 |
Concurrent connections. |
max_connections_per_source |
int | 12 |
Concurrent connections per source address. |
max_line_bytes |
int | 1000000 |
Longest accepted request line. |
max_batch |
int | 345 |
Calls accepted in one batch. |
max_calls_per_connection |
int | 20000 |
Calls before the connection is closed. |
max_connection_seconds |
int | 3600 |
Longest connection lifetime. |
max_subscriptions |
int | 1000 |
Subscriptions per connection. |
max_info_events |
int | 128 |
General INFO events per connection. |
max_query_events |
int | 1000 |
Query events (scripthash_query, scriptpubkey_query, outpoint_query, address_query) per connection, a separate budget. |
info_events_per_source_per_minute |
int | 240 |
General INFO events per source per minute. |
query_events_per_source_per_minute |
int | 2000 |
Query events per source per minute. |
info_field_max_chars |
int | 256 |
Truncation of captured text fields. |
raw_tx_event_chars |
int | 4096 |
Hex characters of a broadcast transaction kept in tx_broadcast. |
Other limits keys accepted, with defaults: max_batch_capture 32, summaries_per_source_per_minute 30, max_rate_entries 4096, max_tx_bytes 100000, max_remembered_txs 16, max_remembered_bytes 262144, max_store_bytes 16777216, store_ttl_seconds 3600, max_outputs 16, max_echo_chars 2048, max_method_names 64, max_rx_log_bytes 65536, max_tx_log_bytes 16384, max_rx_log_events 512, max_tx_log_events 512, max_reply_bytes_per_line 2097152, max_reply_bytes_per_connection 33554432, reply_bytes_per_source_per_minute 67108864, header_periods_per_source_per_minute 20, header_periods_per_minute 200, max_pong_bytes 65536, backlog_seconds 15, close_grace_seconds 5. A source that exhausts a budget is disconnected.
INFO Events¶
Every event is JSON with event_type and schema_version: 1. Only the agent's own address is scrubbed; script hashes, addresses, decoded transactions and advertised peer hosts are preserved.
| Event | Description |
|---|---|
client_version |
Each server.version call: client name, protocol minimum and maximum, negotiated version, extra_args (a count of extra arguments) and a repeat flag. |
rpc_call |
Once per distinct call shape per connection: method, parameter count, whether the method is known, and whether it arrived before server.version. |
scripthash_query |
First use of each script hash on a connection. |
scriptpubkey_query |
First use of each scriptPubKey (protocol 1.7), with script type and derived address. |
outpoint_query |
First use of each outpoint (protocol 1.7). |
address_query |
A legacy blockchain.address.* call. |
tx_broadcast |
A broadcast, broadcast_package or testmempoolaccept call: decoded transaction id, raw hex (first 4096 characters), size, version, locktime, input and output counts, and each output's value, script type and address. |
tx_lookup |
transaction.get, get_merkle, id_from_pos and the Fulcrum lookups. |
peer_advertise |
A server.add_peer call: the advertised hosts and ports, server version, protocol range and genesis hash, or a malformed marker. |
wallet_rpc_probe |
A wallet or node daemon command (getbalance, payto, dumpprivkey and similar) on the line transport, or an HTTP request sent to the port. |
session_summary |
Once at disconnect: methods called, call, line and batch counts, max_batch_size, other_calls, parse errors, oversized_lines, rx_log_suppressed, tx_log_suppressed, negotiated version, subscriptions and duration. events_dropped holds per-reason counts (cap, rate, query_cap, query_rate) of events dropped by a budget. |
info_event_cap_reached |
Shared marker, emitted when the per-connection INFO cap is hit. |
EVMRPC_tcp¶
A medium interaction TCP service that emulates the JSON-RPC API of an Ethereum-family execution client (Geth, op-geth, BSC, Bor, Erigon, Nethermind, Anvil, Hardhat or Ganache) over HTTP. It keeps a stateful synthetic chain and records the destination of attempted transfers, wallet passphrases, imported private keys, signing requests and raw transactions (the sender is recovered from the signature). Submitted transactions are accepted and stored but never mined, broadcast or relayed, and the plugin makes no outbound connections. WebSocket (port 8546) is not supported; the plugin serves HTTP only.
Example service configuration:
[EVMRPC]
plugin = EVMRPC_tcp
low_port = tcp:8545
port = tcp:8545
description = Ethereum-family JSON-RPC node (Geth, Erigon, Nethermind, Anvil, Hardhat, Ganache).
enabled = Yes
persona = geth-mainnet
Only one section ships. Run one persona per section; to emulate several, add sections on distinct ports.
Config¶
Service section keys
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | "geth-mainnet" |
Persona name (case-sensitive). Precedence: this key, then persona in the JSON file, then persona in the built-in document, then geth-mainnet. An unknown or invalid persona logs a warning and falls back to a built-in geth-mainnet with one account; the plugin still starts. |
config |
path | none | Optional JSON file of overrides. |
The JSON file is a partial document containing only the keys to change; it is merged over the built-in document. The merge is by key: limits is merged key by key, seed is replaced, and a persona with the same name as a built-in one is replaced as a whole while the other built-in personas remain available. No file ships in packages; create one only if you need to override something.
JSON file keys
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | "geth-mainnet" |
Persona to use when the service section sets none. |
seed |
string | "" |
Seed for chain state, block hashes, balances and identity. An empty value derives it from the host identity. Set it to pin state across hosts or restarts; without a stable host identity the chain changes on every restart and a warning is logged. |
limits |
object | see below | Resource and event limits. An invalid value (wrong type, boolean, non-positive) logs a warning and keeps the default; unknown keys are ignored. |
personas |
object | built-in personas | Persona name to persona definition. Add a persona or replace a built-in one by name. |
A persona definition accepts: family (geth, erigon, nethermind, anvil, hardhat, ganache; required), wire (Geth family only: v1.17, v1.16, v1.13), client_version (required), chain_id (integer or "seed"; required), network_id (integer, "seed" or "start_time_ms"; default chain_id), block_flavor (ethereum, ethereum-cancun, op-stack, bsc, polygon, dev; required), block_interval (seconds, greater than 0; required), anchor (required: mode fixed with number and timestamp, or mode uptime with min_age_seconds and max_age_seconds), namespaces (required), accounts, unlock_policy (mode: always_fail, always_succeed or succeed_after, plus attempts), fees (base_fee_gwei, priority_fee_gwei, gas_limit), peer_count, cors ("*" or null), strict_paths (default false: any URL path is answered; no effect on the Erigon family), dev_mode, datadir, node_name, listen_port and error_text. A same-name persona replaces the built-in one whole, so a partial definition fails validation; the plugin then logs a warning and falls back to the built-in geth-mainnet.
Limits (all under limits)
| Key | Default | Description |
|---|---|---|
max_request_bytes |
1048576 |
Request body cap. |
max_header_bytes |
65536 |
Header block cap. |
max_chunk_line_bytes |
1024 |
Chunk size line cap. |
idle_timeout_seconds |
30 |
Close after this long with no data. |
request_deadline_seconds |
60 |
Whole-request deadline, not reset by data. |
max_requests_per_connection |
1000 |
Keep-alive request cap. |
max_requests_per_read |
16 |
Pipelined requests handled per read. |
max_unflushed_bytes |
4194304 |
Abort a client that does not read its responses. |
max_tx_event_bytes |
65536 |
Cap on transaction event data. |
max_batch |
100 |
Calls per batch. |
capture_scan_limit |
100 |
Members of an oversized batch still captured. |
max_response_bytes |
2097152 |
Encoded reply budget per request. |
max_pending_transactions |
1024 |
Stored transactions (count). |
max_pending_bytes |
8388608 |
Stored transactions (bytes). |
max_stored_input_bytes |
16384 |
Call data kept per stored transaction. |
pending_ttl_seconds |
3600 |
Lifetime of a stored transaction. |
max_unlock_entries |
4096 |
Unlock-state entries tracked. |
max_rate_entries |
8192 |
Source addresses tracked by rate limits. |
recovery_rate_per_second |
20 |
Sender recoveries per second, plugin-wide. |
recovery_burst |
20 |
Burst for the plugin-wide recovery rate. |
recovery_per_remote_per_second |
2 |
Inline recoveries per second per source. |
recovery_per_remote_burst |
4 |
Burst for per-source inline recoveries. |
max_inline_recoveries_per_request |
4 |
Inline recoveries per request. |
recovery_queue_size |
512 |
Deferred recoveries queued, plugin-wide. |
recovery_queue_per_remote |
32 |
Deferred recoveries queued per source. |
info_field_max_chars |
8192 |
Cap on bounded text fields in INFO events. |
max_rpc_call_events_per_batch |
32 |
rpc_call events per request body. |
max_capture_events_per_batch |
16 |
High-value and cheatcode_call events per request body. |
max_rpc_call_summaries |
32 |
rpc_call summaries emitted at disconnect. |
max_routine_events_per_connection |
256 |
Routine events per connection. |
max_high_value_events_per_connection |
256 |
High-value events per connection. |
routine_events_per_source_per_minute |
120 |
Routine events per source address per minute. |
high_value_events_per_source_per_minute |
60 |
High-value events per source address per minute. |
routine_events_per_second |
50 |
Routine events per second, plugin-wide. |
high_value_events_per_second |
20 |
High-value events per second, plugin-wide. |
Personas¶
| Persona | Emulates | Chain ID | Notes |
|---|---|---|---|
geth-mainnet |
Geth (default) | 1 | eth, net, web3; three unlocked accounts; unlock always fails. |
geth-private-personal |
Geth private chain | seed-derived | Widest API surface (admin, debug, eth, miner, net, personal, txpool, web3); three locked accounts; unlock succeeds after 3 attempts. |
op-geth-optimism |
op-geth, Optimism | 10 | Two unlocked accounts. |
op-geth-base |
op-geth, Base | 8453 | Two unlocked accounts. |
erigon-mainnet |
Erigon | 1 | No accounts. |
nethermind-mainnet |
Nethermind | 1 | Adds txpool; two unlocked accounts. |
bsc-mainnet |
BSC (Geth) | 56 | Three unlocked accounts. |
polygon-mainnet |
Bor (Geth) | 137 | CORS *; two unlocked accounts. |
anvil |
Anvil | 31337 | Dev mode; 10 accounts; any from accepted; unlock always succeeds; CORS *. |
hardhat |
Hardhat | 31337 | Dev mode; 20 accounts; unlock always succeeds; CORS *. |
ganache |
Ganache | 1337 | Dev mode; 10 accounts; network ID is the start time in milliseconds; unlock always succeeds. |
Public-chain personas anchor to real block numbers from the time of the release; the blocks themselves are synthetic. Account addresses are synthetic and no private key exists for any of them.
INFO Events¶
Every event carries event_type and schema_version. Only the agent's own address is scrubbed; attacker IPs and wallet addresses are preserved. Passphrases and private keys are logged unscrubbed. Quantities are decimal strings and addresses are lowercase.
| Event | Description |
|---|---|
client_fingerprint |
First request on a connection and whenever a field changes: user agent, origin, content type, path, query string, HTTP method and JSON-RPC version. |
rpc_call |
Each distinct method in a request body, with whether any persona family knows it; repeats are counted and summarized at disconnect. |
account_unlock_attempt |
personal_unlockAccount: address, passphrase and duration. |
transaction_attempt |
eth_sendTransaction or personal_sendTransaction: sender, destination, value, gas, fees, nonce, call data and selector, ERC-20 token function fields, contract creation and passphrase. |
raw_transaction |
eth_sendRawTransaction: decoded legacy, 2930, 1559, 4844 and 7702 transactions with recovered sender, destination, value, hash, type, chain ID and raw bytes. |
raw_transaction_sender |
Follow-up carrying the sender once deferred recovery finishes under load. |
key_import_attempt |
personal_importRawKey: private key and passphrase. |
sign_request |
eth_sign, personal_sign or eth_signTransaction: address, message and method. |
admin_action |
One of 14 methods: admin_addPeer, admin_removePeer, admin_addTrustedPeer, admin_removeTrustedPeer, miner_setEtherbase, miner_start, miner_stop, miner_setGasPrice, miner_setExtra, miner_setGasLimit, debug_setHead, parity_setEngineSigner, parity_setEngineSignerSecret and parity_clearEngineSigner. |
cheatcode_call |
Any anvil_*, hardhat_* or evm_* call on any persona, with method, parameters and target address. |
events_dropped |
A volume limit dropped events: count, scope (request, connection, source or plugin) and class (routine or high_value). |
Every call is recorded before the persona decides whether it can serve it, so a method that the emulated client does not implement still produces its event. Cheat codes are recorded but have no effect on later reads.
FTP_tcp¶
A low/medium interaction TCP FTP service. Captures credentials and attacker commands, and detects known CVE exploit patterns (CVE-2011-0762 vsftpd backdoor, CVE-2015-3306 ProFTPD mod_copy). At the low interaction level all logins are denied; at medium, anonymous logins and logins from the password file are allowed, along with file uploads and downloads.
Example service configuration:
[FTP]
plugin = FTP_tcp
low_port = tcp:21
port = tcp:21
description = FTP service.
enabled = Yes
config = /etc/honeydb/ftp.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
interaction_level |
string | "medium" |
"low" denies all logins; "medium" allows anonymous logins and logins from the password file. |
version_profile |
string | "vsftpd_3" |
Sets the default banner: vsftpd_2, vsftpd_3, proftpd, or pure_ftpd. |
welcome_message |
string | (profile default) | Overrides the version profile banner when set. |
ftp_root |
string | /tmp/honeydb/ftp |
FTP root directory. This is where FTP user folders are created and file uploads are stored. |
password_file |
string | (built-in list) | File containing FTP usernames and passwords in the format user:password, one entry per line. |
tag_known_cves |
bool | true |
Enable CVE pattern detection events. |
{
"interaction_level": "medium",
"version_profile": "vsftpd_3",
"welcome_message": "(vsFTPd 3.0.3)",
"ftp_root": "/tmp/honeydb/ftp",
"password_file": "/etc/honeydb/ftppasswd.dat"
}
INFO Events¶
| Event | Description |
|---|---|
username |
Captures the username from the USER command. |
credentials |
Captures the username/password pair from the PASS command. |
cve_attempt |
Fires on a CVE indicator: a USER ending in :) (CVE-2011-0762 vsftpd backdoor) or a SITE CPFR/CPTO sequence (CVE-2015-3306 ProFTPD mod_copy). |
client |
Captures the reported client version from the CLNT command. |
auth_mechanism |
Captures the mechanism requested by the AUTH command. |
probe |
Fires on a SYST or FEAT probe command. |
port_command |
Captures the parsed IP and port from a PORT command. |
eprt_command |
Captures the parsed protocol, address, and port from an EPRT command. |
site_command |
Captures a SITE subcommand and its parameters. |
directory_traversal |
Fires when a RETR/STOR/LIST/MKD/DELE path contains ../ or ..\. |
Gas_tcp¶
A low interaction TCP gas tank service. Emulates a Veeder-Root TLS Automatic Tank Gauge (ATG) — the industrial control system used at gas stations to monitor underground fuel storage tanks. Responds to common read and write commands with realistic sensor data, and captures attempts to change tank labels or the system date/time.
Example service configuration:
[Gas]
plugin = Gas_tcp
low_port = tcp:10001
port = tcp:10001
description = Gas pump protocol.
enabled = Yes
config = /etc/honeydb/gas.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
station_name |
string | random from built-in list | Fixed station name; overrides random selection. |
product1 |
string | "SUPER" |
Tank 1 product label. |
product2 |
string | "UNLEAD" |
Tank 2 product label. |
product3 |
string | "DIESEL" |
Tank 3 product label. |
product4 |
string | "PREMIUM" |
Tank 4 product label. |
INFO Events¶
| Event | Description |
|---|---|
protocol_violation |
Fires when a received ATG command is malformed (missing the SOH start byte or too short to be valid). |
atg_command |
Captures a recognized ATG command and its type, including date/time-set (S001) and tank/label-set (S602xx) write attempts; unrecognized commands are captured with their raw bytes. |
HashCountRandom_tcp¶
A low interaction TCP service. This service will return an MD5 hash and integer to the client. For each client connection, the counter will increment by 1 on each RX event.
Example service configuration:
[HashCountRandom]
plugin = HashCountRandom_tcp
low_port = tcp:4096
port = tcp:4096
description = Send random data prefixed with a hash of a counter via tcp.
enabled = Yes
HTTP_tcp¶
A low interaction TCP HTTP service. This service will provide a generic HTTP page to client requests. For certain known targeted applications like phpMyAdmin, wordpress, weblogic, tomcat, and jboss, static responses specific to those applications are returned to the client.
Beyond the generic page, the builtin server.json also answers a set of cryptocurrency-related requests: crypto lure files such as /.env and /wallet.dat, BTCPay Server and LNbits API paths (including the /lnd-rest/btc/... LND REST proxy path), and the Antminer S9 web interface CGI paths behind HTTP digest authentication. JSON-RPC probe paths are answered by the plugin itself. A custom config replaces the builtin server.json, so only the JSON-RPC probes and the lures of entries that carry lure remain. A persona option makes the whole site look like one of three applications. These features require HoneyDB Agent 2.10.0 or later.
Example service configuration:
[HTTP]
plugin = HTTP_tcp
low_port = tcp:80
port = tcp:80
description = HTTP service.
enabled = Yes
config =
Config¶
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
config |
path | none | Absolute path to a custom JSON configuration. It replaces the builtin server.json entirely, so only the paths it lists are served. It must contain paths and default-page, and every response file it names must exist under html_root; otherwise the agent logs an error and uses the builtin configuration. |
persona |
string | none | btcpay-server, lnbits or antminer (lower case). Overlays that persona's paths onto the active path set. An unknown name logs a warning and the default set is served. See Personas below. |
digest_credentials |
string | none | Comma-separated user:password pairs (at most 16) that grant access wherever digest authentication is required. A pair splits at its first colon, so a password may contain colons but not commas. Overrides the credentials of every digest_auth option on the agent; with no value, each option keeps its own ([["root", "root"]] by default). An invalid value is ignored with a warning. |
digest_accept_any |
Yes or No | No | When Yes, any well-formed digest header grants access. Overrides accept_any of every digest_auth option on the agent. |
JSON file keys (top level):
| Key | Type | Default | Description |
|---|---|---|---|
paths |
object | none (required) | Map of request path to path entry. See the path entry fields below. |
default-page |
object | none (required) | response_headers and response_body files served for the path / only; an unmatched path gets the not-found error page. May carry digest_auth. |
personas |
object | none | Persona name to path overlay. A custom config must define the persona for persona = to resolve. |
error_pages |
object | builtin | Response files for the not-found, method-not-allowed and bad-request pages. |
html_root |
string | /etc/honeydb/html |
Directory holding the response files. |
server_version |
string | Apache/2.4.29 (Ubuntu) |
Value substituted for _SERVER_VERSION_ in response head files. |
honor-xff-header |
boolean | false |
Whether to honor the X-Forwarded-For header. |
Path entry fields:
| Field | Required | Description |
|---|---|---|
name |
Yes | Target identifier used in INFO events. |
request_methods |
Yes | JSON array of allowed HTTP methods. |
match |
No | exact (default) or prefix. A path key containing ? matches on query-parameter presence. |
response_headers |
Yes | File name in html_root. |
response_body |
Yes | File name in html_root. |
cves |
No | List of CVE identifiers reported when the path is requested. |
lure |
No | Serve a per-agent crypto lure instead of response_body: dotenv_generic, bitcoin_wallet, wallet_zip, keystore_index, eth_keystore or solana_id. An unknown id serves response_body and logs a warning. |
lure_kind |
No | lure_kind reported in crypto_lure_request. Defaults to the lure id. |
capture_headers |
No | List of request header names (case-insensitive) whose values are logged as api_credential_presented events, for example ["Authorization"] or ["X-Api-Key"]. Authorization: Basic is left to the basic-auth capture. Host, Content-Length and Transfer-Encoding are rejected. |
captured_response |
No | response_headers and response_body served instead of the base pair when at least one capture_headers header is present. |
method_responses |
No | Per-method override: an object keyed by upper-case method (listed in request_methods) holding response_headers, response_body and optionally captured_response. |
digest_auth |
No | Require HTTP digest authentication on the path: realm, credentials (list of [user, password] pairs, default [["root", "root"]], at most 16), accept_any (default false), challenge_headers and challenge_body. |
reassemble_body |
No | Integer cap from 1 to 262144 bytes. Buffers a request body that arrives in several TCP segments, or chunked, up to the cap. |
handler |
No | Capture handler run on every request to the path, authenticated or not: antminer_set_miner_conf, antminer_passwd, antminer_restart, antminer_form_command, antminer_firmware_upload or antminer_restore. |
An invalid optional field is logged as an error at load and dropped; the path is still served with its base response. Response head files may contain the placeholders _SERVER_VERSION_, _DATE_, and, in a digest challenge, _DIGEST_REALM_ and _DIGEST_NONCE_.
Personas¶
By default the service serves the whole builtin profile set at once, including a cPanel default page and Grafana at /login. Some applications own the site root and /login, which that mix cannot give them without losing other profiles. A persona overlays one application's paths onto the set; persona paths win where they collide.
| Persona | What it serves |
|---|---|
btcpay-server |
A BTCPay Server 2.4.4 front end. / redirects to /login?ReturnUrl=%2F. /login serves the sign-in page (GET) and an "Invalid login attempt." page on POST, capturing Email and Password. /register redirects to /. /stores, /server/users, /server/policies, /account, /api-keys/authorize and /docs redirect to the login page. /i/ and /.well-known/lnurlp/ answer with BTCPay's 404 pages. |
lnbits |
An LNbits 1.6.2 front end. / serves the Vue shell, /admin answers 401, /docs serves Swagger UI, /openapi.json a trimmed OpenAPI document, /favicon.ico answers 307 and /first_install answers 400. Nothing else is shadowed. |
antminer |
The whole site is an Antminer S9 web interface (lighttpd 1.4.32) behind digest authentication, realm antMiner Configuration, factory login root / root. Pages include /, /index.html, /minerConfiguration.html, /minerStatus.html, /network.html, /networkDiagnostics.html, /upgrade.html, /administration.html, /reboot.html, /monitor.html and /kernelLog.html. Every other path is challenged and, once logged in, gets a lighttpd 404, 405 or 400 page. It replaces the cPanel default page and the builtin 404, 405 and 400 pages. |
Warning
The btcpay-server persona claims /login, which shadows the builtin Grafana login profile on that agent. The CVE attributions attached to that profile (CVE-2021-43798, CVE-2022-31107 and CVE-2023-22462) are shadowed on an agent running this persona. Use it on an agent where you do not need the Grafana login profile.
Without a persona, the 20 Antminer S9 /cgi-bin/*.cgi paths (minerStatus.cgi, get_miner_status.cgi, minerConfiguration.cgi, get_miner_conf.cgi, set_miner_conf.cgi, minerAdvanced.cgi, get_system_info.cgi, get_network_info.cgi, set_network_conf.cgi, network_diag.cgi, get_kernel_log.cgi, monitor.cgi, passwd.cgi, reboot.cgi, reset_conf.cgi, kill_cgminer.cgi, create_conf_backup.cgi, upload_conf.cgi, upgrade.cgi and upgrade_clear.cgi) already answer with the digest challenge and an S9's replies. With the builtin server.json, the BTCPay Server and LNbits API paths and the crypto lures are also always on, as are the JSON-RPC probe paths.
Example of a persona with custom digest logins:
[HTTP]
plugin = HTTP_tcp
low_port = tcp:80
port = tcp:80
description = Antminer web interface.
enabled = Yes
persona = antminer
digest_credentials = root:root,admin:s3cret
digest_accept_any = No
Crypto lures and JSON-RPC probes¶
The lures are on with the builtin server.json; a custom config serves one only on an entry with lure. The JSON-RPC probes are always on. A scanner fetching /.env now produces crypto_lure_request instead of unknown_path.
| Paths (GET) | Response | lure_kind |
|---|---|---|
/.env, /.env.bak, /.env.local, /.env.production |
.env with PRIVATE_KEY=, MNEMONIC=, an RPC URL and KEYSTORE_PASSWORD= |
dotenv |
/wallet.dat, /wallet_backup.dat, /backup/wallet.dat, /.bitcoin/wallet.dat |
A 16 KiB Berkeley DB shaped wallet file | bitcoin_wallet |
/wallet.dat.zip, /wallet.zip |
A ZIP holding that wallet.dat |
wallet_archive |
/keystore/, /keystore/UTC--* |
A directory index listing, and a Web3 secret-storage v3 keystore | eth_keystore |
/id.json, /.config/solana/id.json |
A Solana keypair (a JSON array of 64 integers) | solana_keypair |
The lure material is synthetic, unfunded, generated locally on first run and unique per agent. It is the same material the SSH_tcp and Telnet_tcp crypto node persona serves, and it is kept in crypto-lures.json in the state_dir of agent.conf.
A POST to /rpc, /jsonrpc, /solana, /eth or /v1 whose body parses as JSON-RPC emits jsonrpc_probe and gets a static JSON-RPC reply. Nothing is relayed anywhere. For a fuller chain emulation use EVMRPC_tcp.
Digest authentication¶
A path with digest_auth behaves like a server that protects the path with HTTP digest authentication (RFC 2617, MD5, qop=auth).
- A request without valid digest credentials gets the challenge, with a fresh nonce. The client repeats the request on a new connection, because
HTTP_tcpanswers one request per connection. - Access is granted only when the response verifies for one of the configured credentials (user and password), the realm matches, the nonce was issued by this agent in the last five minutes and the digest
urimatches the request. Withaccept_anyset, any well-formed digest header is accepted. Nonces are invalidated by an agent restart. - Every digest login on any path is logged as
http_digest_auth. When the response verifies against a configured credential or one of 24 common device passwords, the event carries the plaintextpassword; otherwise the logged fields allow offline recovery. - Capture does not depend on authentication: a path's handler and CVE detection also run on the unauthenticated request, so a client that sends its body before and after the challenge can produce the handler's events twice. The
authenticatedfield tells them apart.
Request body reassembly¶
By default a request is parsed from one TCP segment. A path with reassemble_body buffers the rest of the body (by Content-Length, or until the terminating chunk) up to the cap, for at most 10 seconds in total and 5 seconds without data. Across the agent, at most 64 reassemblies run at once, 2 per source address, and 8 MiB is reserved in total; a request over those limits is handled from its first segment. Expect: 100-continue is answered with 100 Continue.
INFO Events¶
| Event | Description |
|---|---|
fingerprint |
Captures the client fingerprint submitted to the JS-based /session endpoint. |
credentials |
Captures username/password from a POST form or JSON body login for a matched target. |
client_headers |
Captures the User-Agent and/or Referer sent to a matched target path. |
basic_auth |
Captures HTTP Basic Auth credentials for a target. |
api_credential_presented |
Captures a request header listed in a path's capture_headers (target, header, value, path, method, scheme). The BTCPay Server and LNbits API paths use this to capture API keys and tokens. |
crypto_lure_request |
Fires on a request for a crypto lure file (target, path, lure_kind). |
jsonrpc_probe |
Fires on a JSON-RPC request to a probe path (path, methods, batch_size). |
http_digest_auth |
Captures an Authorization: Digest header on any path: username, realm, uri, nonce, response, qop, nc, cnonce, algorithm, opaque, and password when recovered. accepted is added on a digest-protected path; an unparseable header gives malformed and raw. |
pool_change_attempt |
Fires once per filled pool slot when new pool settings are posted to the Antminer set_miner_conf.cgi (pool_url, user, password, pool_index, authenticated). A miner_api_command event accompanies it, and a cryptomining_indicators event when pools or wallets are found. |
password_change_attempt |
Fires on a post to the Antminer passwd.cgi, or a GET carrying current_pw, new_pw or new_pw_ctrl in the query string (current_password, new_password, confirm_mismatch, authenticated). |
restart_attempt |
Fires on the Antminer reboot.cgi, kill_cgminer.cgi and reset_conf.cgi (command, authenticated). |
miner_api_command |
Fires on Antminer set_miner_conf.cgi, network_diag.cgi and set_network_conf.cgi requests only (method, path, privileged, params, authenticated). command_injection is true when a network form field holds shell metacharacters, and a dropper_indicators event follows. |
firmware_upload_attempt |
Captures a firmware upload to the Antminer upgrade.cgi (filename, size as declared by Content-Length, sha256 over the bytes actually received, authenticated). |
config_restore_attempt |
Captures a configuration restore upload to the Antminer upload_conf.cgi (filename, size, sha256, entries, compressed, stock_script, authenticated). |
cryptomining_indicators |
Mining pools, wallets or miners in submitted Antminer pool settings (see Events). |
dropper_indicators |
Download tools, URLs, hosts and file names in an injected Antminer network form command (see Events). |
cve_attempt |
Canonical CVE detection, one event per CVE (cve, short_name, indicators). Fires when a request matches a path entry with cves, a CVE pattern in the path, body or headers, or a detection signature such as CVE-2024-3721 (TBK DVR command injection; further events carry the injected command, dropper indicators and loader signatures). |
cve_pattern |
Fires on a non-CVE attack pattern (SSTI, SQL injection, XSS, directory traversal) in the path, body or headers. Carries cve (null), label, pattern and location. |
unknown_path |
Captures a request to an unmatched (404) path (method, path, query string, User-Agent). |
The path field of these events holds the request path only; the raw query string is carried in query_string. Only the agent's own IP address is scrubbed, so attacker infrastructure in a request is preserved.
CVEs Detected¶
The plugin reports many CVEs through cve_attempt. The detections tied to the cryptocurrency profiles described above are:
| CVE | Short name | Detection |
|---|---|---|
| CVE-2018-11220 | antminer_restore_backup_rce |
Bitmain Antminer restore-backup RCE: a configuration archive posted to upload_conf.cgi that carries a non-stock restoreConfig.sh. Indicators include the archive file name, its entries and the script with its SHA-256. |
| CVE-2025-32013 | lnbits_lnurlauth |
LNbits LNURL-auth SSRF: a POST to /api/v1/lnurlauth. |
iKettle_tcp¶
A low interaction TCP smart kettle service. Emulates the Smarter iKettle 2.0 smart kettle, which communicates over plain TCP with no authentication and is actively indexed by Shodan and probed by automated scanners. Captures which commands attackers issue — device fingerprinting, control attempts (boil, stop, set temperature), and supplied parameters.
Example service configuration:
[iKettle]
plugin = iKettle_tcp
low_port = tcp:2081
port = tcp:2081
description = iKettle smart kettle emulation.
enabled = Yes
INFO Events¶
| Event | Description |
|---|---|
version_query |
Fires on a firmware/version query command (0x11). |
status_query |
Fires on a status query command (0x21). |
stop_command |
Fires on a stop command (0x61). |
boil_command |
Fires on a boil command (0x65); captures the target and keep-warm temperatures. |
set_temperature |
Fires on a set-temperature command (0x69); captures the target temperature. |
unknown_command |
Fires on an unrecognized command byte. |
IMAP_tcp¶
A low interaction TCP IMAP service. Emulates an IMAP4rev1 server to attract credential-stuffing attacks, automated mail-client probes, and post-authentication enumeration activity. Captures login credentials, mailbox selections, search criteria, and appended messages.
Example service configuration:
[IMAP]
plugin = IMAP_tcp
low_port = tcp:143
port = tcp:143
description = IMAP service.
enabled = Yes
config = /etc/honeydb/imap.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
server_name |
string | "localhost" |
Hostname shown in the IMAP greeting and STATUS responses. |
version_string |
string | "Dovecot ready" |
Banner string appended to the greeting. |
valid_passwords |
list | ["admin","letmein","password123","password"] |
Passwords accepted for any username. |
capabilities_pre_auth |
list | ["IMAP4rev1","LOGIN"] |
Capabilities advertised before authentication. |
capabilities_post_auth |
list | ["IMAP4rev1","IDLE","NAMESPACE"] |
Capabilities advertised after authentication. |
mailboxes |
list | ["INBOX","Sent","Drafts","Trash","Spam"] |
Mailboxes available to clients. |
INFO Events¶
| Event | Description |
|---|---|
credentials |
Captures the username/password from a LOGIN command. |
imap_select |
Fires on SELECT or EXAMINE; records the mailbox and read-only flag. |
imap_search |
Captures the criteria from a SEARCH command. |
imap_append |
Captures the mailbox and uploaded message body from an APPEND command. |
IPFS_tcp¶
A low to medium interaction TCP service that emulates the Kubo (go-ipfs) RPC API of an IPFS node whose API port 5001 was exposed by mistake, which attackers use for free hosting and remote node control. It speaks the HTTP dialect of Kubo 0.43.1 (status codes, headers, JSON and NDJSON bodies and error texts from a real node) and answers the ipfs CLI, curl and scanners. The real CID of every upload is computed while streaming; the content is discarded and only a bounded sample and hash are kept. Pins, keys, configuration, peers and the MFS tree are remembered per source in memory. Content is never fetched, dialed, relayed, served back or executed, no outbound connection is made, and the node never joins the IPFS network. Reads of content the node does not have hold the connection open, like an online node that cannot find it.
Example service configuration:
[IPFS]
plugin = IPFS_tcp
low_port = tcp:5001
port = tcp:5001
description = IPFS Kubo RPC API.
enabled = Yes
Leave tls = No: the RPC API is plain HTTP. As Kubo does, a POST to a known /api/v0 command (after the 405 method check) receives 403 - Forbidden when its Origin or Referer is not localhost:<api port> or 127.0.0.1:<api port>, or when it carries neither and the user agent starts with Mozilla.
Config¶
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
agent_version_suffix |
string | none | Appended to AgentVersion (kubo/0.43.1/acc53c0/<suffix>). Kubo's Docker image uses docker. When set, the default debug command line also gains --migrate=true and --agent-version-suffix=<suffix>. Overrides the JSON key; a blank value is ignored. |
config |
string | none | Path to a JSON file merged over the built-in document. |
JSON file keys:
The file is a partial document containing only the keys to change. It is deep-merged over the document built into the agent: objects merge key by key, and lists and scalars replace the built-in value. An unusable document falls back to a built-in Kubo 0.43.1 persona and logs a warning. No JSON file ships in packages.
| Key | Type | Default | Description |
|---|---|---|---|
agent_version_suffix |
string | "" |
See the service section key. |
version |
object | Kubo 0.43.1 | Version, Commit, Repo, System and Golang; all five must be non-empty strings. |
listen.interface_ipv4 |
string or null | null |
LAN address shown in the node's addresses. null uses a seeded 10.x.x.x address, never the agent's own. |
listen.swarm_port |
int | 4001 |
Swarm port shown in the node's addresses. |
repo.size_base, repo.num_objects_base, repo.storage_max |
int | 27550, 1, 10000000000 |
Repo statistics baseline. |
repo.path, repo.fs_repo |
string | /data/ipfs, 18 |
Repo path and version. |
pubsub_enabled |
bool | false |
When false, pubsub/* records the call and returns Kubo's "not enabled" error. |
connect_succeeds |
bool | false |
When false, swarm/connect fails with the captured dial-failure text; when true it returns the success shape. |
debug.enabled |
bool | true |
When false, /debug/* and /logs return 404. |
debug.cmdline |
list | ["ipfs", "daemon"] |
Command line shown by the debug endpoints (up to 16 items of 256 characters). Not present in the built-in document; the default applies when it is empty. |
seed |
string | "" |
Replaces the host identity seed material. |
webui_cid |
string | built-in | CID served for /webui; must start with bafy. |
config |
object | captured Kubo configuration | Baseline for config/show. |
config_types |
object | built-in | Map of configuration key to Go type, which drives the exact decode-error text. |
protocols, log_subsystems, peers |
list | built-in | Libp2p protocol ids, log subsystem names, and bootstrap peers (each with peer and addr). |
limits |
object | see below | Bounds. An out-of-range value is clamped to the key's valid range (LIMIT_RANGES in the plugin) with a warning; unknown names are ignored. |
Keys in limits (name and default):
| Group | Keys |
|---|---|
| Transport | max_header_bytes 16384, max_rx_body_bytes 65536, max_rx_bytes_per_connection 1048576, max_upload_bytes 67108864, max_body_discard_bytes 268435456, max_requests_per_connection 200, idle_timeout_seconds 60, request_timeout_seconds 120, max_connection_seconds 900, max_hold_seconds 600, stream_timeout_seconds 60, max_connections 256, max_connections_per_source 16, max_response_bytes 8388608, max_list_items 1024, max_tx_log_bytes 65536, max_script_steps 64, debug_bytes_per_source_per_minute 16777216, debug_bytes_per_minute 134217728, max_journal_entries 2048, max_journal_entries_per_source 32 |
| Uploads | sample_bytes 4096, max_parts 64, max_filename_chars 1024, max_path_depth 32, max_directory_frames 512, max_leaves 65536, min_chunk_size 1024, min_exact_chunk_size 64, max_car_header_bytes 4096, cdc_bytes_per_source_per_minute 134217728, cdc_bytes_per_minute 536870912, small_leaves_per_source_per_minute 262144, small_leaves_per_minute 1048576, max_document_bytes 262144, max_key_bytes 8192, max_stored_document_bytes 65536 |
| Holds (seconds) | dial_wait_seconds 5, pin_wait_seconds 3, lookup_wait_seconds 6, findpeer_wait_seconds 12, ping_lookup_seconds 10, provide_wait_seconds 1, remote_pin_wait_seconds 30 |
| Repo state | max_pins 1024, max_known 4096, max_keys 64, max_ipns 64, max_bootstrap 64, max_peering 64, max_config_overrides 128, max_mfs_entries 1024, max_mfs_leaves 8192, max_filters 64, max_remote_services 16, max_routing_records 64, max_children_per_entry 1024, max_name_chars 256, max_value_chars 4096, max_ipns_value_chars 1024, key_derivations_per_source_per_minute 20 |
| Events | info_field_max_chars 8192, max_routine_events_per_connection 100, max_high_value_events_per_connection 200, max_capture_events_per_batch 66 (raised to max_parts + 2 if lower), max_rpc_call_events_per_batch 32, max_rpc_call_summaries 32, routine_events_per_second 50, high_value_events_per_second 20, routine_events_per_source_per_minute 300, high_value_events_per_source_per_minute 120, max_rate_entries 8192, max_scans_per_connection 64 |
An upload larger than max_upload_bytes gets no response and the connection is dropped; the content_add event is marked truncated.
INFO Events¶
Only the agent's own address is scrubbed; attacker multiaddrs, URLs, configuration values and API keys are preserved. cid, sha256 and sniffed content type are derived from client bytes. The envelopes cryptomining_indicators and dropper_indicators are emitted as described in the Events section, from upload samples, configuration values, pubsub payloads, IPNS values, pinning-service endpoints, peer multiaddrs and routing record samples.
| Event | Description |
|---|---|
api_request |
Every request: method, path, arguments, query, user agent, origin, whether the command is known, and body size. |
api_auth |
An Authorization header: scheme, and username and password (Basic), token (Bearer) or the raw value. |
content_add |
Each part of an add, block/put, dag/put, dag/import or files/write: endpoint, filename, sniffed type, size, SHA-256, CID, a bounded sample, and layout and chunker for adds. |
pin_request |
pin/add, rm, update or verify, and the roots of a dag/import: action, CID and options. |
config_change |
config set, config/replace or config/profile/apply: action, key, value, document hash and size, and profile name. |
key_request |
key/gen, import, rm, rename, sign or verify: action, name, type, size, key hash and sample, and signing data. |
peer_connect |
swarm/connect or disconnect, peering, bootstrap and swarm filter changes: action, multiaddrs and peer id. |
mfs_request |
files/* operations and add with to-files: action, paths, source and CID. |
routing_request |
routing/*, dht/query, ping, resolve and name/get, put or inspect: action, key, CID, peer or name, and record hash and sample. |
remote_pin_request |
pin/remote/*: action, service name, endpoint, API key, CID, name and status. |
ipns_publish |
name/publish: path, CID, key, lifetime, TTL and allow_offline. |
pubsub_request |
pubsub/*: action, decoded topic, payload and payload size. |
shutdown_request |
A shutdown call (acknowledged and ignored). |
cryptomining_indicators |
Shared envelope for pools, wallets and miner names found in client data. |
dropper_indicators |
Shared envelope for download tools, URLs, hosts and filenames found in client data. |
events_dropped |
Marker emitted when volume limits dropped events: scope, event class and count dropped. |
IPP_tcp¶
A low interaction TCP Internet Printing Protocol (IPP/2.0) service. Accepts both HTTP-wrapped IPP requests (as used by CUPS, Windows, and exploit frameworks) and raw IPP binary. Captures print-job metadata, usernames, targeted printer URIs, and CVE exploit patterns (CVE-2024-47176, CVE-2024-47177).
Example service configuration:
[IPP]
plugin = IPP_tcp
low_port = tcp:631
port = tcp:631
description = Internet Printing Protocol service.
enabled = Yes
config = /etc/honeydb/ipp.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
upload_dir |
string | "var/uploads/print_jobs" |
Directory where captured print job files are saved. |
save_print_jobs |
bool | true |
Set to false to disable saving print job files while still emitting INFO events. |
printer_name |
string | "HP LaserJet Pro MFP" |
Printer name returned in Get-Printer-Attributes responses. |
printer_location |
string | "Office - Floor 2" |
Location string returned in Get-Printer-Attributes responses. |
INFO Events¶
| Event | Description |
|---|---|
ipp_username |
Captures the requesting-user-name attribute and the operation. |
ipp_abuse_pattern |
Fires on a shell-injection (CVE-2024-47177) or suspicious printer-uri (CVE-2024-47176) pattern. |
ipp_print_job |
Fires on a Print-Job operation. |
ipp_validate_job |
Fires on a Validate-Job operation. |
ipp_create_job |
Fires on a Create-Job operation. |
ipp_send_document |
Fires on a Send-Document operation. |
ipp_cancel_job |
Fires on a Cancel-Job operation; records the job id. |
Kubernetes_tcp¶
A low interaction TCP Kubernetes service. Emulates a Kubernetes API server, responding to common kubectl API paths (nodes, pods, secrets, namespaces, deployments, RBAC) with plausible JSON payloads. Captures bearer tokens, anonymous access, request bodies, and pod exec/attach/portforward attempts.
Example service configuration:
[Kubernetes]
plugin = Kubernetes_tcp
low_port = tcp:6443
port = tcp:6443
description = Send basic Kubernetes server replies.
enabled = Yes
INFO Events¶
| Event | Description |
|---|---|
http_request |
Captures each API request's method, path, and query string. |
user_agent |
Captures the request User-Agent header. |
bearer_token |
Captures a Bearer token presented in the Authorization header. |
anonymous_access |
Fires on a request made with no authentication credentials. |
body_capture |
Captures the (truncated) request body. |
exec_attempt |
Fires on a pod exec/attach/portforward subresource request. |
cryptomining_indicators |
Fires when a POST, PUT or PATCH body (other than an admission webhook review) contains mining pools, wallets or miner names (see Events). The JSON body is flattened first, so command, args and env entries are read as a shell would, and base64-wrapped commands are unwrapped. source_field is the resource collection written (pods, deployments, daemonsets, statefulsets, replicasets, replicationcontrollers, jobs, cronjobs or configmaps), or body. |
dropper_indicators |
Fires when such a body contains a download tool together with a URL, or a /dev/tcp host (see Events). |
cve_attempt |
Fires on a CVE detection (see CVEs Detected below). |
CVEs Detected¶
| CVE | Short name | Detection |
|---|---|---|
| CVE-2026-3288 | ingress_nginx_rewrite_target_injection |
The ingress-nginx rewrite-target annotation value breaks out of its directive. |
| CVE-2026-1580 | ingress_nginx_auth_method_injection |
The auth-method annotation value is more than a bare HTTP method. |
| CVE-2026-24512 | ingress_nginx_path_injection |
An Ingress rule path is not a clean URI path. |
| CVE-2026-4342 | ingress_nginx_annotation_injection |
Injection in another annotation (for example configuration-snippet) or in two or more annotations. |
| CVE-2025-24514 | ingress_nginx_auth_url_injection |
The auth-url annotation value contains an nginx break-out character. |
| CVE-2025-1097 | ingress_nginx_auth_tls_match_cn_injection |
The auth-tls-match-cn annotation value contains a break-out character. |
| CVE-2025-1098 | ingress_nginx_mirror_target_injection |
The mirror-target or mirror-host annotation value contains a break-out character. |
| CVE-2026-35469 | kubelet_spdy_frame_exhaustion |
An exec, attach or portforward request whose Upgrade value contains spdy. This is an attempt signal only. |
| CVE-2021-44228 | log4shell_jndi |
Log4Shell JNDI lookup in the request target, a header value or the body. |
| CVE-2024-9042 | kubelet_nodelogquery_cmdi |
kubelet NodeLogQuery command injection: a shell metacharacter in the query parameter of a /api/v1/nodes/<node>/proxy/logs/ request. |
The ingress-nginx detections inspect an Ingress write (POST, PUT or PATCH to /apis/networking.k8s.io/v1/namespaces/<ns>/ingresses) and an AdmissionReview posted to a path ending in /networking/v1/ingresses, which the plugin answers with an allowed review. JSON-patch bodies are not inspected.
LDAP_tcp¶
A low interaction TCP LDAP service. Captures bind attempts (anonymous and credential), directory search queries, and write-operation attempts. Detects Log4Shell JNDI injection (CVE-2021-44228) and classic LDAP filter-injection patterns. Binds using any password from a built-in common-password list are accepted; all other passwords are rejected.
Example service configuration:
[LDAP]
plugin = LDAP_tcp
low_port = tcp:389
port = tcp:389
description = LDAP server
enabled = Yes
INFO Events¶
| Event | Description |
|---|---|
cve_attempt |
Fires on a Log4Shell JNDI pattern (CVE-2021-44228) detected in request text. |
injection_attempt |
Fires on an LDAP filter-injection pattern detected in request text. |
anonymous_bind |
Fires on an anonymous (empty-credential) bind. |
bind_attempt |
Captures an authenticated bind attempt and supplied credentials. |
search_query |
Captures an LDAP search request. |
modify_attempt |
Captures an LDAP modify request. |
add_attempt |
Captures an LDAP add request. |
delete_attempt |
Captures an LDAP delete request. |
LND_tcp¶
A medium to high interaction TCP service that emulates the REST API (grpc-gateway, HTTP/1.1 over TLS) of lnd 0.21.4-beta, the Lightning Network Daemon. It is the service a stolen admin.macaroon gets presented to: macaroon authentication, a wallet state machine, the read endpoints scanners and BTCPay, LNbits, Zeus or Thunderhub clients call, and the spend endpoints an attacker holding a macaroon tries (pay invoice, send coins, open or close a channel, bake a macaroon). Every macaroon, wallet password, seed, invoice and destination presented is recorded. Nothing is executed or relayed: spends are fabricated, consistent per source address in memory only, and the service opens no outbound connection, holds no real key and writes no file.
Example service configuration:
[LND]
plugin = LND_tcp
low_port = tcp:8080
port = tcp:8080
tls = Yes
description = LND Lightning node REST API.
enabled = Yes
The shipped section sets no other option. Port 8080 is not used by another shipped section. The service presents its own ECDSA P-256 certificate (O=lnd autogenerated cert, CN=<hostname>, ALPN http/1.1), not the agent's default certificate.
Config¶
There is no persona key. The emulated node is the bundled funded lure node (three channels, about 412,853 sat on chain), shaped by wallet_state and post_init_view. The bundled document is always loaded; a config JSON file is a partial document containing only the keys to change, deep-merged over it: nested objects are merged key by key, while lists (for example lure.channels) and scalars are replaced. No file ships in packages; create your own.
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
macaroon_policy |
string | accept |
accept: any well-formed macaroon (v2 binary or v1 packet, with a location and identifier) is served. lure_only: only this agent's own admin.macaroon lure, and the per-source macaroon issued by initwallet (credential_source init), are served. reject: every macaroon is rejected with lnd's real errors. |
wallet_state |
string | unlocked |
unlocked: /v1/state reports SERVER_ACTIVE. locked: /v1/state reports LOCKED, protected calls answer "wallet locked", and unlockwallet and changepassword reject every password after about one second. none: a fresh uninitialised node (NON_EXISTING) on which initwallet succeeds for the calling source. |
post_init_view |
string | empty |
Only meaningful with wallet_state = none. empty: after a takeover the source sees a real fresh wallet with no funds, channels, peers or history, and an identity derived from the seed it submitted. seeded: the source sees the funded lure node. |
alias |
string | first 20 hex characters of the node public key | Node alias. Blank means default; truncated to 32 bytes. |
max_body |
int | 1048576 |
Request body cap in bytes. Overrides limits.max_request_bytes. |
idle_timeout |
int | 30 |
Idle and stream timeout in seconds. Overrides limits.idle_timeout_seconds. |
event_cap |
int | 200 |
High-value events per connection. Overrides limits.max_high_value_events_per_connection. |
tls |
string | Yes in the shipped section |
Keep Yes; the service speaks TLS. |
config |
string | none | Path to a JSON file as described above. |
Values of macaroon_policy, wallet_state and post_init_view are case-insensitive; an invalid value falls back to the default with a logged warning.
JSON file keys (top level):
| Key | Type | Default | Description |
|---|---|---|---|
seed |
string | "" (derived per agent) |
Pins the node identity and synthetic chain data. |
lure |
object | see description | onchain_confirmed_sat (412853), onchain_unconfirmed_sat (0), channels (list of channel objects) and peer_onion_addresses. |
node |
object | see description | color, features, num_peers, num_channels, num_closed_channels, num_pending_channels, graph_num_nodes, graph_num_channels, graph_total_capacity_sat. |
chain |
object | mainnet anchor | anchor_height, anchor_time, block_interval_seconds (604), network. Shares the anchor with the bitcoin persona of BitcoinRPC_tcp. |
limits |
object | see below | Resource and rate limits; positive numbers only, invalid values are ignored with a warning. |
The bundled document also carries version, getinfo_version, wallet, fixtures and templates (response bodies); these merge the same way and are not intended as options.
Commonly set limits keys: max_connections 512, max_connections_per_source 32, max_requests_per_connection 1000, max_secret_chars 4096 (credentials are logged in full up to this length), info_field_max_chars 4096, max_high_value_events_per_connection 200, max_routine_events_per_connection 1000, high_value_events_per_source_per_minute 60, routine_events_per_source_per_minute 120, unlock_failure_delay_seconds 1.0, init_wallet_delay_seconds 2.2, connect_peer_delay_seconds 3.0. The bundled limits object also holds the transport and session bounds (max_header_bytes, max_chunk_line_bytes, request_deadline_seconds, max_requests_per_read, max_unflushed_bytes, max_tx_event_bytes, max_stream_seconds, max_sessions, max_session_invoices, max_session_root_keys, max_session_macaroons, max_payment_request_chars, max_mnemonic_words, max_rate_entries, routine_events_per_second, high_value_events_per_second, genseed_delay_seconds, wallet_startup_seconds).
INFO Events¶
Every event carries event_type, schema_version: 1, method, path and wallet_state (under wallet_state = none also wallet_initialized and post_init_view). Only the agent's own address is scrubbed; node public keys, hosts, invoice destinations and payout addresses are preserved. Macaroons, wallet passwords, mnemonics and passphrases are recorded in full.
| Event | Description |
|---|---|
macaroon_presented |
One per request carrying a Grpc-Metadata-macaroon header (and one with missing: true per protected request without it): the macaroon, its parsed identifier, root key id, permissions and caveats, whether it was accepted, and credential_source (lure, policy, init, rejected, malformed or missing). |
wallet_unlock_attempt |
A call to unlockwallet, changepassword or genseed: the passwords, recovery window, aezeed passphrase and the response given. |
wallet_init_attempt |
A call to initwallet: the mnemonic and its validity, aezeed passphrase, wallet password, extended master key, channel backups and the response given. |
payment_attempt |
A payment or invoice decode (router_send, sendpayment_sync, sendtoroute_sync, payreq_decode): the payment request with its decoded BOLT11 fields, keysend fields, fee limit and result. |
onchain_send_attempt |
sendcoins or sendmany: destination address and its validity and network, amount, fee rate and result. |
channel_action |
Channel open or close, or peer connect or disconnect: node public key, host as sent, funding amount, channel point and result. |
macaroon_bake_attempt |
Macaroon bake, delete or list: requested permissions, root key id and the macaroon returned. |
rest_call |
Every request: route pattern (raw path for unknown paths, 256 characters), authenticated, known, status and a per-connection count. |
events_dropped |
Marker that events were dropped by a rate limit. |
LPD_tcp¶
A low interaction TCP Line Printer Daemon (LPD) service (RFC 1179). LPD is a legacy print-spooling protocol commonly found on embedded network printers, older UNIX hosts, and industrial print servers. Captures submitted print jobs, including job metadata (hostname, user, job name) and print data format.
Example service configuration:
[LPD]
plugin = LPD_tcp
low_port = tcp:515
port = tcp:515
description = LPD service.
enabled = Yes
config = /etc/honeydb/lpd.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
save_jobs |
bool | false |
Write received print job files to disk. |
upload_dir |
string | /tmp/honeydb/var/uploads/print_jobs |
Directory for saved print job files and metadata. Only used when save_jobs is true. |
INFO Events¶
| Event | Description |
|---|---|
job_info |
Captures print-job metadata from the control file (hostname, user, job name, source filename, queue). |
data_info |
Captures the print-job data summary (detected print format and data size). |
unexpected_queue |
Fires when a job targets a print queue name not in the known-queues list. |
Memcached_tcp¶
A low interaction TCP Memcached service. Accepts connections and returns plausible Memcached text-protocol responses without storing any data. Captures which cache keys clients probe, write attempts, and destructive commands such as flush_all.
Example service configuration:
[Memcached]
plugin = Memcached_tcp
low_port = tcp:11211
port = tcp:11211
description = Send basic memcached replies.
enabled = Yes
config = /etc/honeydb/memcached.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
version |
string | "1.6.12" |
Memcached version string returned in VERSION and STATS responses. |
INFO Events¶
| Event | Description |
|---|---|
memcached_keys_probed |
Captures a read command (get/gets) and the keys requested. |
memcached_write_attempt |
Captures a write command (set/add/replace/append/prepend) and its key/storage metadata. |
memcached_flush |
Captures a flush_all command and its optional delay. |
Modbus_tcp¶
A low interaction TCP Modbus service. Emulates a Modbus TCP PLC with selectable vendor profiles, handling the common read/write function codes and device identification requests. Captures register/coil read and write attempts and detects scanning behavior (sequential address walks, multi-function-code enumeration).
Example service configuration:
[Modbus]
plugin = Modbus_tcp
low_port = tcp:502
port = tcp:502
description = Modbus service.
enabled = Yes
config = /etc/honeydb/modbus.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
vendor_profile |
string | "abb" |
Profile key: abb, schneider, siemens, allen_bradley, or generic. |
vendor_name |
string | (from profile) | Override vendor name from profile. |
product_code |
string | (from profile) | Override product code from profile. |
product_name |
string | (from profile) | Override product name from profile. |
model_name |
string | (from profile) | Override model name from profile. |
major_minor_revision |
string | (from profile) | Override firmware revision from profile. |
vendor_url |
string | (from profile) | Override vendor URL from profile. |
user_application_name |
string | (empty) | Override user application name from profile. |
holding_registers |
object | — | Map of address string → value string to pre-set holding register values. |
input_registers |
object | — | Map of address string → value string to pre-set input register values. |
coils |
object | — | Map of address string → value string (0 or 1) to pre-set coil values. |
discrete_inputs |
object | — | Map of address string → value string (0 or 1) to pre-set discrete input values. |
Vendor profiles:
| Profile Key | Vendor | Product | Revision |
|---|---|---|---|
abb |
ABB | AC500 | 3.0.2 |
schneider |
Schneider Electric | Modicon M340 | 2.6.1 |
siemens |
Siemens | SIMATIC S7-1200 | 4.4.0 |
allen_bradley |
Rockwell Automation | MicroLogix 1400 | 21.003 |
generic |
Generic PLC | Generic PLC | 1.0.0 |
INFO Events¶
| Event | Description |
|---|---|
function_code |
Captures a valid Modbus function-code request (FC, name, unit/transaction IDs, request parameters). |
write_attempt |
Captures a write function code that modifies the simulated data store. |
scan_pattern |
Fires on detected scanning behavior (sequential address walk or multi-function-code enumeration). |
parse_error |
Fires when a frame fails to parse; captures the raw hex of the offending PDU. |
MoneroRPC_tcp¶
A high interaction TCP service that emulates the HTTP JSON-RPC interfaces of a Monero node: monerod v0.18.5.3 (unrestricted, or in the restricted public-node form) and monero-wallet-rpc v0.18.5.1 with an open, funded wallet. Status lines, headers, JSON layout and error codes follow real captures, including epee quirks. The service captures mining redirection, transfers, key and seed requests, wallet open and restore attempts, daemon redirection, daemon administration calls and submitted raw transactions. Nothing is executed or relayed: requests are accepted and remembered per source address in memory only, no transaction or block is sent anywhere, and the service opens no outbound connection and writes no file. Lure keys derive from the agent seed and are never funded.
Example service configuration:
[MoneroRPC]
plugin = MoneroRPC_tcp
low_port = tcp:18081
port = tcp:18081
description = monerod JSON-RPC (unrestricted).
enabled = Yes
persona = daemon
Default sections (all ship with enabled = No):
| Section | Port | Persona | Notes |
|---|---|---|---|
[MoneroRPC] |
18081 | daemon |
Unrestricted monerod. |
[MoneroRPC.restricted] |
18089 | daemon_restricted |
Public-node form: hidden methods answer -32601, hidden endpoints 404, /get_transactions capped at 100 hashes. |
[MoneroRPC.wallet] |
18082 | wallet |
monero-wallet-rpc with an open, funded wallet of two accounts. |
The shipped sections set no option other than persona, so HTTP digest authentication is off and no rpc_auth_attempt events occur until rpc_login is enabled. The plugin is plain HTTP; there is no tls option. The ports do not conflict with another shipped section.
Config¶
A config JSON file is a partial document containing only the keys to change, merged over the bundled document. The merge is shallow: limits and chain are merged one level (a key you give replaces the same key; nested values such as chain.fees are replaced whole), seed and weak_credentials are replaced whole, and personas is merged by persona name with each persona's top-level keys replaced. No file ships in packages; create your own.
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | daemon |
daemon, daemon_restricted or wallet. Case-insensitive. Falls back to the file's persona, then the bundled persona, then daemon; an unknown value uses daemon with a logged warning. |
rpc_login |
flag | no |
yes presents HTTP digest authentication (realm monero-rpc), as --rpc-login does. Accepts yes/true/1/on and no/false/0/off. |
auth_policy |
string | list |
Which digests are accepted: list (the weak_credentials pairs), after_n_attempts (any valid digest after auth_after_n failures from that source) or never. Only used with rpc_login. |
auth_after_n |
int | 3 |
Failures before after_n_attempts accepts. Minimum 1. |
key_policy |
string | synthetic |
synthetic: query_key returns the lure seed and keys. error: query_key is hidden (-32601), as under --restricted-rpc. |
wallet_password_policy |
string | any |
Which passwords open_wallet accepts for the lure wallet: any, list (only weak_credentials passwords, or one set by change_wallet_password in the session; the lure wallet's own password is the empty string, so an empty password also opens it) or never. Wallets the session created, restored or generated always open with their own password. |
wallet_dir |
string | /home/monero/wallet |
Directory quoted in open_wallet's "file not found" text. |
config |
string | none | Path to a JSON file as described above. The bundled document is always loaded first. |
An invalid value for any option is replaced by its default with a logged warning.
JSON file keys:
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | daemon |
Persona used when the section has no persona. |
seed |
string | "" (derived from agent identity) |
Pins the chain and lure keys. When empty and no stable host identity exists, they change on every restart (a warning is logged). |
weak_credentials |
list | ten user and password pairs | Pairs accepted by auth_policy = list and wallet_password_policy = list. |
limits |
object | see below | Resource and rate limits. |
chain |
object | mainnet anchor | anchor_height, anchor_timestamp, anchor_difficulty, block_interval (120 s), fees, hard_forks, peer and rate values. One block is produced per interval from the anchor. |
personas |
object | three personas | Per-persona overrides, for example wallet_dir or the wallet account balances. |
Commonly set limits keys: max_request_bytes 1048576, max_requests_per_connection 1000, idle_timeout_seconds 30, max_sessions 4096, session_ttl_seconds 86400, max_high_value_events_per_connection 64, max_routine_events_per_connection 200, high_value_events_per_source_per_minute 60, routine_events_per_source_per_minute 120, auth_events_per_source_per_minute 300, info_field_max_chars 8192, cve_26819_min_requests 64. The bundled limits object also holds transport, response-size, session-count and per-call bounds not listed here.
INFO Events¶
Every event is JSON with event_type, schema_version and persona. Only the agent's own address is scrubbed; miner and destination addresses, banned hosts and bootstrap-daemon targets are preserved. Passwords, seeds and keys are recorded in full up to 1024 characters.
| Event | Description |
|---|---|
client_fingerprint |
User-Agent, path, HTTP method, auth scheme, JSON-RPC version and content type. |
rpc_call |
Method (JSON-RPC method or plain path), parameter count, batch size, count and whether the method is known. |
rpc_auth_attempt |
A digest authentication attempt (only with rpc_login): username, realm, URI, nonce, response, whether it was accepted and credential_source. |
mining_redirect_attempt |
/start_mining, a wallet start_mining or the wallet address in get_block_template: miner address and its validity, threads and background-mining flags. |
transfer_attempt |
transfer, transfer_split, sweep_all, sweep_single or sweep_dust: destinations with amounts and address validity, priority, ring size, payment id and result. |
key_exfil_attempt |
query_key, export_key_images, export_outputs, prepare_multisig, transaction key or proof requests and signing: the key type or signature type, policy and result. |
raw_transaction |
/send_raw_transaction or block submission: transaction hex (truncated), validity, transaction id and result. |
wallet_open_attempt |
open_wallet, create_wallet, generate_from_keys or change_wallet_password: filename, passwords, view and spend keys, address and whether the path attempted traversal. |
seed_restore_attempt |
restore_deterministic_wallet: the submitted seed in full, seed offset, password, restore height and whether the seed is valid (25, 24, 13, 12 or invalid). |
daemon_redirect_attempt |
set_daemon or /set_bootstrap_daemon: target address, credentials, trust, SSL and proxy settings. |
daemon_admin_call |
Bans, log level, peer and bandwidth limits, flush_txpool, flush_cache, /update, /save_bc, /set_log_categories, /set_log_hash_rate, /pop_blocks, /stop_daemon and the wallet's stop_wallet: the parameters sent. |
events_dropped |
Marker that events were dropped by a rate limit. |
cve_attempt |
Canonical CVE detection event, see CVEs Detected. |
CVEs Detected¶
| CVE | Short name | Detection |
|---|---|---|
| CVE-2025-26819 | monero_rpc_response_exhaustion |
A client floods requests without reading the replies, as monerod 0.18.3.4 and earlier kept every response in memory. Emitted once per connection when the write buffer fills after at least cve_26819_min_requests answered requests. |
MOTD_tcp¶
A low interaction TCP Message Of The Day (MOTD) service. On connect, sends a banner selected at random from the configured list (or built-in IoT/embedded-device defaults: OpenWrt router, Hikvision IP camera, Synology NAS, Home Assistant hub) and closes the connection.
Example service configuration:
[MOTD]
plugin = MOTD_tcp
low_port = tcp:8
port = tcp:8
description = Send a message via tcp and close connection.
enabled = Yes
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
banners |
list | built-in IoT list | Banner strings to send on connect. One is selected at random per session. |
detect_scanner_fingerprints |
bool | true |
Enable scanner-attribution INFO events. |
detect_credential_like |
bool | true |
Enable credential-shape INFO events. |
event_field_max_bytes |
int | 1024 |
Per-string byte truncation cap inside event payloads. |
override_name |
bool | false |
When false, the service is reported as "MOTD" to normalize naming with MOTD_udp. |
{
"banners": [
"My Custom Device v1.0\r\n",
"Another Device Banner\r\n"
]
}
INFO Events¶
| Event | Description |
|---|---|
scanner_fingerprint |
Fires when received bytes match a known scanner fingerprint. |
credential_like_string |
Fires when received bytes contain credential-like strings. |
MOTD_udp¶
A low interaction UDP Message Of The Day (MOTD) service. Replies to each received datagram with a banner selected at random from the configured list (or built-in IoT/embedded-device defaults: OpenWrt router, Hikvision IP camera, Synology NAS, Home Assistant hub).
Example service configuration:
[MOTD.udp]
plugin = MOTD_udp
low_port = udp:8
port = udp:8
description = Send a message via udp.
enabled = Yes
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
banners |
list | built-in IoT list | Banner strings to send in reply. One is selected at random per datagram. |
detect_scanner_fingerprints |
bool | true |
Enable scanner-attribution INFO events. |
detect_credential_like |
bool | true |
Enable credential-shape INFO events. |
event_field_max_bytes |
int | 1024 |
Per-string byte truncation cap inside event payloads. |
override_name |
bool | false |
When false, the service is reported as "MOTD" to normalize naming with MOTD_tcp. |
INFO Events¶
| Event | Description |
|---|---|
scanner_fingerprint |
Fires when received datagram bytes match a known scanner fingerprint. |
credential_like_string |
Fires when received datagram bytes contain credential-like strings. |
MQTT_tcp¶
A low interaction TCP MQTT service. Emulates an MQTT broker that accepts all connections, capturing credentials, topic subscriptions, published payloads, and known IoT device patterns (Home Assistant, Tasmota, Zigbee2MQTT, Shelly, and others) from connecting clients.
Example service configuration:
[MQTT]
plugin = MQTT_tcp
low_port = tcp:1883
port = tcp:1883
description = MQTT
enabled = Yes
INFO Events¶
| Event | Description |
|---|---|
connect |
Captures a CONNECT packet (client ID, protocol version, supplied/absent credentials). |
will |
Captures a declared Last Will (topic, message size/preview). |
subscribe |
Captures a SUBSCRIBE (topic, wildcard flag, known IoT topic pattern). |
publish |
Captures a PUBLISH (topic, payload size/preview, known IoT topic pattern). |
large_message |
Fires when a publish message exceeds the logging cap (total/logged/dropped bytes). |
disconnect |
Fires on a DISCONNECT packet. |
MSSQL_tcp¶
A low interaction TCP Microsoft SQL Server service using the TDS protocol. Handles client PRELOGIN and LOGIN7 handshakes and captures SQL and Windows (NTLM) credentials. When configured with allow_login: true, it accepts a fake login and also captures post-authentication SQL queries, stored procedure calls, attack patterns (such as xp_cmdshell usage), and known attack tool fingerprints.
Example service configuration:
[MSSQL]
plugin = MSSQL_tcp
low_port = tcp:1433
port = tcp:1433
description = MSSQL server
enabled = Yes
config = /etc/honeydb/mssql.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
mssql_version |
string | "0f000000" |
Single SQL Server version hex string advertised in PRELOGIN. Ignored if mssql_versions is set. |
mssql_versions |
list | (built-in pool) | Pool of version strings; one is selected at random per connection. The built-in pool covers SQL Server 2012–2022. |
server_name |
string | "CustomerDB" |
Server name returned in TDS error tokens. |
allow_login |
bool | false |
If true, all login attempts are granted a fake successful login and post-auth queries are captured. |
Version hex string reference:
| SQL Server Version | Hex string |
|---|---|
| 2012 (v11) | 0b000000 |
| 2014 (v12) | 0c000000 |
| 2016 (v13) | 0d000000 |
| 2017 (v14) | 0e000000 |
| 2019 (v15) | 0f000000 |
| 2022 (v16) | 10000000 |
INFO Events¶
| Event | Description |
|---|---|
prelogin |
Captures a parsed PRELOGIN packet and the client's pre-login options. |
advertised_version |
Records the server version advertised back during pre-login. |
client_login |
Captures a parsed LOGIN7 packet (login details, auth type, result). |
ntlm_auth |
Captures NTLM authentication data (domain, username, workstation) from a Windows-auth login. |
query |
Captures the decoded SQL text of a client batch query. |
rpc |
Captures a parsed RPC request (procedure name, first string parameter). |
attack_pattern |
Fires when query/RPC text matches an attack pattern (xp_cmdshell, sp_configure, UNC injection, enumeration). |
tool_fingerprint |
Fires when the client app/library name matches a known tool fingerprint. |
parse_error |
Fires when a TDS header or packet fails to parse. |
MySQL_tcp¶
A low interaction TCP MySQL service. Emulates a MySQL 5.7/8.x server and simulates a complete authenticated session — every login is accepted — to capture credentials, post-login SQL queries, attack patterns (file read/write, UDF code execution, schema enumeration), and attack tool fingerprints (sqlmap, metasploit, hydra, and others).
Example service configuration:
[MySQL]
plugin = MySQL_tcp
low_port = tcp:3306
port = tcp:3306
description = MySQL server
enabled = Yes
config = /etc/honeydb/mysql.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
mysql_version |
string | (uses pool) | Single version string to advertise in the server greeting. |
mysql_versions |
list | built-in pool | Pool of version strings to randomize from per connection — takes precedence over mysql_version. |
os_version |
string | "ubuntu0.20.04.3" |
OS suffix appended to the version string in the greeting. |
{
"mysql_versions": ["8.0.32", "8.0.36", "5.7.44-log"],
"os_version": "ubuntu0.22.04.1"
}
INFO Events¶
| Event | Description |
|---|---|
client_info |
Captures client handshake-response info during login. |
credentials |
Captures the username and auth data from the client login. |
query |
Captures the scrubbed SQL text of a COM_QUERY. |
db_change |
Captures a database switch (COM_INIT_DB) and the target database. |
unknown_command |
Fires on an unrecognized MySQL command byte. |
attack_pattern |
Fires when query text matches a known attack pattern. |
tool_fingerprint |
Fires when a client name or query matches a known tool fingerprint. |
parse_error |
Fires when a packet fails to parse. |
NTP_udp¶
A low interaction UDP NTP service. Emulates a stratum-2 NTP server, responding to standard NTP client requests. Detects NTP Mode 7 monlist requests associated with the CVE-2013-5211 NTP amplification DDoS attack.
Example service configuration:
[NTP]
plugin = NTP_udp
low_port = udp:123
port = udp:123
description = NTP service.
enabled = Yes
INFO Events¶
| Event | Description |
|---|---|
ntp_mode7 |
Fires on an NTP Mode 7 private-mode packet; tags CVE-2013-5211 on monlist request codes. |
ntp_client_info |
Captures a parsed standard NTP request (client version, mode, stratum). |
ntp_error |
Fires when packet parsing raises an error. |
Oracle_tcp¶
A low interaction TCP Oracle Database service. Emulates an Oracle TNS listener, capturing connect strings, client versions, privilege escalation attempts (AS SYSDBA/AS SYSOPER), and login credentials. When allow_login is enabled, it simulates a successful login and captures post-authentication SQL and attack patterns. Detects CVE-2012-1675 (TNS Poison) indicators.
Example service configuration:
[Oracle]
plugin = Oracle_tcp
low_port = tcp:1521
port = tcp:1521
description = Oracle TNS listener
enabled = Yes
config = /etc/honeydb/oracle.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
oracle_version |
string | "19.0.0.0.0" |
Oracle version string included in the banner and ACCEPT packet. |
oracle_os |
string | "Linux" |
OS label in the banner string (Linux or Windows). |
allow_login |
bool | true |
When true, simulate a successful login and capture post-auth activity. When false, reject with ORA-01017 immediately after credentials are captured. |
timeout |
int | 30 |
Seconds before closing a connection that has not completed authentication. |
post_auth_timeout |
int | 30 |
Seconds of inactivity after authentication before closing. |
INFO Events¶
| Event | Description |
|---|---|
connect_info |
Captures a TNS CONNECT (service name, client id, username, raw connect string). |
cve |
Fires when the service name matches CVE-2012-1675 (TNS Poison) indicators. |
privilege_escalation |
Fires when a connect string requests SYSDBA or SYSOPER privileges. |
client_version |
Captures the decoded client version when present in the connect string. |
auth_attempt |
Captures the submitted username and password. |
post_auth_command |
Captures a post-auth SQL statement (type, raw SQL, tables, columns). |
attack_pattern |
Fires when post-auth SQL matches a known Oracle attack pattern. |
post_auth_summary |
Reports the count of post-auth packets seen at timeout or connection close. |
POP3_tcp¶
A low interaction TCP POP3 service. Attracts credential-stuffing attacks, automated mail-client probes, and post-authentication enumeration activity. Captures usernames, passwords, and message retrieval activity.
Example service configuration:
[POP3]
plugin = POP3_tcp
low_port = tcp:110
port = tcp:110
description = POP3 service.
enabled = Yes
config = /etc/honeydb/pop3.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
server_name |
string | "localhost" |
Hostname shown in the POP3 greeting. |
version_string |
string | "POP3 server ready" |
Banner string appended to the greeting. |
valid_passwords |
list | ["admin","letmein","password123","password"] |
Passwords accepted for any username. |
capabilities |
list | ["USER","UIDL","TOP"] |
Capabilities advertised by the CAPA command. |
INFO Events¶
| Event | Description |
|---|---|
username |
Captures the username from the USER command. |
credentials |
Captures the username/password pair from the PASS command. |
pop3_retr |
Captures the message number from a RETR command. |
pop3_uidl |
Captures the message number from a UIDL command. |
pop3_top |
Captures the message number and requested line count from a TOP command. |
PostgreSQL_tcp¶
A low interaction TCP PostgreSQL service (wire protocol v3). Captures credential spray attempts, client tool fingerprints (hydra, nmap, sqlmap, metasploit, and others), and — when allow_login is enabled — post-authentication queries and attack patterns, including CVE-2019-9193 (COPY ... FROM PROGRAM) attempts.
Example service configuration:
[PostgreSQL]
plugin = PostgreSQL_tcp
low_port = tcp:5432
port = tcp:5432
description = PostgreSQL server
enabled = Yes
config = /etc/honeydb/postgresql.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
pg_version |
string | "14.2" |
PostgreSQL version string advertised after login and embedded in auth failure messages. |
os_version |
string | "14.2-1.pgdg20.04+1" |
OS suffix appended to the version string as (Ubuntu <os_version>). Leave empty to omit. |
max_connections |
int | 100 |
Advertised after successful auth (allow_login mode). |
allow_login |
bool | false |
When true, authentication succeeds (fake) and the session advances to query capture mode. When false, authentication always fails after capturing credentials. |
INFO Events¶
| Event | Description |
|---|---|
startup_info |
Captures a valid startup message (user, database, encoding, application_name, protocol version). |
tool_detection |
Fires when the application_name matches a known client/scanner fingerprint. |
auth_attempt |
Captures a password message (user, database, submitted password). |
query |
Captures the SQL text of a simple Query message. |
attack_pattern |
Fires when query SQL matches a detected attack pattern. |
unexpected_message |
Fires when a non-password message arrives during the auth phase. |
parse_error |
Fires on an oversized or malformed startup/password/query message. |
ProConOs_tcp¶
A low interaction TCP ProConOs service. Emulates a Phoenix Contact ProConOs PLC runtime — the environment used by Phoenix Contact Inline, AXC, and RFC series PLCs — with selectable device profiles. Captures device identification probes, project info queries, and high-severity PLC start/stop control attempts.
Example service configuration:
[ProConOs]
plugin = ProConOs_tcp
low_port = tcp:20547
port = tcp:20547
description = ProConOs PLC runtime service.
enabled = Yes
config = /etc/honeydb/proconos.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
device_profile |
string | "il_2050_me" |
Profile key; selects all identity strings as a group. One of il_2050_me, axc_f_2152, rfc_460r, generic. |
ladder_runtime |
string | (from profile) | ProConOs runtime version string returned in the identify response. |
plc_type |
string | (from profile) | PLC model and firmware string returned in the identify response. |
project_name |
string | (from profile) | Project name returned in identify and project info responses. |
boot_project |
string | (from profile) | Boot project name returned in identify and project info responses. |
project_source |
string | "n/a" |
Project source returned in identify and project info responses. |
plc_state |
string | (from profile) | PLC operating state (RUN, STOP, or HALT). |
probe_threshold |
int | 3 |
Number of commands in one session before a scan-pattern INFO event fires. |
Device profiles:
| Profile Key | PLC Type | Project | State |
|---|---|---|---|
il_2050_me |
IL 2050 ME V 3.95A.6 | QuickMix | RUN |
axc_f_2152 |
AXC F 2152 V 2.0.4 | MainProcess | RUN |
rfc_460r |
RFC 460R PN 3TX V 5.20 | WaterControl | STOP |
generic |
Generic PLC V 1.0 | Project | RUN |
INFO Events¶
| Event | Description |
|---|---|
identify |
Fires on an IDENTIFY (device fingerprint) request. |
plc_state_query |
Fires on a PLC state query. |
project_info |
Fires on a project-info request. |
write_attempt |
Fires on a PLC start/stop control command (high severity). |
unknown_command |
Fires on an unrecognized command byte; captures the byte and raw hex. |
scan_pattern |
Fires when the per-session command count reaches the probe threshold. |
parse_error |
Fires on a buffer-overflow or frame-dispatch error; captures the raw hex. |
Random_tcp¶
A low interaction TCP service that responds with random data. Responds to any inbound connection with a burst of random base64-encoded data, and keeps the connection open — replying to each client payload with a fresh random blob — to engage slow/persistent probers until the client disconnects or the per-session exchange limit is reached.
Example service configuration:
[Random]
plugin = Random_tcp
low_port = tcp:2048
port = tcp:2048
description = Send random data via tcp.
enabled = Yes
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
max_exchanges |
int | 100 |
Maximum number of exchanges per session. When reached, the server sends one final random response and closes the connection. |
RAW_tcp¶
A low interaction TCP JetDirect (RAW printing) service. Captures print jobs sent to the printer port, detects the print data format (PDF, PostScript, PCL, PJL, and others), extracts PJL job metadata, and detects abuse patterns and CVE exploit indicators (CVE-2024-47076, CVE-2024-47177).
Example service configuration:
[RAW]
plugin = RAW_tcp
low_port = tcp:9100
port = tcp:9100
description = JetDirect raw printing service.
enabled = Yes
config = /etc/honeydb/raw.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
upload_dir |
string | "var/uploads/print_jobs" |
Directory where captured print job files and metadata are saved. |
banner |
string | (none) | Bytes sent to the client on connect. Preset values: "hp_laserjet", "brother", "xerox". Any other non-empty string is sent verbatim. Omit or set to empty to send nothing (default). |
tag_known_cves |
bool | true |
Enable CVE pattern detection events. |
INFO Events¶
| Event | Description |
|---|---|
print_format |
Captures the detected print format of each print job (PJL, PostScript, PCL, etc.). |
pjl_job_name |
Captures the job name from a PJL JOB NAME directive. |
pjl_username |
Captures the username from a PJL JOB/SET directive. |
pjl_info_probe |
Fires on a PJL INFO/ECHO probe directive. |
pjl_filesystem_access |
Fires on a PJL filesystem-access command. |
postscript_dangerous_operator |
Fires when a dangerous PostScript operator appears in a job. |
cve_attempt |
Fires on path traversal in a PJL FS command (CVE-2024-47076) or Foomatic RIP shell injection in a PJL directive (CVE-2024-47177). |
RDP_tcp¶
A low interaction TCP RDP service. Emulates the RDP connection handshake (X.224, TLS upgrade, MCS channel negotiation) to maximize credential capture, then terminates the connection after the client submits credentials. Captures requested security protocols, routing cookies, and submitted domain/username/password values. Detects CVE-2019-0708 (BlueKeep) exploit attempts.
Example service configuration:
[RDP]
plugin = RDP_tcp
low_port = tcp:3389
port = tcp:3389
description = RDP service.
enabled = Yes
INFO Events¶
| Event | Description |
|---|---|
cookie |
Captures the mstshash routing-cookie identifier when present. |
requested_protocols |
Captures the requested security protocols from the RDP negotiation request. |
cve |
Fires when BlueKeep (CVE-2019-0708) ms_t120 channel binding is detected. |
credentials |
Captures the domain, username, and password from the Info PDU. |
Redis_tcp¶
A low interaction TCP Redis service. Emulates a Redis 7.2.4 server, capturing authentication attempts, configuration changes, Lua script execution, replication commands, and module load attempts. Detects known Redis attack patterns including CVE-2022-0543 (Lua sandbox escape), config-based RCE, and module-based RCE.
Example service configuration:
[Redis]
plugin = Redis_tcp
low_port = tcp:6379
port = tcp:6379
description = Basic Redis
enabled = Yes
config = /etc/honeydb/redis.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
decoy_password |
string or null | null |
Exact password that results in a fake authenticated session; null or empty disables the feature. |
INFO Events¶
| Event | Description |
|---|---|
auth_attempt |
Captures the password (and ACL username on Redis 6+) supplied with an AUTH command. |
auth_result |
Fires when a submitted password matches the configured decoy password. |
config_set |
Captures a CONFIG SET key/value; tags config-based RCE setups. |
lua_script |
Captures an EVAL Lua script body. A sandbox-escape or overflow indicator is reported as a separate cve_attempt event. |
script_load |
Captures a Lua script body from SCRIPT LOAD, inspected like lua_script. |
evalsha |
Captures the SHA passed to EVALSHA. |
slaveof |
Captures the host/port targeted by SLAVEOF/REPLICAOF. |
slaveof_no_one |
Fires on SLAVEOF/REPLICAOF NO ONE (replication detachment). |
module_load |
Captures a MODULE LOAD path used for module-injection RCE. |
cryptomining_indicators |
Fires when a value written by SET, SETNX, GETSET, APPEND, SETEX, PSETEX, MSET or CONFIG SET contains mining pools, wallets or miner names (see Events). source_field is the lower-cased command, or config_set for CONFIG SET. Base64-wrapped payloads are unwrapped, and an identical finding on one connection is reported once. |
dropper_indicators |
Fires when a value written by the same commands contains a download tool together with a URL, or a /dev/tcp host, the usual cron payload (see Events). |
cve_attempt |
Fires on a CVE detection (see CVEs Detected below). |
CVEs Detected¶
| CVE | Short name | Detection |
|---|---|---|
| CVE-2022-0543 | lua_sandbox_escape |
An EVAL or SCRIPT LOAD script contains package.loadlib, io.open, os.execute, os.popen or io.popen. |
| CVE-2024-31449 | redis_lua_bit_tohex_overflow |
An EVAL or SCRIPT LOAD script calls bit.tohex(. When the second argument is an integer literal, an abnormal value is flagged. EVALSHA is not inspected. |
SIP_udp¶
A low interaction UDP SIP service. Emulates a SIP registrar/proxy to capture toll fraud attempts, credential stuffing (Digest auth brute-forcing via REGISTER floods), and reconnaissance probes from automated scanners. Known SIP scanner tools (sipvicious, friendly-scanner, sipsak, and others) are detected and flagged.
Example service configuration:
[SIP]
plugin = SIP_udp
low_port = udp:5060
port = udp:5060
description = Session Initiation Protocol (SIP) for voice and video calls.
enabled = Yes
config = /etc/honeydb/sip.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
server_header |
string | "Asterisk PBX" |
Value sent in the SIP Server: response header. Set to match the identity of a real SIP server (e.g. "FreeSWITCH-mod_sofia/1.10.9", "Cisco-SIPGateway/IOS-12.x"). |
INFO Events¶
| Event | Description |
|---|---|
sip_request |
Captures the method and key headers (User-Agent, From/To URI, Call-ID) of an incoming SIP request. |
options_probe |
Fires on a SIP OPTIONS request. |
sip_auth_attempt |
Captures Authorization/Proxy-Authorization credentials (scheme, username, realm, uri, algorithm). |
abuse_pattern |
Fires when the User-Agent matches a known scanner/abuse pattern. |
SMTP_tcp¶
A low interaction TCP SMTP service. Attracts credential-stuffing attacks, open-relay probes, email enumeration attempts, and malware delivery activity. Captures sender/recipient addresses, authentication credentials, and email headers, and detects open-relay probes, header injection, and known-bad EHLO strings.
Example service configuration:
[SMTP]
plugin = SMTP_tcp
low_port = tcp:25
port = tcp:25
description = SMTP service.
enabled = Yes
config = /etc/honeydb/smtp.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
server_name |
string | "localhost" |
Hostname in the banner and EHLO response; also used as the local domain for open-relay detection. |
version_string |
string | "ESMTP Exim 4.80" |
Version string appended to the banner. |
{
"server_name": "mail.example.com",
"version_string": "ESMTP Exim 4.96"
}
INFO Events¶
| Event | Description |
|---|---|
helo_domain |
Captures the domain argument from EHLO/HELO. |
from_address |
Captures the sender from MAIL FROM. |
to_address |
Captures the recipient from RCPT TO. |
auth_type |
Records the requested AUTH mechanism (PLAIN/LOGIN). |
auth_credentials |
Captures the username/password from an AUTH PLAIN or AUTH LOGIN attempt. |
vrfy_address |
Captures the address probed via VRFY. |
expn_list |
Captures the mailing list probed via EXPN. |
email_header |
Captures selected DATA message headers (Subject, From, To, etc.). |
abuse_type |
Fires on an open-relay probe, header injection, or known-bad EHLO string. |
SNMP_udp¶
A low interaction UDP SNMP service (SNMPv1/v2c). Responds to GET, GETNEXT, GETBULK, SET, TRAP, and INFORM requests with a simulated Cisco IOS router MIB. Captures community strings, OID requests, SET write attempts, and MIB walk enumeration, and detects known CVE probe patterns (CVE-2002-0013, CVE-2017-6736/6737/6738).
Example service configuration:
[SNMP]
plugin = SNMP_udp
low_port = udp:161
port = udp:161
description = SNMP service.
enabled = Yes
config = /etc/honeydb/snmp.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
mib_overrides |
dict | {} |
Map of OID string → string value to override default MIB values (e.g. set a custom sysName or sysLocation). |
community_string_length_threshold |
int | 64 |
Byte length above which a community string triggers a CVE-2002-0013 probe event. |
mib_walk_max_repetitions_threshold |
int | 10 |
GETBULK max-repetitions value above which MIB walk detection fires. |
mib_walk_getnext_count_threshold |
int | 5 |
Cumulative GETNEXT count per source IP above which MIB walk detection fires. |
Example override to rename the device:
{
"mib_overrides": {
"1.3.6.1.2.1.1.5.0": "edge-rtr-01",
"1.3.6.1.2.1.1.6.0": "Data Centre A"
}
}
INFO Events¶
| Event | Description |
|---|---|
snmp_request |
Captures each SNMP request (version, community, PDU type, OIDs). |
set_attempt |
Captures each varbind OID/value (and writability) on an SNMP SET. |
mib_walk_detected |
Fires on a suspected MIB walk (high GETBULK max-repetitions or repeated GETNEXT). |
cve_probe |
Fires on an oversized community string (CVE-2002-0013) or known Cisco CVE OID prefixes. |
SolanaRPC_tcp¶
A medium interaction TCP service that emulates a Solana RPC node: JSON-RPC 2.0 over HTTP and PubSub over WebSocket. It answers as an Agave mainnet-beta RPC node, a solana-test-validator or Surfpool. It keeps a stateful synthetic chain and records who maps the node, which accounts and programs they watch, and every submitted transaction decoded to its fee payer, programs and transfer destinations. Transactions are accepted, decoded and tracked but never relayed, and the plugin makes no outbound connections. On mainnet the only funded accounts are this agent's own crypto lure keys, plus program accounts and the USDC mint; on test-validator and surfpool, airdropped keys also become funded. Every other public key is unfunded.
Example service configuration:
[SolanaRPC]
plugin = SolanaRPC_tcp
low_port = tcp:8899
port = tcp:8899
description = Solana JSON-RPC node (Agave mainnet, solana-test-validator, Surfpool).
enabled = Yes
persona = mainnet
Default sections:
| Section | Port | Persona | Notes |
|---|---|---|---|
SolanaRPC |
8899 | mainnet |
HTTP JSON-RPC. |
SolanaRPC.ws |
8900 | mainnet |
PubSub WebSocket; sets transport = websocket. |
Either section can run alone. The two sections share one node (chain state, slot and subscription IDs) only when their persona and seed are both the same, so give both the same persona.
Config¶
Service section keys
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | "mainnet" |
One of mainnet, test-validator, surfpool (case-sensitive), or a persona you add in the JSON file. Precedence: this key, then persona in the JSON file, then the built-in document, then mainnet. An unknown or invalid persona logs a warning and falls back to the built-in mainnet; the plugin still starts. |
transport |
string | "http" |
websocket (case-insensitive) makes the section the PubSub endpoint; any other value serves HTTP JSON-RPC. |
config |
path | none | Optional JSON file of overrides. |
The JSON file is a partial document containing only the keys to change; it is merged over the built-in document. The merge is by key: limits is merged key by key, seed is replaced, and a persona with the same name as a built-in one is replaced as a whole. No file ships in packages.
JSON file keys
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | "mainnet" |
Persona to use when the service section sets none. |
seed |
string | "" |
Seed for chain state and identities. An empty value derives it from the host identity; set it to pin state. |
limits |
object | see below | Resource and event limits. Non-integer, non-positive or unknown values log a warning and keep the default. |
personas |
object | built-in personas | Persona name to persona definition; add one or replace a built-in one by name. |
A persona definition requires kind, version, feature_set, genesis_hash, cluster, anchor, slot_ms, epoch_schedule, skip_per_mille, transactions_per_slot, ledger_retention_slots, snapshot_interval_slots, airdrop, shred_version, cluster_nodes, vote_accounts, delinquent_vote_accounts, prioritization_fees, supply, inflation, max_tx_version_error and ws_server_header. Unknown keys are rejected, apart from the optional surfnet_version and cheatcodes. A rejected persona logs a warning and falls back to the built-in mainnet.
Limits (all under limits)
| Key | Default | Description |
|---|---|---|
max_request_bytes |
51200 |
HTTP request body cap (Agave's own limit); larger bodies receive 413. |
max_header_bytes |
65536 |
Header block cap. |
max_chunk_line_bytes |
1024 |
Chunk size line cap. |
idle_timeout_seconds |
30 |
HTTP idle timeout. |
request_deadline_seconds |
60 |
Whole-request deadline. |
max_requests_per_connection |
1000 |
Keep-alive request cap. |
max_requests_per_read |
16 |
Pipelined requests handled per read. |
max_unflushed_bytes |
4194304 |
Abort a client that does not read its responses. |
max_tx_event_bytes |
65536 |
Cap on transaction event data. |
max_batch |
2048 |
Calls per batch. |
capture_scan_limit |
64 |
Members of an oversized batch still captured. |
max_response_bytes |
8388608 |
Encoded reply budget per request. |
large_response_bytes |
262144 |
Accepted and validated, but not read by any code path at this release; which calls count as large is fixed in the plugin. |
large_responses_per_source_per_minute |
2 |
Multi-megabyte replies (such as an unfiltered mainnet getLeaderSchedule) per source per minute; beyond that the call gets -32603. |
ws_max_handshake_bytes |
8192 |
WebSocket handshake cap. |
ws_max_frame_bytes |
65536 |
WebSocket frame cap. |
ws_max_message_bytes |
65536 |
WebSocket message cap. |
ws_idle_timeout_seconds |
300 |
WebSocket idle timeout. |
ws_max_lifetime_seconds |
3600 |
Maximum WebSocket connection lifetime. |
ws_handshake_timeout_seconds |
10 |
Time allowed to complete the handshake. |
ws_max_unflushed_bytes |
1048576 |
Abort a WebSocket client that does not read. |
ws_max_tx_event_bytes |
65536 |
Cap on transaction event data from notifications. |
max_subscriptions_per_connection |
64 |
Subscriptions per connection. |
max_subscriptions_per_node |
4096 |
Subscriptions per node. |
max_ws_connections |
256 |
Concurrent WebSocket connections. |
max_notification_tx_events |
20 |
Notifications per connection logged as transaction events; the rest are counted in the rpc_call summary. |
max_signatures |
4096 |
Submitted signatures tracked. |
max_signature_bytes |
4194304 |
Bytes of tracked signature data. |
max_airdrop_entries |
4096 |
Airdrop entries tracked. |
max_rate_entries |
8192 |
Source addresses tracked by rate limits. |
info_field_max_chars |
8192 |
Cap on bounded text fields in INFO events. |
max_rpc_call_events_per_batch |
32 |
rpc_call events per request body. |
max_capture_events_per_batch |
16 |
High-value events per request body. |
max_rpc_call_summaries |
32 |
rpc_call summaries emitted at disconnect. |
max_routine_events_per_connection |
200 |
Routine events per connection. |
max_high_value_events_per_connection |
64 |
High-value events per connection. |
routine_events_per_source_per_minute |
120 |
Routine events per source address per minute. |
high_value_events_per_source_per_minute |
60 |
High-value events per source address per minute. |
routine_events_per_second |
50 |
Routine events per second, plugin-wide. |
high_value_events_per_second |
20 |
High-value events per second, plugin-wide. |
Personas¶
| Persona | Emulates | Notes |
|---|---|---|
mainnet |
Agave 4.3.0 on mainnet-beta | Real mainnet genesis hash; requestAirdrop returns -32600, as on an RPC node without a faucet; getClusterNodes lists real public gossip entries plus synthetic validators. |
test-validator |
solana-test-validator (Agave 4.3.0) |
Per-agent genesis hash and shred version; single-node cluster; pruned ledger; requestAirdrop succeeds and credits the balance. |
surfpool |
Surfpool 1.6.0 (solana-core 4.2.1) | Mainnet genesis; airdrop succeeds; accepts surfnet_* cheat codes. |
The WebSocket endpoint supports slotSubscribe, rootSubscribe, accountSubscribe, programSubscribe, logsSubscribe and signatureSubscribe with their unsubscribe methods; any other method returns Method not found.
INFO Events¶
Every event carries event_type and schema_version. Only the agent's own address is scrubbed; attacker IPs and public keys are preserved. Lamport and token quantities are decimal strings.
| Event | Description |
|---|---|
client_fingerprint |
First request on a connection, and again on HTTP whenever the path, user agent, origin, content type or JSON-RPC version changes: those fields plus transport (http or websocket). |
rpc_call |
Each distinct method per request body with parameter count, batch size and whether the method is known; repeats are summarized per connection (and per WebSocket subscription). |
transaction_submit |
sendTransaction or simulateTransaction: encoding, signature, fee payer, recent blockhash, program IDs, decoded transfers, approvals, authority changes, lookup tables, memos, compute budget settings and the raw transaction (up to 2048 characters). |
airdrop_request |
requestAirdrop: public key and lamports. |
account_query |
Account, program, token-account, signature-history and multi-account queries; a single-account query is recorded only when it names one of this agent's lure keys. |
cheatcode_call |
A surfnet_* call: method and parameters. |
subscription |
A WebSocket subscribe call: method and parameters. |
snapshot_request |
A snapshot download request: path and kind (full, incremental, genesis or snapshot_file). |
events_dropped |
A volume limit dropped events: scope, event class and count. |
An unknown method (for example eth_chainId) returns -32601 and is still recorded.
SSH_tcp¶
A medium interaction TCP SSH service based on Cowrie. To authenticate, use the user root with almost any password; Cowrie's default user database refuses a few, such as root, 123456 and any password containing honeypot.
Example service configuration:
[SSH]
plugin = SSH_tcp
low_port = tcp:22
port = tcp:22
description = SSH service.
enabled = Yes
Config¶
These options are set in the service section of services.conf and are off by default. They require HoneyDB Agent 2.10.0 or later.
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | none | Crypto node persona: ethereum, solana or bitcoin. See Crypto node persona below. An unknown value is ignored with a warning. |
persona_hostname |
string | eth-validator-NN, sol-validator-NN or btc-node-NN, by persona |
Hostname the persona shows. A single label of letters, digits and hyphens, up to 63 characters, with no leading or trailing hyphen. An invalid value is replaced by the default with a warning. NN is a two-digit number from 01 to 19 chosen once per agent. |
Crypto node persona¶
Setting persona makes every session look like a blockchain node, so attackers hunting for wallets and keys have something to find.
[SSH]
plugin = SSH_tcp
low_port = tcp:22
port = tcp:22
description = SSH service.
enabled = Yes
persona = ethereum
persona_hostname = eth-rpc-2
| Persona | Default hostname | Lure files under the session home (~) |
ps auxww shows |
|---|---|---|---|
ethereum |
eth-validator-NN |
.ethereum/keystore/UTC--<ts>--<address> (Web3 secret-storage v3), validator_keys/keystore-m_12381_3600_0_0_0-<ts>.json (EIP-2335), .env, docker-compose.yml |
geth, lighthouse bn, lighthouse vc |
solana |
sol-validator-NN |
.config/solana/id.json, validator-keypair.json, vote-account-keypair.json, .env, docker-compose.yml |
agave-validator |
bitcoin |
btc-node-NN |
.bitcoin/wallet.dat, .bitcoin/bitcoin.conf (rpcuser/rpcpassword), .lnd/data/chain/bitcoin/mainnet/admin.macaroon, .env, docker-compose.yml |
bitcoind, lnd |
The .env file depends on the persona. ethereum has PRIVATE_KEY, MNEMONIC, an RPC URL, KEYSTORE_PASSWORD, WALLET_ADDRESS and CHAIN_ID; solana has SOLANA_RPC_URL, SOLANA_KEYPAIR_PATH, a base58 PRIVATE_KEY and MNEMONIC; bitcoin has BITCOIN_RPC_URL, BITCOIN_RPC_USER, BITCOIN_RPC_PASSWORD, MNEMONIC and LND_MACAROON_PATH. The lure files are visible to ls, cat, find, stat, cp, tar, base64, scp and SFTP, and /etc/hostname shows the persona hostname.
- Scope. The embedded Cowrie emulator is one per agent, so the persona applies to every
SSH_tcpandTelnet_tcpservice in the agent, including sections that do not setpersona. The first section that setspersonaalso suppliespersona_hostname. If two sections ask for different personas, the agent logs a warning and runs with no persona at all. - Synthetic material. The keys are generated locally on first run, are never funded, and are unique to each agent: a checksum-valid 24-word BIP-39 mnemonic, Ed25519 Solana keypairs, a Web3 keystore that decrypts to the
.envprivate key, and an EIP-2335 keystore.HTTP_tcpserves the same material on its lure paths. - State directory. The material is kept in
crypto-lures.json(mode 0600) in thestate_dirset in the[agent]section ofagent.conf, which defaults to the directory holdingagent.conf. If that directory is not writable, the agent logs a warning and generates new material on every start. A store that fails validation is renamed tocrypto-lures.json.bad-<timestamp>and regenerated.
INFO Events¶
SSH session INFO events are produced by the Cowrie bridge. Shell input is logged as RX events.
| Event | Description |
|---|---|
login_success |
Captures the username and password of a successful login. |
login_failed |
Captures the username and password of a failed login. |
client_version |
Captures the SSH client version string. |
client_fingerprint |
Captures the client public key (type and key) with its HASSH fingerprint. |
client_key_exchange |
Captures the client's HASSH key exchange fingerprint. |
client_terminal_size |
Captures the terminal width and height requested by the client. |
client_request_env |
Captures an environment variable name and value requested by the client. |
client_arch |
Captures the client architecture reported for the session. |
file_download |
Captures the URL and hash of a file the attacker downloaded from the shell. |
session_summary |
Captures the session duration in seconds. |
cryptomining_indicators |
Fires when a shell command line contains mining pools, wallets or miner names (see Events). source_field is command. An identical finding is reported once per session. Downloads are reported as file_download, so no dropper_indicators event is emitted. |
crypto_recon_command |
Fires, with or without a persona, when a command searches for wallets or keys. pattern is find_wallet_files, grep_key_material, list_wallet_dirs, node_cli or node_process_recon. |
crypto_lure_access |
Fires only while a persona is active, when a command reads, copies, archives, encodes or sends a lure file (cat, cp, scp, tar, base64, curl, nc). lure_kind is eth_keystore, eth2_validator_keystore, bitcoin_wallet, bitcoin_conf, solana_keypair, lnd_macaroon, dotenv or docker_compose. SFTP downloads of a lure produce no event. |
cve_attempt |
Fires on a CVE detection (see CVEs Detected below). |
For crypto_recon_command and crypto_lure_access, command is capped at 1024 characters and path at 256, and each session reports an identical finding once and at most 32 events of these two types. Only the agent's own IP address is scrubbed, so attacker hosts in a command, such as the destination of an scp, are preserved.
CVEs Detected¶
| CVE | Short name | Detection |
|---|---|---|
| CVE-2021-44228 | log4shell_jndi |
Log4Shell JNDI lookup (${jndi:<scheme>://host/...}, plain or obfuscated) in the login username or password, shell input, the client version string or an environment request. |
| CVE-2025-32433 | erlang_otp_ssh_preauth_channel |
Erlang/OTP SSH pre-authentication channel handling: a key exchange packet with an all-zero cookie, or an SSH connection-protocol message received before authentication succeeds. |
SubstrateRPC_tcp¶
A high interaction TCP service that emulates the JSON-RPC server of a Substrate node (Parity Polkadot 1.24.2). One port serves both HTTP POST JSON-RPC and WebSocket JSON-RPC with subscriptions, as the real node does; the protocol is detected on connect. It answers as a Polkadot or Kusama mainnet node, as the same node started with --rpc-methods unsafe, or as polkadot --dev. It captures probes of validator infrastructure: session key insertions (with the secret URI), key rotations, reserved-peer changes, submitted extrinsics decoded to signer, destination and amount, and storage and runtime queries. Nothing is executed or relayed: state an attacker changes is kept per source address in memory only, signatures and balances are not checked, and the service opens no outbound connection. Real runtime metadata ships with the agent.
Example service configuration:
[SubstrateRPC]
plugin = SubstrateRPC_tcp
low_port = tcp:9944
port = tcp:9944
description = Substrate / Polkadot JSON-RPC node (HTTP and WebSocket).
enabled = Yes
persona = polkadot
Default sections (all ship with enabled = No):
| Section | Port | Persona | Notes |
|---|---|---|---|
[SubstrateRPC] |
9944 | polkadot |
HTTP and WebSocket. |
[SubstrateRPC.legacy] |
9933 | polkadot |
The legacy HTTP port. Sections with the same persona share one emulated node, so a key inserted over 9933 is visible over 9944. |
Keep tls = No: the real node speaks plain HTTP and WebSocket (TLS is added by a reverse proxy). The ports do not conflict with another shipped section.
Personas¶
| Persona | Emulates |
|---|---|
polkadot |
Polkadot mainnet node. Unsafe RPC calls are rejected with the real error and still captured with accepted: false. |
kusama |
Kusama mainnet node. Unsafe RPC calls are rejected and captured. |
polkadot-unsafe |
A Polkadot node started with --rpc-methods unsafe: author_insertKey, author_rotateKeys, system_addReservedPeer and similar calls are accepted and captured. |
dev |
polkadot --dev: unsafe RPC accepted and captured, two well-known development accounts (Alice and Bob). |
Config¶
A config JSON file is a partial document containing only the keys to change, deep-merged over the bundled document: nested objects are merged key by key. The top-level keys accepted are persona, default_persona, seed, limits, personas, methods, unsafe_methods, unsafe_message, storage_keys and storage_labels; one unknown top-level key makes the whole document fall back to the built-in persona. No file ships in packages; create your own.
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | polkadot |
polkadot, kusama, polkadot-unsafe or dev. The value is only trimmed, not lowercased: use the exact lowercase spelling. Falls back to the file's persona, then default_persona; an unusable name uses polkadot with a logged warning. |
config |
string | none | Path to a JSON file as described above. |
tls |
string | No |
Leave at No. |
JSON file keys:
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | none | Persona used when the section has no persona. |
default_persona |
string | polkadot |
Persona used when neither the section nor the file names one. |
seed |
string | "" (derived per agent) |
Pins the node identity and chain data. When empty each agent derives its own, stable across restarts. |
limits |
object | see below | Resource and rate limits. A value outside its allowed range is clamped with a logged warning. |
personas |
object | four personas | Add or replace personas. |
Commonly set limits keys (allowed range in parentheses):
| Key | Type | Default | Description |
|---|---|---|---|
max_batch |
int | 1000 |
Calls accepted in one batch (1 to 10000). |
max_request_bytes |
int | 1048576 |
Largest request body (1024 to 16777216). |
max_subscriptions |
int | 16 |
Subscriptions per connection (1 to 1024). |
max_notifications_per_connection |
int | 2000 |
Notifications sent before the connection is closed. |
max_ws_connections |
int | 64 |
Concurrent WebSocket connections. |
max_ws_connections_per_source |
int | 8 |
Concurrent WebSocket connections per source address. |
max_requests_per_connection |
int | 1000 |
Requests per HTTP connection. |
idle_timeout_seconds |
int | 30 |
HTTP idle timeout. |
ws_idle_timeout_seconds |
int | 300 |
WebSocket idle timeout. |
ws_max_lifetime_seconds |
int | 3600 |
Longest WebSocket connection lifetime. |
large_responses_per_source_per_minute |
int | 30 |
Rate limit for large replies such as metadata (about 0.9 MB each). |
max_high_value_events_per_connection |
int | 64 |
High-value events per connection. |
max_routine_events_per_connection |
int | 200 |
Routine events per connection. |
high_value_events_per_source_per_minute |
int | 60 |
High-value events per source per minute. |
routine_events_per_source_per_minute |
int | 120 |
Routine events per source per minute. |
info_field_max_chars |
int | 4096 |
Truncation of captured text fields. It does not govern suri, which is fixed at 4096 characters. |
The bundled limits object also holds the remaining transport, WebSocket, session and rate bounds; values are clamped to their ranges.
INFO Events¶
Every event is JSON with event_type and schema_version: 1, and carries only what the client sent. accepted states whether the persona honours unsafe calls, not whether the call individually succeeded. Only the agent's own address is scrubbed; secret URIs, multiaddr hosts and peer ids, and signer and destination addresses are preserved.
| Event | Description |
|---|---|
client_fingerprint |
Once per connection: User-Agent, Origin, path and transport (http or websocket). |
rpc_call |
Each call: method, parameter count, batch size, count and whether the method is known. |
key_insert_attempt |
author_insertKey: key type, secret URI in full (up to 4096 characters), public key and accepted. |
key_rotate_attempt |
author_rotateKeys or author_rotateKeysWithOwner: method, owner and accepted. |
key_query |
author_hasKey or author_hasSessionKeys: public key or session keys and accepted. |
peer_add_attempt |
system_addReservedPeer or system_removeReservedPeer: multiaddr, peer id, host, host kind, port and transport protocols. |
extrinsic_submit |
author_submitExtrinsic, author_submitAndWatchExtrinsic, transactionWatch_v1_submitAndWatch or transaction_v1_broadcast: decoded signer, signature type, nonce, tip, era, pallet and call, destination, amount and raw bytes. |
extrinsic_query |
system_dryRun, system_dryRunAt, payment_queryInfo, payment_queryFeeDetails or the TransactionPaymentApi_query_info runtime call: the same decoded fields. |
node_config_attempt |
Log filter changes, offchain storage writes and author_removeExtrinsic: method, parameters and accepted. |
storage_query |
state_* and childstate_* storage, key, proof and call methods, and chainHead_v1_storage or chainHead_v1_call: keys, labels, accounts and block. |
subscription |
Each subscribe method over WebSocket: method and parameters. |
events_dropped |
Marker that events were dropped by a rate limit. |
Telnet_tcp¶
A medium interaction TCP Telnet service. This service will provide generic guessable logins and emulate basic commands post login. It is based on the same embedded Cowrie emulator as SSH_tcp; to authenticate, use the user root with almost any password (Cowrie's default user database refuses a few, such as root, 123456 and any password containing honeypot).
Example service configuration:
[Telnet]
plugin = Telnet_tcp
low_port = tcp:23
port = tcp:23
description = Telnet service.
enabled = Yes
Config¶
These options are set in the service section of services.conf and are off by default. They require HoneyDB Agent 2.10.0 or later.
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | none | Crypto node persona: ethereum, solana or bitcoin. See Crypto node persona below. An unknown value is ignored with a warning. |
persona_hostname |
string | eth-validator-NN, sol-validator-NN or btc-node-NN, by persona |
Hostname the persona shows. A single label of letters, digits and hyphens, up to 63 characters, with no leading or trailing hyphen. An invalid value is replaced by the default with a warning. NN is a two-digit number from 01 to 19 chosen once per agent. |
Crypto node persona¶
Setting persona makes every session look like a blockchain node, so attackers hunting for wallets and keys have something to find.
[Telnet]
plugin = Telnet_tcp
low_port = tcp:23
port = tcp:23
description = Telnet service.
enabled = Yes
persona = ethereum
persona_hostname = eth-rpc-2
| Persona | Default hostname | Lure files under the session home (~) |
ps auxww shows |
|---|---|---|---|
ethereum |
eth-validator-NN |
.ethereum/keystore/UTC--<ts>--<address> (Web3 secret-storage v3), validator_keys/keystore-m_12381_3600_0_0_0-<ts>.json (EIP-2335), .env, docker-compose.yml |
geth, lighthouse bn, lighthouse vc |
solana |
sol-validator-NN |
.config/solana/id.json, validator-keypair.json, vote-account-keypair.json, .env, docker-compose.yml |
agave-validator |
bitcoin |
btc-node-NN |
.bitcoin/wallet.dat, .bitcoin/bitcoin.conf (rpcuser/rpcpassword), .lnd/data/chain/bitcoin/mainnet/admin.macaroon, .env, docker-compose.yml |
bitcoind, lnd |
The .env file depends on the persona. ethereum has PRIVATE_KEY, MNEMONIC, an RPC URL, KEYSTORE_PASSWORD, WALLET_ADDRESS and CHAIN_ID; solana has SOLANA_RPC_URL, SOLANA_KEYPAIR_PATH, a base58 PRIVATE_KEY and MNEMONIC; bitcoin has BITCOIN_RPC_URL, BITCOIN_RPC_USER, BITCOIN_RPC_PASSWORD, MNEMONIC and LND_MACAROON_PATH. The lure files are visible to ls, cat, find, stat, cp, tar, base64, scp and /etc/hostname shows the persona hostname.
- Scope. The embedded Cowrie emulator is one per agent, so the persona applies to every
SSH_tcpandTelnet_tcpservice in the agent, including sections that do not setpersona. The first section that setspersonaalso suppliespersona_hostname. If two sections ask for different personas, the agent logs a warning and runs with no persona at all. - Synthetic material. The keys are generated locally on first run, are never funded, and are unique to each agent: a checksum-valid 24-word BIP-39 mnemonic, Ed25519 Solana keypairs, a Web3 keystore that decrypts to the
.envprivate key, and an EIP-2335 keystore.HTTP_tcpserves the same material on its lure paths. - State directory. The material is kept in
crypto-lures.json(mode 0600) in thestate_dirset in the[agent]section ofagent.conf, which defaults to the directory holdingagent.conf. If that directory is not writable, the agent logs a warning and generates new material on every start. A store that fails validation is renamed tocrypto-lures.json.bad-<timestamp>and regenerated.
INFO Events¶
Telnet session INFO events are produced by the same Cowrie bridge as SSH_tcp, including the events below. Shell input is logged as RX events.
| Event | Description |
|---|---|
login_success |
Captures the username and password of a successful login. |
login_failed |
Captures the username and password of a failed login. |
client_request_env |
Captures an environment variable name and value sent by the client during Telnet option negotiation (one event per variable). |
file_download |
Captures the URL and hash of a file the attacker downloaded from the shell. |
session_summary |
Captures the session duration in seconds. |
cryptomining_indicators |
Fires when a shell command line contains mining pools, wallets or miner names (see Events). source_field is command. An identical finding is reported once per session. Downloads are reported as file_download, so no dropper_indicators event is emitted. |
crypto_recon_command |
Fires, with or without a persona, when a command searches for wallets or keys. pattern is find_wallet_files, grep_key_material, list_wallet_dirs, node_cli or node_process_recon. |
crypto_lure_access |
Fires only while a persona is active, when a command reads, copies, archives, encodes or sends a lure file (cat, cp, scp, tar, base64, curl, nc). lure_kind is eth_keystore, eth2_validator_keystore, bitcoin_wallet, bitcoin_conf, solana_keypair, lnd_macaroon, dotenv or docker_compose. |
cve_attempt |
Fires on a CVE detection (see CVEs Detected below). |
For crypto_recon_command and crypto_lure_access, command is capped at 1024 characters and path at 256, and each session reports an identical finding once and at most 32 events of these two types. Only the agent's own IP address is scrubbed, so attacker hosts in a command, such as the destination of an scp, are preserved.
CVEs Detected¶
| CVE | Short name | Detection |
|---|---|---|
| CVE-2026-24061 | telnet_auth_bypass |
Telnet authentication bypass: a username injected during Telnet option negotiation bypasses the password prompt. Indicators are injected_username, original_username and attempted_command. |
| CVE-2021-44228 | log4shell_jndi |
Log4Shell JNDI lookup (${jndi:<scheme>://host/...}, plain or obfuscated) in the login username or password, shell input, the CVE-2026-24061 exploit fields, or any environment variable value (field env:<name>). |
| CVE-2000-0733 | sgi_telnetd_format_string |
SGI IRIX telnetd format-string bug: a printf-style format-string payload (%n, a positional %N$ form, or two or more conversions such as %x%x) in a NEW-ENVIRON variable value, the login username or password, shell input, or the CVE-2026-24061 exploit fields (telnet.exploit_success); or a _RLD* environment variable carrying bin/sh. |
TFTP_udp¶
A low interaction UDP TFTP service. Serves a configurable decoy file for any read request and accepts write uploads (logging the first bytes without writing to disk). Detects abuse patterns including path traversal and IoT firmware download attempts.
Example service configuration:
[TFTP]
plugin = TFTP_udp
low_port = udp:69
port = udp:69
description = TFTP service.
enabled = Yes
config = /etc/honeydb/tftp.json
Config¶
| Key | Type | Default | Description |
|---|---|---|---|
decoy_netascii |
string or null | null |
Text content served for netascii read requests. Falls back to a built-in decoy file if absent. |
decoy_octet |
string or null | null |
Base64-encoded bytes served for octet read requests. Falls back to a built-in decoy file if absent. |
upload_capture_bytes |
int | 512 |
Maximum bytes captured from each upload for logging. |
max_upload_size_bytes |
int | 2000384 |
Maximum total upload size before an error packet is sent. |
session_timeout_seconds |
int | 60 |
Sessions with no activity for this many seconds are cleaned up. |
max_active_sessions |
int | 1000 |
Cap on concurrent tracked sessions. |
iot_firmware_keywords |
list | [] |
Additional filename keywords that trigger IoT firmware detection. Merged with the built-in list (firmware, boot.img, uimage, bzimage, vmlinuz, rootfs, kernel.img, factory.bin). |
INFO Events¶
| Event | Description |
|---|---|
get_file |
Captures the filename and mode from an RRQ (read request). |
put_file |
Captures the filename and mode from a WRQ (write request). |
upload_data |
Captures buffered hex of an uploaded file (blocks received, completion/truncation flags). |
abuse_pattern |
Fires on path traversal, IoT firmware keywords, invalid transfer mode, or binary-in-netascii. |
parse_error |
Fires when a datagram cannot be parsed; captures the error and raw hex. |
VNC_tcp¶
A low interaction TCP VNC service (RFB protocol). Implements the RFB handshake (versions 3.3/3.7/3.8), configurable security-type negotiation, dictionary-attack VNC authentication, and a synthetic post-auth desktop served as a static screenshot. Captures handshake details, authentication attempts, and post-auth keyboard/mouse/clipboard activity. Detects CVE-2006-2369 (RealVNC authentication bypass) probes.
Example service configuration:
[VNC]
plugin = VNC_tcp
low_port = tcp:5900
port = tcp:5900
description = Low interaction VNC service
enabled = Yes
config = /etc/honeydb/vnc.json
Config¶
| Key | Default | Description |
|---|---|---|
rfb_version |
"003.008" |
One of "003.003", "003.007", "003.008". |
security_types_preset |
"honeypot-default" |
One of "tightvnc-like", "tigervnc-like", "realvnc-like", "honeypot-default". |
security_types |
null |
Explicit list of security type integers (overrides preset when set). |
server_name |
"ubuntu-server" |
Desktop name sent to the client. |
framebuffer.width / framebuffer.height |
1024 / 768 |
Synthetic framebuffer dimensions. Screenshot images that don't match are excluded at load time. |
screenshots.directory |
null |
Directory of same-dimension images. One is picked per source IP, so the same scanner sees the same "desktop" across reconnects. |
screenshots.path |
null |
Single image (alternative to directory). |
screenshots.max_loaded |
32 |
Cap on cached screenshot images. |
password_wordlist_path |
null |
Optional path to a password wordlist used for VNC-Auth; falls back to an embedded ~100-entry default. |
tcp_idle_timeout_seconds |
60 |
Per-connection idle timeout. |
max_message_bytes |
8192 |
Per-message size cap. |
max_buffer_bytes |
65536 |
Per-connection buffer cap. |
post_auth.framebuffer_enabled |
true |
Switch for serving the screenshot framebuffer. |
post_auth.max_post_auth_messages_per_session |
200 |
Cap on post-auth client messages per session. |
structured_info_events |
true |
false falls back to human-readable INFO strings. |
tag_known_abuse |
true |
false suppresses abuse-pattern events. |
Security-type presets:
| Preset | Advertised types | Emulates |
|---|---|---|
tightvnc-like |
[16, 2] |
TightVNC server |
tigervnc-like |
[2, 19] |
TigerVNC server |
realvnc-like |
[2] |
RealVNC Free |
honeypot-default |
[1, 2, 16, 19] |
Maximally attractive — all four probe surfaces |
Note
Operator-supplied screenshot images are sent verbatim to attackers — never include real desktop content; use synthetic or generic images only.
INFO Events¶
| Event | Description |
|---|---|
rfb_handshake |
Captures the server/client RFB versions and downgrade flag. |
security_choice |
Captures the advertised security types and the selected type. |
auth_attempt |
Captures the VNC-auth challenge/response and any matched wordlist password. |
client_init |
Captures the ClientInit shared-desktop flag. |
set_pixel_format |
Captures the client-supplied pixel format. |
set_encodings |
Captures the client's advertised encoding list and total count. |
framebuffer_update_request |
Captures the requested rectangle (x/y/w/h) and incremental flag. |
key_event |
Captures keyboard events (keysym, down flag, ASCII). |
pointer_event |
Captures pointer position and button mask. |
client_cut_text |
Captures clipboard cut-text length and content. |
abuse_pattern |
Fires on CVE/fingerprint probes (e.g. CVE-2006-2369) and malformed/oversize/unknown client messages. |
XMRigAPI_tcp¶
A low interaction TCP service that emulates the HTTP API of an XMRig 6.26.0 CPU miner on Linux. It serves GET /1/summary, /2/summary, /2/backends, /1/config and /2/config, and accepts PUT or POST to the config paths (answered 204; the upload is stored per source IP, shown back on the next read, and its pool is reported as connected after settle_seconds). POST /json_rpc pause, resume and stop succeed and change that source's summary. Every Authorization header, uploaded config and pool in an upload is captured, which exposes cryptojacking hijack attempts. Nothing is executed or written to disk, no pool is contacted and no host name is resolved. State is kept in memory only.
Example service configuration:
[XMRigAPI]
plugin = XMRigAPI_tcp
low_port = tcp:44444
port = tcp:44444
description = XMRig miner HTTP API.
enabled = Yes
persona = xmrig_6_26_linux
auth_mode = any_token
XMRig has no default API port; 44444 is the port its documentation uses.
Authorization modes:
auth_mode |
Behavior |
|---|---|
any_token |
No Authorization header gets 401. Any Bearer token is accepted and logged; a non-Bearer scheme gets 403. |
token |
Only the access_token from the JSON file is accepted; other bearer tokens get 403. |
restricted |
Reads work without a token; GET config and every write get 403. Any Authorization header gets 401. |
In every mode that lets a write through (any_token and token), a write must carry exactly Content-Type: application/json; anything else, including application/json; charset=utf-8, gets 415.
The request body is captured before the reply is chosen, so a refused upload still yields pools and wallets. A config above max_config_bytes_per_source (16384) still gets 204 but is not stored.
Config¶
Service section keys:
| Key | Type | Default | Description |
|---|---|---|---|
persona |
string | xmrig_6_26_linux |
Miner persona, case-insensitive. xmrig_6_26_linux is the only persona shipped. An unknown name falls back to the default and logs a warning. |
auth_mode |
string | any_token |
any_token, token or restricted (case-insensitive). Overrides the JSON auth_mode. An unknown value falls back to any_token, as does token when no access_token is set. |
config |
string | none | Path to a JSON file merged over the built-in document. |
JSON file keys:
The file is a partial document containing only the keys to change, merged over the document built into the agent. The merge is shallow: a top-level key you supply replaces the built-in value, except that personas is merged by persona name, so a persona you define replaces the built-in persona of the same name as a whole. A supplied limits object replaces the built-in one, and any key it omits takes its default. No JSON file ships in packages.
| Key | Type | Default | Description |
|---|---|---|---|
default_persona |
string | xmrig_6_26_linux |
Persona used when the section does not set one. |
seed |
string | "" |
Seed for the per-agent identity. Empty derives it from the agent identity. |
auth_mode |
string | any_token |
Used when the service section sets no auth_mode. |
access_token |
string | "" |
Token accepted in token mode. This key is read only from the JSON file, never from services.conf. |
personas |
object | one built-in persona | Miner definitions keyed by persona name, including default_config, the configuration the miner serves before any upload. |
limits |
object | see below | Each value must be a positive number; an invalid value keeps its default. |
Keys in limits (name and default): max_request_bytes 262144, max_header_bytes 16384, idle_timeout_seconds 15, request_deadline_seconds 30, max_requests_per_connection 1, max_connections 256, max_connections_per_source 16, max_sources 1024, source_ttl_seconds 86400, max_config_bytes_per_source 16384, max_pools_per_upload 16, max_info_events 200, max_api_request_events 50, max_scans_per_connection 16, info_events_per_minute_per_source 240, settle_seconds 20.
INFO Events¶
Only the agent's own address is scrubbed; attacker pools, hosts, wallets and tokens are preserved. Values are clipped to 512 characters. The envelopes cryptomining_indicators, dropper_indicators and info_event_cap_reached are emitted as described in the Events section. Under the per-source rate limit, routine miner_api_command events are dropped first.
| Event | Description |
|---|---|
api_credential_presented |
An Authorization header: scheme, value, path and method. |
config_write_attempt |
A PUT or POST to a config path, whatever the reply: size, SHA-256, whether it was authorized, pool count, the http and api settings, donate_level, and the top-level key names. parse_error is set only when the body fails to parse; valid JSON that is not an object is recorded raw without it. raw is capped at 2048 characters, and truncated and incomplete flags mark a body that was cut short. |
pool_change_attempt |
One per pool in an uploaded config (first 16): URL, user, password, index and path, plus algorithm, coin, rig id, TLS, NiceHash, daemon, SOCKS5 and self-select settings when sent. |
restart_attempt |
A JSON-RPC pause, resume, stop or start call. |
miner_api_command |
Every request: HTTP verb or JSON-RPC method, path, whether it is privileged, and parameters. |
cryptomining_indicators |
Shared envelope for pools, wallets and miner names found in an upload. |
dropper_indicators |
Shared envelope for download tools, URLs, hosts and filenames found in an upload. |
info_event_cap_reached |
Marker emitted once when the per-connection event cap (max_info_events) is reached. |