{"body":"# ProvisionIRCd v3.0-beta — Full Source Tree Audit\n\n**Audit Date:** April 5, 2026\n**Source:** ProvisionIRCd-main.zip (98 Python files, ~16,226 lines)\n**Methodology:** Line-1-to-EOF review of every `.py` file, applying Cathexis IRCd audit standards\n\n---\n\n## 1. Executive Summary\n\nProvisionIRCd is a Python 3.10+ IRC daemon with a modular architecture closely modeled on UnrealIRCd's protocol (PROTOCTL, SJOIN, UID, EOS, TKL, S2S metadata). It uses pyOpenSSL for TLS, a `selectors`-based single-threaded event loop, and a plugin system where nearly every IRC command and feature is a loadable module.\n\n**Verdict:** Functional beta with a clean modular design, but carrying **7 critical security defects**, **12 high-severity bugs**, and numerous code quality issues that would prevent deployment in any environment handling real user traffic.\n\n---\n\n## 2. Architecture Overview\n\n### 2.1 Core Design\n\nThe `IRCD` class (`handle/core.py`) is a monolithic static namespace: no instances are ever created. All global state — client tables, channel tables, configuration, module dispatch, the selector, the thread pool executor — lives as class-level attributes. This is a common pattern for single-process IRC daemons but creates tight coupling and makes testing impossible without mocking the entire global state.\n\nClient lifecycle is managed through three sub-objects: `User` (IRC user attributes), `Server` (server link attributes), and `LocalClient` (socket-level state for directly connected clients). The `Client` dataclass holds all three, discriminating type by which sub-objects are populated. This is the same pattern as UnrealIRCd's `aClient` struct and works well in practice.\n\nThe module system uses a simple `init(module)` / `post_load(module)` convention. Modules register commands via `Command.add()`, hooks via `Hook.add()`, modes via `Channelmode.add()` / `Usermode.add()`, and capabilities via `Capability.add()`. Hooks support priority ordering. This is clean, extensible, and well-implemented.\n\n### 2.2 Event Loop (`handle/sockets.py`)\n\nThe main loop uses `selectors.DefaultSelector` with a 100ms timeout. Periodic tasks (pings, timeouts, backbuffer processing, throttle expiry, hostname resolution) run on a 1-second interval. The loop structure is straightforward:\n\n```\nwhile IRCD.running:\n    events = selector.select(timeout=0.1)\n    for event in events:\n        process_event(event)\n    if 1-second interval elapsed:\n        run periodic tasks\n```\n\nThis is adequate for moderate loads but will struggle above ~2,000 concurrent connections due to the linear iteration patterns in periodic tasks.\n\n### 2.3 Module Inventory\n\n98 Python files organized as:\n- **classes/** (4 files): Data models, configuration entries, error codes\n- **handle/** (9 files): Core runtime, client/channel management, sockets, TLS, config parsing, logging\n- **modules/** (55+ files): IRC commands, channel modes, user modes, IRCv3 extensions, services integration\n\n---\n\n## 3. Critical Security Defects\n\n### 3.1 CRITICAL: Unauthenticated Command Socket\n\n**Files:** `ircd.py:63-93`, `handle/sockets.py:599-604,462-472`\n\nThe rehash/restart mechanism binds a TCP socket on `127.0.0.1:65432` with **zero authentication**. Any local process can send `REHASH`, `RESTART`, or `SHUTDOWN` to control the daemon.\n\n```python\n# ircd.py:63-68\nsock.connect((\"127.0.0.1\", 65432))\nsock.sendall(b\"REHASH\")  # No password, no token, nothing\n```\n\n**Impact:** Local privilege escalation. Any compromised web application, container escape, or unprivileged user on the host can shut down or reconfigure the IRC daemon.\n\n**Remediation:** Use a Unix domain socket with `0600` permissions, or require a shared secret. Better yet, use a PID-file-based signal mechanism (`SIGHUP` for rehash).\n\n### 3.2 CRITICAL: Plaintext Oper Password Fallback\n\n**File:** `modules/m_oper.py:137-145`\n\n```python\nif oper.password.startswith(\"$2b$\") and len(oper.password) > 58 and bcrypt is not None:\n    if not bcrypt.checkpw(recv[2].encode(\"utf-8\"), oper.password.encode(\"utf-8\")):\n        oper_fail(...)\nelif recv[2] != oper.password:  # PLAINTEXT COMPARISON\n    oper_fail(...)\n```\n\nIf bcrypt is not installed (it's not in `requirements.txt`) or if the password doesn't look like a bcrypt hash, oper passwords are compared in **plaintext**. This means:\n- Configuration files must store passwords in cleartext\n- Passwords are visible in memory as raw strings\n- No timing-safe comparison is used\n\n**Impact:** Credential exposure via config file access, memory dumps, or timing side channels.\n\n**Remediation:** Make bcrypt mandatory in `requirements.txt`. Reject non-hashed passwords at config validation time. Use `hmac.compare_digest()` for any string comparison.\n\n### 3.3 CRITICAL: Plaintext Server Link Passwords\n\n**File:** `modules/m_server.py:94,147`\n\nServer link authentication always uses plaintext password comparison:\n\n```python\nif client.local.authpass != password:  # Direct string comparison\n    deny_direct_link(...)\n```\n\nNo hashing, no constant-time comparison, no key derivation. The password is sent over the wire via `PASS :password` and stored in config files in cleartext.\n\n**Impact:** Any network observer between linked servers can capture link passwords. Timing attacks are trivially possible.\n\n**Remediation:** Use HMAC-based challenge-response or require TLS client certificates for all server links. At minimum, hash stored passwords and use `hmac.compare_digest()`.\n\n### 3.4 CRITICAL: Weak Cloaking (SHA-512 → CRC32)\n\n**File:** `handle/core.py:636-662`\n\n```python\nkey_bytes = bytes(f\"{host}{cloak_key}\", \"utf-8\")\nhex_dig = hashlib.sha512(key_bytes).hexdigest()\ncloak1 = hex(binascii.crc32(bytes(hex_dig[0:32], \"utf-8\")) % (1 << 32))[2:]\ncloak2 = hex(binascii.crc32(bytes(hex_dig[32:64], \"utf-8\")) % (1 << 32))[2:]\n```\n\nMultiple issues:\n1. **CRC32 output** — 32-bit checksum, not a cryptographic function. ~77,000 unique hosts = 50% collision probability per segment.\n2. **Simple concatenation** — `host + cloak_key` is vulnerable to length-extension and key-recovery attacks. Should use HMAC.\n3. **No salt** — Same host always produces the same cloak, enabling rainbow table attacks.\n4. **Cloak key leaked** — `handleLink.py:95` sends `MD5:cloakhash` of the cloak key to linked servers in plaintext.\n\n**Impact:** User IP addresses can be recovered from cloaked hostnames with modest computational effort.\n\n**Remediation:** Replace with HMAC-SHA256, add per-instance salt, stop leaking the key hash over links.\n\n### 3.5 CRITICAL: Thread-Unsafe WebSocket Bridge\n\n**File:** `modules/irc_websockets.py:70-109`\n\nThe WebSocket server runs in a separate daemon thread but shares all mutable state with the main event loop — `Client.table`, `IRCD.client_by_id`, `IRCD.client_by_name`, `IRCD.client_by_sock` — without any locking:\n\n```python\ndef handler(self, websocket):\n    client = make_client(direction=None, uplink=IRCD.me)  # Modifies Client.table from websocket thread\n    ...\n    IRCD.websocketbridge.clients.add(client)  # Race condition with main loop iteration\n```\n\n**Impact:** Data corruption, use-after-free conditions, and crashes under concurrent WebSocket and traditional connections.\n\n**Remediation:** Either marshal all client operations to the main thread via a thread-safe queue, or use asyncio for the WebSocket server and integrate with the main event loop.\n\n### 3.6 CRITICAL: ssl_verify_callback Always Returns True\n\n**File:** `handle/handle_tls.py:13-14`\n\n```python\ndef ssl_verify_callback(*args):\n    return True\n```\n\nThis callback is set as the verify callback for all TLS contexts. Combined with `SSL.VERIFY_PEER`, this means the server requests client certificates but **always accepts them regardless of validity**. A self-signed, expired, or completely fabricated certificate will be accepted.\n\n**Impact:** Certificate-based authentication (certfp-based oper, server link auth) can be bypassed by presenting any certificate with the target fingerprint, even if it's not properly signed.\n\n**Remediation:** Implement proper certificate chain validation or remove the verify callback and rely on fingerprint matching only.\n\n### 3.7 CRITICAL: WEBIRC Password in Plaintext\n\n**File:** `modules/m_webirc.py:47-49`\n\n```python\ndef cmd_webirc(client, recv):\n    if client.registered or recv[1] != WebIRCConf.password or client.ip not in WebIRCConf.ip_whitelist:\n        return\n```\n\nThe WEBIRC password is compared in plaintext with no timing-safe comparison, and stored in cleartext in config. This is the gateway password that allows web gateways to set arbitrary client IPs.\n\n**Impact:** Timing attack on WEBIRC password enables IP spoofing from whitelisted gateways.\n\n---\n\n## 4. High-Severity Bugs\n\n### 4.1 File Handle Leak\n\n**File:** `handle/core.py:487`\n```python\ndef read_from_file(file: str) -> str:\n    return open(file, 'r').read() if os.path.exists(file) else ''\n```\nOpens file without context manager. Under sustained operation, this leaks file descriptors.\n\n### 4.2 Unbound ThreadPoolExecutor\n\n**File:** `handle/core.py:320`\n```python\nexecutor = ThreadPoolExecutor()  # Default max_workers = min(32, cpu_count + 4)\n```\nUsed for DNS resolution, DNSBL checks, geodata API calls, delayed operations, and STARTTLS handshakes. A connection flood can exhaust the pool, blocking all async operations including hostname resolution for legitimate users.\n\n### 4.3 Duplicate Exception Handler\n\n**File:** `handle/sockets.py:583-592`\n```python\nexcept SSL.SysCallError as ex:           # Line 583\n    if ex.args[0] in (10035, 35):\n        ...\nexcept (..., SSL.SysCallError, ...) as ex:  # Line 590 — dead code for SysCallError\n```\nThe second catch for `SSL.SysCallError` is unreachable.\n\n### 4.4 Unbounded SASL Request Table\n\n**File:** `modules/m_sasl.py:22-29`\n```python\nclass SaslRequest:\n    table = []\n    def __init__(self, client, user_id):\n        ...\n        SaslRequest.table.append(self)\n```\nNo maximum size. A slowloris attack sending AUTHENTICATE but never completing can fill this list indefinitely.\n\n### 4.5 Geodata Module Syntax Error\n\n**File:** `modules/geodata.py:66`\n```python\nprovider = API_PROVIDERS[current_index] // debian\n```\nThis line contains `// debian` which will cause a `TypeError` at runtime (integer floor-division on a dict by an undefined variable `debian`). This module will crash on every API call.\n\n### 4.6 `is_match()` Recursive Glob with No Depth Limit\n\n**File:** `handle/functions.py:88-100`\n```python\ndef is_match(first: str, second: str, memo=None) -> bool:\n    ...\n    elif first[0] == '*':\n        result = is_match(first[1:], second, memo) or (second and is_match(first, second[1:], memo))\n```\nThe memoization dict prevents infinite recursion for identical subproblems, but pathological inputs like `\"*\" * 100` matched against a 100-char string can still produce O(n²) memo entries. This is used in ban matching, spamfilter matching, and client mask matching — all hot paths.\n\n### 4.7 Regex Spamfilter Without Timeout\n\n**File:** `modules/m_spamfilter.py:57`\n```python\nis_matched = bool(re.search(pattern, message))\n```\nUser-provided regex patterns are compiled and executed against every message with no timeout or complexity limit. A crafted ReDoS pattern (e.g., `(a+)+$`) can freeze the main event loop.\n\n### 4.8 Race in `permanent.py` Channel Restore\n\n**File:** `modules/chanmodes/permanent.py`\nChannel data is stored as JSON via `IRCD.write_data_file()` on every mode change and topic change. If two mode changes happen in rapid succession, the file writes can interleave.\n\n### 4.9 Founder Module IP/Mask Matching Too Loose\n\n**File:** `modules/founder.py:49-54`\n```python\nreturn channel in Founders.channels and (\n    client.fullrealhost == Founders.channels[channel].get(\"fullrealhost\") or\n    client.get_md_value(\"certfp\") == Founders.channels[channel].get(\"certfp\") or\n    client.user.account != '*' and client.user.account == Founders.channels[channel].get(\"account\")\n)\n```\nThe `certfp` check will match if both are `None`/`0`/falsy, granting founder to any user without a certificate when the original founder also had no certificate.\n\n### 4.10 Die/Restart Passwords in Plaintext\n\n**Files:** `modules/m_die.py:22`, `modules/m_restart.py:22`\n```python\nif recv[1] != IRCD.get_setting(\"diepass\"):\n```\nPlaintext comparison, no timing safety, passwords stored in cleartext config.\n\n### 4.11 `exit()` Calls in Library Code\n\n**Files:** `handle/handle_tls.py:35`, `handle/client.py:1037`, `classes/conf_entries.py` (multiple)\nCalling `exit()` or `sys.exit()` from library functions makes error handling impossible for callers and can crash the daemon during module loading.\n\n### 4.12 STARTTLS Race Condition\n\n**File:** `modules/starttls.py:13-17`\n```python\nclient.local.handshake = 0\nclient.sendnumeric(Numeric.RPL_STARTTLS, \"STARTTLS successful, proceed with TLS handshake\")\nIRCD.run_parallel_function(wrap_socket, args=(client,), kwargs={\"starttls\": 1})\n```\nThe `RPL_STARTTLS` is sent before the TLS handshake starts, in cleartext. Then `wrap_socket` runs in a **separate thread**, creating a race between the client sending data and the TLS handshake completing.\n\n---\n\n## 5. Code Quality Issues\n\n### 5.1 Pattern: `next(..., 0)` Instead of `None`\n\nUsed extensively across the codebase. `0` is falsy but also a valid integer in many contexts. Returning `0` when \"not found\" conflates \"absent\" with \"zero,\" creating subtle bugs. Examples: `SaslRequest.get_from_id()`, `Capability.find_cap()`, `Snomask.add()`, `Stat.get()`, `Tkl.exists()`.\n\n### 5.2 Dead Code\n\n- `client.py:960-994` — `direct_send_old()` method still present\n- `channel.py:96-104` — `clients_()` method, duplicates `clients()` with minor differences\n- `handle/sockets.py:590` — duplicate `SSL.SysCallError` catch\n- `handle/sockets.py:498` — unreferenced debug comment `# IRCD.ref_counts(self)`\n\n### 5.3 Broad Exception Handlers\n\nAt least 30 instances of `except Exception as ex: logging.exception(ex)` that swallow errors that should propagate. Most critically in `Command.do()` (core.py:144) which catches all exceptions during command execution, masking bugs in modules.\n\n### 5.4 Global Mutable State in Modules\n\nMany modules use class-level or module-level mutable containers:\n- `SaslRequest.table = []`\n- `Blacklist.cache = []`\n- `Watch.watchlist = {}`\n- `Monitor.monlist = {}`\n- `Founders.channels = ChannelsDict()`\n- `nick_flood = defaultdict(...)`\n- `GeoData.data = {}`\n\nNone of these are cleaned up during rehash, leading to memory leaks and stale state accumulation.\n\n### 5.5 Inconsistent Error Handling in Config Validation\n\n`validate_conf.py` accumulates errors in `ConfErrors.entries` but many validation functions return early on the first error, missing subsequent issues. Some call `conf_error()` and return, others call it and continue. The `conf_error()` function itself doesn't raise — it just appends to a list, but callers treat it as if it stops processing.\n\n### 5.6 `gc.collect()` Called on Every Client Disconnect\n\n**File:** `handle/client.py:508`\n```python\ndef cleanup(self):\n    ...\n    gc.collect()\n```\nFull garbage collection on every disconnect is expensive and unnecessary with Python's generational collector. Under a connection flood, this will significantly degrade performance.\n\n### 5.7 m_quotes.py — 519 Lines of Hardcoded Quotes\n\nThis module contains ~500 lines of hardcoded computer-related quotes sent on connect. Should be loaded from a file.\n\n---\n\n## 6. Protocol Compliance Notes\n\n### 6.1 UnrealIRCd Compatibility\n\nThe S2S protocol is closely modeled on UnrealIRCd 5.x/6.x:\n- PROTOCTL with EAUTH, SID, VL, SJOIN, SJOIN2, UMODE2, MTAGS, NICKIP, NEXTBANS\n- UID for user introduction\n- SID for server introduction\n- SJOIN for channel sync\n- TKL for bans\n- MD for metadata\n- EOS for end-of-sync\n- SLOG for remote logging\n\nThis is well-implemented and should interoperate with UnrealIRCd networks, though the mode compatibility checks in `m_protoctl.py` are strict enough that minor mode mismatches will deny links.\n\n### 6.2 IRCv3 Support\n\nImplemented capabilities: `message-tags`, `labeled-response`, `batch`, `echo-message`, `server-time`, `account-notify`, `account-tag`, `chghost`, `setname`, `extended-monitor`, `cap-notify`, `multi-prefix`, `userhost-in-names`, `oper-notify`, `tls` (STARTTLS), `sasl`, `typing`, `standard-replies`, `no-implicit-names`.\n\nAlso implements `CHATHISTORY` and `MONITOR` (IRCv3 drafts). The implementation quality is generally good, with proper capability negotiation and tag filtering.\n\n### 6.3 Channel Types\n\nSupports `#`, `+`, and `&` prefixes. The `!` prefix is in the CHANPREFIXES constant but has no special handling (no creation TS, no short-name routing). This is acceptable for a modern IRCd.\n\n---\n\n## 7. Comparison to Cathexis IRCd Standards\n\n| Criterion | Cathexis Standard | ProvisionIRCd | Verdict |\n|-----------|------------------|---------------|---------|\n| TLS minimum | TLS 1.2 enforced | TLS 1.2 effective (disables lower) | ✅ Pass |\n| Crypto API centralization | All through `ircd_crypto.h` | Scattered across modules | ❌ Fail |\n| Cloaking | HMAC-SHA256 | SHA-512 → CRC32 | ❌ Fail |\n| Password storage | Argon2id only | bcrypt optional, plaintext fallback | ❌ Fail |\n| Constant-time comparisons | `ircd_constcmp()` for secrets | Direct `!=` everywhere | ❌ Fail |\n| Key material wiping | `OPENSSL_cleanse` | Never wiped | ❌ Fail |\n| Deprecated API avoidance | Modern OpenSSL only | pyOpenSSL (legacy wrapper) | ⚠️ Marginal |\n| Code audit standard | 36-pass scan, line 1 to EOF | No formal audit process | ❌ Fail |\n| AI attribution | No AI attribution in code | N/A | ✅ Pass |\n| CodeQL clean | Target: zero findings | No static analysis | ❌ Fail |\n| Buffer safety | `ircd_strncpy`, range checks | Python handles this natively | ✅ N/A |\n\n---\n\n## 8. Positive Findings\n\nDespite the security issues, several aspects of ProvisionIRCd are well-engineered:\n\n1. **Module system** — Clean `init()`/`post_load()` lifecycle with hook priorities, command registration, and capability management. This is a genuinely good plugin architecture.\n\n2. **Non-blocking TLS state machine** — The `wrap_socket()` function in `sockets.py` correctly handles `WantReadError`/`WantWriteError` with flag-based state tracking and selector modification. This is tricky to get right and is well-implemented.\n\n3. **Flood control** — Multi-layered: penalty-based (accumulated per action, decays over 60s), recvq/sendq byte limits, backbuffer entry counting, and per-command delays. The `check_flood()` method is thorough.\n\n4. **Client lookup optimization** — `client_by_id`, `client_by_name`, and `client_by_sock` dicts provide O(1) lookup for the three most common search patterns, with the `name` property setter keeping the name dict in sync.\n\n5. **Server link authentication** — The `auth` block in `m_server.py` supports three methods (password, certfp, CN matching) and requires them in combination. This is more flexible than many IRCd implementations.\n\n6. **Configuration parser** — The custom `ConfigParser` in `handle/configparser.py` handles includes, nested blocks, and provides source-file-and-line-number tracking for error messages. Good UX for operators.\n\n7. **Logging system** — MDC-based contextual logging with per-client context, file rotation with size and age limits, and colored console output. The `@logging.client_context` decorator is a nice touch.\n\n8. **Root check** — `ircd.py:18-19` refuses to run as root on Linux. Simple but important.\n\n9. **Host resolution** — Async hostname resolution via `ThreadPoolExecutor.submit()` with a 1-second timeout and result caching. Non-blocking and correct.\n\n10. **Ban system** — TKL implementation is comprehensive: K-lines, G-lines, Z-lines, GZ-lines, shuns, Q-lines (nick), spamfilters, and E-lines (exceptions). Supports extended ban types (`~account:`, `~certfp:`). Expiry, persistence to JSON, and network sync all work correctly.\n\n---\n\n## 9. Recommendations\n\n### Immediate (before any production use)\n\n1. Replace the command socket with a Unix domain socket + permissions or signal-based mechanism\n2. Add bcrypt to `requirements.txt` and reject plaintext oper/link passwords at config validation\n3. Add `hmac.compare_digest()` for all secret comparisons\n4. Replace CRC32 cloaking with HMAC-SHA256\n5. Fix the `geodata.py` syntax error (`// debian`)\n6. Add threading locks or marshal WebSocket operations to the main thread\n7. Add timeout/complexity limits to regex spamfilters\n\n### Short-term\n\n8. Cap the `ThreadPoolExecutor` size and add backpressure\n9. Add `re.TIMEOUT` or compile-time complexity analysis for spamfilter patterns\n10. Remove `gc.collect()` from client cleanup\n11. Fix the `certfp` check in `founder.py` to handle `None` values\n12. Replace all `next(..., 0)` with `next(..., None)` and adjust callers\n13. Remove dead code (`direct_send_old`, duplicate `clients_()`)\n14. Add connection rate limiting to the WebSocket bridge\n\n### Long-term\n\n15. Migrate from pyOpenSSL to Python's `ssl` module (pyOpenSSL is deprecated)\n16. Add a formal static analysis pipeline (pylint, mypy, bandit)\n17. Implement proper certificate validation for server links\n18. Add unit tests — the modular architecture makes this feasible\n19. Move hardcoded quotes to a data file\n20. Consider migrating the event loop to `asyncio` for better WebSocket integration and DNS resolution\n\n---\n\n## 10. File-by-File Summary\n\n### classes/\n| File | Lines | Purpose | Issues |\n|------|-------|---------|--------|\n| conf_entries.py | 422 | Config data models (Mask, Allow, Listen, Oper, etc.) | `exit()` calls in `Module.load()` |\n| configuration.py | 230 | Config builder, rehash logic | Rehash `_restore_config` doesn't restore all state |\n| data.py | 753 | Numerics, Flags, Hooks, Isupport, Extbans | Flag/hook IDs use sequential globals, fragile |\n| errors.py | 46 | S2S error codes | Clean |\n\n### handle/\n| File | Lines | Purpose | Issues |\n|------|-------|---------|--------|\n| channel.py | 500 | Channel and Channelmode classes | Duplicate `clients_()` method |\n| client.py | 1079 | Client, User, Server, LocalClient, flood control | Dead `direct_send_old()`, `gc.collect()` in cleanup |\n| configparser.py | 678 | Block-based config file parser | Adequate |\n| core.py | 1185 | IRCD class, Command, Usermode, Snomask, Capability | CRC32 cloaking, file handle leak, global mutable state |\n| functions.py | 115 | Utility functions (IP encoding, glob matching) | Recursive `is_match()` without depth limit |\n| handleLink.py | 193 | Server link negotiation and sync | Cloak key MD5 leaked in NETINFO |\n| handle_tls.py | 142 | TLS context creation and cert generation | Verify callback always returns True |\n| log.py | 93 | Log entry system, snomask routing | `cmd_slog` registered at module level (not in init) |\n| logger.py | 310 | Logging infrastructure with MDC | Well-engineered |\n| sockets.py | 652 | Event loop, socket I/O, TLS handshake | Duplicate exception handler, unbound command socket |\n| validate_conf.py | 752 | Config validation functions | Inconsistent error accumulation |\n\n### modules/ (command modules)\n| File | Lines | Key Issues |\n|------|-------|------------|\n| m_oper.py | 213 | Plaintext password fallback |\n| m_server.py | 280 | Plaintext link password comparison |\n| m_sasl.py | 191 | Unbounded request table |\n| m_spamfilter.py | 337 | Regex without timeout |\n| m_tkl.py | 621 | Clean implementation |\n| m_nick.py | 219 | Clean, proper collision handling |\n| m_msg.py | 262 | Clean, proper flood penalties |\n| m_mode.py | 606 | Complex but functional |\n| m_sjoin.py | 337 | Proper timestamp-based conflict resolution |\n| m_protoctl.py | 148 | Strict mode compatibility checks |\n| m_webirc.py | 54 | Plaintext password comparison |\n| m_die.py | 36 | Plaintext password comparison |\n| m_restart.py | 36 | Plaintext password comparison |\n| m_rehash.py | 72 | Clean |\n| m_quotes.py | 519 | 500 lines of hardcoded strings |\n| m_antirandom.py | 505 | Nick entropy checker, well-implemented |\n| blacklist.py | 191 | Async DNSBL with caching, good |\n| irc_websockets.py | 170 | Thread-unsafe shared state |\n| geodata.py | 142 | Syntax error on line 66 |\n| certfp.py | 63 | Clean |\n| founder.py | 143 | certfp None-matching bug |\n| starttls.py | 25 | Race condition with thread |\n\n### modules/chanmodes/ (26 files)\nAll channel mode modules follow a consistent pattern and are generally clean. Notable: `m_history.py` (240 lines) implements full IRCv3 `CHATHISTORY` with `BEFORE`, `AFTER`, `BETWEEN`, `LATEST` subcommands. `permanent.py` handles JSON persistence correctly.\n\n### modules/usermodes/ (5 files)\nClean implementations of `+B` (bot), `+d` (deaf), `+i` (invisible), `+x` (cloak), `+z` (TLS), `+D` (block private messages), `+g` (caller-ID).\n\n### modules/ircv3/ (16 files)\nIRCv3 capability implementations are generally correct. `messagetags.py` (110 lines) provides the tag filtering framework. `batch.py` (100 lines) handles `BATCH` creation and lifecycle. `labeled-response.py` (94 lines) correctly wraps responses in labels.\n\n---\n\n*End of audit.*\n","name":"","extension":"txt","url":"https://www.irccloud.com/pastebin/l16A13jg","modified":1775455717,"id":"l16A13jg","size":25059,"lines":504,"own_paste":false,"theme":"","date":1775455717}