Refactor: implemented Repository Factory and Async Mutator Engine. Decoupled storage logic and enforced Dependency Injection across CLI and Web API. Updated documentation.
Some checks failed
CI / Lint (ruff) (push) Successful in 12s
CI / SAST (bandit) (push) Successful in 13s
CI / Dependency audit (pip-audit) (push) Successful in 22s
CI / Test (Standard) (3.11) (push) Failing after 54s
CI / Test (Standard) (3.12) (push) Successful in 1m35s
CI / Test (Live) (3.11) (push) Has been skipped
CI / Test (Fuzz) (3.11) (push) Has been skipped
CI / Merge dev → testing (push) Has been skipped
CI / Prepare Merge to Main (push) Has been skipped
CI / Finalize Merge to Main (push) Has been skipped

This commit is contained in:
2026-04-12 07:48:17 -04:00
parent 6095d0d2ed
commit b2e4706a14
53 changed files with 2155 additions and 360 deletions

View File

@@ -29,12 +29,12 @@ def _log(event_type: str, severity: int = 6, **kwargs) -> None:
def _setup_bait_fs() -> str:
bait_dir = Path("/tmp/ftp_bait")
bait_dir.mkdir(parents=True, exist_ok=True)
(bait_dir / "backup.tar.gz").write_bytes(b"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00")
(bait_dir / "db_dump.sql").write_text("CREATE TABLE users (id INT, username VARCHAR(50), password VARCHAR(50));\nINSERT INTO users VALUES (1, 'admin', 'pbkdf2:sha256:5000$...');\n")
(bait_dir / "config.ini").write_text("[database]\nuser = dbadmin\npassword = db_super_admin_pass_!\nhost = localhost\n")
(bait_dir / "credentials.txt").write_text("admin:super_secret_admin_pw\nroot:toor\nalice:wonderland\n")
return str(bait_dir)
class ServerFTP(FTP):

View File

@@ -97,7 +97,7 @@ def _publish(topic: str, value: str, retain: bool = True) -> bytes:
payload = str(value).encode()
fixed = 0x31 if retain else 0x30
remaining = len(topic_len) + len(topic_bytes) + len(payload)
# variable length encoding
rem_bytes = []
while remaining > 0:
@@ -108,7 +108,7 @@ def _publish(topic: str, value: str, retain: bool = True) -> bytes:
rem_bytes.append(encoded)
if not rem_bytes:
rem_bytes = [0]
return bytes([fixed]) + bytes(rem_bytes) + topic_len + topic_bytes + payload
@@ -132,7 +132,7 @@ def _generate_topics() -> dict:
return topics
except Exception as e:
_log("config_error", severity=4, error=str(e))
if MQTT_PERSONA == "water_plant":
topics.update({
"plant/water/tank1/level": f"{random.uniform(60.0, 80.0):.1f}",
@@ -186,7 +186,7 @@ class MQTTProtocol(asyncio.Protocol):
pkt_type = (pkt_byte >> 4) & 0x0f
flags = pkt_byte & 0x0f
qos = (flags >> 1) & 0x03
# Decode remaining length (variable-length encoding)
pos = 1
remaining = 0
@@ -225,7 +225,7 @@ class MQTTProtocol(asyncio.Protocol):
packet_id, subs = _parse_subscribe(payload)
granted_qos = [1] * len(subs) # grant QoS 1 for all
self._transport.write(_suback(packet_id, granted_qos))
# Immediately send retained publishes matching topics
for sub_topic, _ in subs:
_log("subscribe", src=self._peer[0], topics=[sub_topic])
@@ -245,11 +245,11 @@ class MQTTProtocol(asyncio.Protocol):
topic, packet_id, data = _parse_publish(payload, qos)
# Attacker command received!
_log("publish", src=self._peer[0], topic=topic, payload=data.decode(errors="replace"))
if qos == 1:
puback = bytes([0x40, 0x02]) + struct.pack(">H", packet_id)
self._transport.write(puback)
elif pkt_type == 12: # PINGREQ
self._transport.write(b"\xd0\x00") # PINGRESP
elif pkt_type == 14: # DISCONNECT

View File

@@ -156,7 +156,7 @@ class RedisProtocol(asyncio.Protocol):
elif pattern != '*':
pat = pattern.encode()
keys = [k for k in keys if k == pat]
resp = f"*{len(keys)}\r\n".encode() + b"".join(_bulk(k.decode()) for k in keys)
self._transport.write(resp)
elif verb == "GET":

View File

@@ -45,7 +45,7 @@ def _log(event_type: str, severity: int = 6, **kwargs) -> None:
def _rand_msg_id() -> str:
"""Return a Postfix-style 12-char alphanumeric queue ID."""
chars = string.ascii_uppercase + string.digits
return "".join(random.choices(chars, k=12)) # noqa: S311
return "".join(random.choices(chars, k=12))
def _decode_auth_plain(blob: str) -> tuple[str, str]:

View File

@@ -153,11 +153,11 @@ def _parse_snmp(data: bytes):
# PDU type (0xa0 = GetRequest, 0xa1 = GetNextRequest)
if pos >= len(data):
raise ValueError("Missing PDU type")
pdu_type = data[pos]
if pdu_type not in (0xa0, 0xa1):
raise ValueError(f"Invalid PDU type {pdu_type}")
pos += 1
_, pos = _read_ber_length(data, pos)
# request-id

View File

@@ -155,13 +155,13 @@ def write_syslog_file(line: str) -> None:
"""Append a syslog line to the rotating log file."""
try:
_get_file_logger().info(line)
# Also parse and write JSON log
import json
import re
from datetime import datetime
from typing import Optional, Any
from typing import Optional
_RFC5424_RE: re.Pattern = re.compile(
r"^<\d+>1 "
r"(\S+) " # 1: TIMESTAMP
@@ -174,7 +174,7 @@ def write_syslog_file(line: str) -> None:
_SD_BLOCK_RE: re.Pattern = re.compile(r'\[decnet@55555\s+(.*?)\]', re.DOTALL)
_PARAM_RE: re.Pattern = re.compile(r'(\w+)="((?:[^"\\]|\\.)*)"')
_IP_FIELDS: tuple[str, ...] = ("src_ip", "src", "client_ip", "remote_ip", "ip")
_m: Optional[re.Match] = _RFC5424_RE.match(line)
if _m:
_ts_raw: str
@@ -183,10 +183,10 @@ def write_syslog_file(line: str) -> None:
_event_type: str
_sd_rest: str
_ts_raw, _decky, _service, _event_type, _sd_rest = _m.groups()
_fields: dict[str, str] = {}
_msg: str = ""
if _sd_rest.startswith("-"):
_msg = _sd_rest[1:].lstrip()
elif _sd_rest.startswith("["):
@@ -194,27 +194,27 @@ def write_syslog_file(line: str) -> None:
if _block:
for _k, _v in _PARAM_RE.findall(_block.group(1)):
_fields[_k] = _v.replace('\\"', '"').replace("\\\\", "\\").replace("\\]", "]")
# extract msg after the block
_msg_match: Optional[re.Match] = re.search(r'\]\s+(.+)$', _sd_rest)
if _msg_match:
_msg = _msg_match.group(1).strip()
else:
_msg = _sd_rest
_attacker_ip: str = "Unknown"
for _fname in _IP_FIELDS:
if _fname in _fields:
_attacker_ip = _fields[_fname]
break
# Parse timestamp to normalize it
_ts_formatted: str
try:
_ts_formatted = datetime.fromisoformat(_ts_raw).strftime("%Y-%m-%d %H:%M:%S")
except ValueError:
_ts_formatted = _ts_raw
_payload: dict[str, Any] = {
"timestamp": _ts_formatted,
"decky": _decky,
@@ -226,7 +226,7 @@ def write_syslog_file(line: str) -> None:
"raw_line": line
}
_get_json_logger().info(json.dumps(_payload))
except Exception:
pass