Glutton has two output paths: process logs through Go slog, and optional producer events sent to HTTP or hpfeeds sinks. Handlers can emit both, but they're configured separately.
producer.NewLogger(...) creates a JSON slog logger that writes every record to:
- stdout
- the path configured by
--logpath
The file writer uses lumberjack rotation: 200 MB max size, 356 days max age, compression on. Every record carries the sensorID attribute (read from / written to <var-dir>/glutton.id).
The --debug flag is parsed but not wired into slog.HandlerOptions, so it does not currently lower the log level.
Producer events follow the producer.Event schema:
| JSON field | Meaning |
|---|---|
timestamp |
UTC event timestamp. |
transport |
tcp or udp. |
srcHost |
Source IP. |
srcPort |
Source port. |
dstPort |
Original destination port from metadata. |
sensorID |
Glutton sensor ID. |
rule |
Rule string when metadata includes a rule. |
handler |
Handler name supplied by the protocol handler. |
payload |
Base64-encoded payload bytes. |
scanner |
Scanner classification from scanner.IsScanner(...). |
decoded |
Handler-specific decoded data. |
Events are emitted only when (1) producers.enabled is true so a producer object exists, (2) a handler calls ProduceTCP(...) or ProduceUDP(...), (3) the matched rule does not set produce: false, and (4) at least one sink is enabled. Before output, configured addresses values are scrubbed from the payload and replaced with 1.2.3.4.
Example shape:
{
"timestamp": "2026-05-15T12:00:00Z",
"transport": "tcp",
"srcHost": "203.0.113.10",
"srcPort": "54321",
"dstPort": 80,
"sensorID": "00000000-0000-0000-0000-000000000000",
"rule": "Rule: tcp",
"handler": "http",
"payload": "R0VUIC8gSFRUUC8xLjENCg0K",
"scanner": "",
"decoded": [
{
"direction": "read",
"command": "GET",
"path": "/",
"session_id": "11111111-1111-1111-1111-111111111111",
"payload": "R0VUIC8gSFRUUC8xLjENCg0K"
}
]
}decoded is handler-specific:
| Handler | decoded |
Notes |
|---|---|---|
http (Go) |
Array of per-direction frames: direction, command, path, query, session_id, payload |
Keep-alive HTTP. command is the HTTP method. Responses set a session cookie; frames that share that cookie (per source host) are grouped into one produced event when the session idles out (conn_timeout). Shares the idle session table with mcp. |
http (Spicy) |
{method, url, path, query} |
Request body is the event payload. |
tcp |
Array of per-direction frames: direction, payload, payload_hash |
Catch-all. Client bytes are one read frame (capped by max_tcp_payload); the honeypot replies with random bytes as a write frame. |
udp |
Array of read frames: direction, payload |
Catch-all. One datagram per event (capped at 1024 bytes). The handler does not reply. Datagrams with RakNet offline magic are rerouted to raknet; APPLICATION 10/12 Kerberos AS-REQ/TGS-REQ are rerouted to kerberos; CoAP version-1 GET/POST/PUT/DELETE datagrams are rerouted to coap. |
proxy_tcp, proxy_udp |
Per-direction entries: direction, payload, payload_hash, bytes, truncated |
Only when capture_traffic.enabled is true. Samples are capped by max_tcp_payload; truncated is whether more bytes were forwarded than captured. proxy_udp emits one event per flow when the flow idles out or closes. |
sip (TCP/UDP) |
Array of per-direction frames: direction, payload, message |
UDP OPTIONS probes get a write frame with the 200 OK response. |
openvpn (UDP) |
Array of read frames: direction, opcode, opcode_name, key_id, session_id (hex), payload |
The handler does not reply. |
mdns (UDP) |
Array of read frames: direction, questions (qname, qtype, qtype_name, qclass), payload |
The handler does not reply. |
l2tp (UDP) |
Array of per-direction frames: direction, message_type, message_name, host_name, vendor_name, tunnel_id, assigned_tunnel_id, ns, nr, payload |
SCCRQ probes get a write frame with an SCCRP reply. |
raknet (UDP) |
Array of read frames: direction, packet_id, packet_name, protocol, magic_ok, mtu, payload |
Minecraft Bedrock / RakNet. Parse-only; no Open Connection Reply or Unconnected Pong. 0x05 OCR1 sets protocol (byte after magic) and mtu (datagram length). Generic UDP peeks the same magic and reroutes here. |
kerberos (UDP) |
Array of read frames: direction, msg_type, msg_name, pvno, realm, sname, cname, etypes, from, nonce, payload |
Kerberos AS-REQ (msg_type 10) / TGS-REQ (12). Parse-only; no KRB-ERROR or AS-REP. Truncated or non-DER datagrams still emit a frame with raw payload (msg_name UNKNOWN). Generic UDP peeks APPLICATION 10/12 and reroutes here. |
coap (UDP) |
Array of per-direction frames: direction, type, code, code_name, message_id, token (hex), path, observe, payload |
One datagram per event. CON/NON GET, POST, PUT, DELETE get a matching ACK/NON reply (CONTENT / CREATED / DELETED). GET .well-known/core returns CoRE Link Format </ps/temp>,</ps/hum>. Truncated or invalid datagrams still emit a read frame (code_name UNKNOWN). Generic UDP peeks version-1 request codes 1–4 and reroutes here. |
mqtt (TCP) |
Array of per-direction frames: direction, packet, client_id, username, topic, topics, qos, payload |
One event per session. MQTT 3.1.1 remaining-length framing. CONNECT gets CONNACK accept; SUBSCRIBE gets SUBACK; QoS 1/2 PUBLISH get PUBACK/PUBREC; PINGREQ gets PINGRESP. payload is the raw packet including the header. Password bytes from CONNECT are not copied into decoded. |
memcache (TCP) |
Array of per-direction frames: direction, command, payload |
stats probes get a write frame with a short fake STAT/END block. set frames aggregate the command line plus the data chunk. |
modbus (TCP) |
Array of per-direction frames: direction, function_code, unit_id, address, quantity, payload |
One event per session. MBAP + PDU frames; payload is the full ADU. Writes echo the request transaction ID and unit ID. Function codes 1–6 / 15 / 16 are answered from per-session register maps (unread addresses read as zero). Unknown function codes get an illegal-function exception (function_code Exception). |
mongodb (TCP) |
Array of per-direction frames: direction, header, opcode_str, command, payload |
command is the BSON command name on read frames (hello, isMaster, buildInfo). OP_QUERY is answered with OP_REPLY and OP_MSG with OP_MSG; hello/isMaster get a fake handshake document (ismaster, maxWireVersion, …) and buildInfo a fake version/versionArray. |
mcp (TCP) |
Array of per-direction frames: direction, command, path, session_id, payload |
Streamable HTTP JSON-RPC. command is the JSON-RPC method (e.g. initialize, tools/list) or the HTTP verb. initialize gets a JSON-RPC result plus Mcp-Session-Id; the connection stays open for further requests. Shares the idle session table with HTTP. |
telnet (TCP) |
Array of per-direction frames: direction, message |
Telnet IAC negotiation is recorded as its own read frames so username/password/message values stay free of negotiation octets. |
smb (TCP) |
Array of per-direction frames: direction, header, command, path, payload, truncated |
Direct TCP (port 445) length-prefixed SMB1 and SMB2. command is the opcode name (SMB_COM_NT_CREATE_ANDX, SMB_COM_NT_TRANSACT, SMB2_NEGOTIATE, …). SMB1 NT Create AndX read frames set path to the requested filename (e.g. \svcctl). payload includes the 4-byte session header; truncated is set when the PDU exceeded the 256 KiB capture cap. SMB1 0x25 still returns STATUS_INSUFF_SERVER_RESOURCES; 0xA0 NT Transact gets a success stub so large follow-on bodies are stored; 0xA2 NT Create AndX gets WordCount 34 with a FID. See the skill reference. |
rdp (TCP) |
Array of per-direction frames: direction, header, payload |
header is the TPKT (empty on TLS stub frames). Connection Confirm is 11 bytes with no RDP_NEG_RSP when the client Connection Request omitted RDP_NEG_REQ, and 19 bytes selecting TLS|CredSSP when negotiation was requested. MCS Connect-Initial (X.224 DT) is a separate read frame and is answered with MCS Connect-Response, not a second X.224 CC. |
When producers.http.enabled is true, Glutton marshals each event as JSON and POSTs it to producers.http.remote with Content-Type: application/json. From source:
- HTTP client timeout: 10s; TLS handshake timeout: 5s.
- Events from private source IPs are skipped.
- Userinfo in the remote URL is used as HTTP Basic Auth.
- Query strings in the configured URL are preserved.
When producers.hpfeeds.enabled is true, Glutton connects to the broker at startup and publishes gob-encoded producer.Event values to producers.hpfeeds.channel.
producers:
hpfeeds:
enabled: false
host: 172.26.0.2
port: 20000
ident: ident
auth: auth
channel: testTreat all logged payloads as attacker-controlled data in downstream pipelines.