75
Tests passing
0
ASan / UBSan errors
0
clang-tidy open bugs
0
Compiler warnings
284
Assertions
61
Fuzz inputs
35
IRCv3 caps
104
Source files
24 015
Lines of C++
297
Themes

All tests pass

Five Qt Test suites cover the IRC protocol parser, the mIRC chat renderer, the TOML config loader, the history log reader, and the ignore-list types. Below is the live output from the last run.

tst_ircparser
********* Start testing of TstIrcParser ********* Config: QtTest 6.11.1 · GCC 16.1.1 · x86_64 PASS : TstIrcParser::basicPrivmsg() PASS : TstIrcParser::serverPrefix() PASS : TstIrcParser::noPrefix() PASS : TstIrcParser::multiParam() PASS : TstIrcParser::trailingOnly() PASS : TstIrcParser::ircv3Tags() PASS : TstIrcParser::tagUnescape() PASS : TstIrcParser::serverTimeTag() PASS : TstIrcParser::emptyLine() PASS : TstIrcParser::crlfStripped() PASS : TstIrcParser::numericCommand() PASS : TstIrcParser::tagsThenNoPrefix() PASS : TstIrcParser::malformedTagsOnly() PASS : TstIrcParser::decodeValidUtf8() PASS : TstIrcParser::decodeLatin1Fallback() PASS : TstIrcParser::decodeAsciiAndEmpty() Totals: 18 passed, 0 failed, 0 skipped, 0ms ********* Finished testing of TstIrcParser *********
tst_chatformat
********* Start testing of TstChatFormat ********* Config: QtTest 6.11.1 · GCC 16.1.1 · x86_64 PASS : TstChatFormat::htmlAttrQuote() PASS : TstChatFormat::htmlAttrEntities() PASS : TstChatFormat::ircToHtmlPlain() PASS : TstChatFormat::ircToHtmlEscaping() PASS : TstChatFormat::ircToHtmlBold() PASS : TstChatFormat::ircToHtmlItalic() PASS : TstChatFormat::ircToHtmlUnderline() PASS : TstChatFormat::ircToHtmlColorFg() PASS : TstChatFormat::ircToHtmlColorFgBg() PASS : TstChatFormat::ircToHtmlResetPlain() PASS : TstChatFormat::ircToHtmlResetColor() PASS : TstChatFormat::linkifyHttp() PASS : TstChatFormat::linkifyHttps() PASS : TstChatFormat::linkifyFtpSkipped() PASS : TstChatFormat::linkifyNoUrl() PASS : TstChatFormat::highlightReCaptureGroup() PASS : TstChatFormat::highlightWordSegment() Totals: 19 passed, 0 failed, 0 skipped, 2ms ********* Finished testing of TstChatFormat *********
fuzz harnesses - corpus replay
$ ./build/tests/fuzz_ircparser tests/corpus Replaying 50 real-world IRC messages against IrcParser::parse() … 001_basic_privmsg 002_numeric_001 003_ping_no_prefix 004_ircv3_tags 005_tag_escapes 006_extended_join 007_mode_multiparams 008_tags_no_prefix 009_tags_only_no_command 010_empty 011_crlf_only 012_topic 013_quit 014_client_tags 015_batch_open 016_batch_close 017_names_server_time 018_nick_change 019_kick 020_prefix_only … +30 more fuzz_ircparser: replayed 50 input(s) - no crashes $ ./build/tests/fuzz_chatformat tests/corpus_chatformat Replaying hostile message content against the chat renderer: mIRC colour codes, unterminated sequences, emoji/ZWJ runs, RTL overrides, invalid UTF-8, 300-char words, redactions, replies … fuzz_chatformat: replayed 11 input(s) - no crashes libFuzzer mode also available: cmake -DUPLINK_BUILD_FUZZ=ON (requires clang, ASan, libFuzzer)

Tests are run with ctest as part of the standard cmake -B build -DUPLINK_BUILD_TESTS=ON build. Source: tests/tst_ircparser.cpp, tests/tst_chatformat.cpp, tests/tst_config.cpp, tests/tst_logreader.cpp, tests/tst_ignoretypes.cpp, tests/fuzz_ircparser.cpp, tests/fuzz_chatformat.cpp.

38 IRCv3 capabilities negotiated

Full CAP LS 302 negotiation. Only capabilities the server actually advertises are requested; no blindly batched REQs that fail silently.

Capability What Uplink does with it Status
message-tagsTransport layer for all tag-based features; full IRCv3 escape unescapingFull
server-timeUses server timestamp over local clock, essential for bouncer history replayFull
batchBuffers all tagged messages; delivers on close; 8-batch / 1 000-msg safety limitsFull
sasl PLAINAuthenticates before appearing on-network; holds CAP END until 903/904Full
sasl EXTERNALClient certificate TLS auth; RSA and EC PEM keys supportedFull
chathistorySends CHATHISTORY LATEST after join; renders history dimmed with original timestampsFull
draft/chathistorySame as above, covers Ergo IRCd's draft nameFull
draft/typingSends active/paused/done TAGMSG; renders "nick is typing…" with auto-timeoutFull (send + recv)
msgidStored on every message; anchor for reply, react, redactFull
draft/replyRight-click → Reply; renders ↩ origNick indicator on incoming repliesFull (send + recv)
draft/reactEmoji picker; renders inline reactions per-message with countFull (send + recv)
draft/message-redactionRight-click → Delete; replaces with [message deleted] preserving reply anchorsFull (send + recv)
draft/multilineShift+Enter to compose; opens BATCH, sends lines, falls back gracefullyFull (send + recv)
draft/metadata-2 / -3Display name, avatar + status text synced from server; channel avatars as sidebar icons; async fetch, cached per session; -3 preferred when both offeredFull (send + recv)
draft/read-markerSyncs read position across clients via MARKREAD (standard form of soju.im/read)Full (send + recv)
draft/persistenceReads the server's persistence state on connect and sets it with /persistence (Ergo always-on)Full (send + recv)
no-implicit-namesSuppresses automatic NAMES on join; Uplink requests names explicitly (ratified + soju.im/ forms)Full
labeled-responseCAP negotiated; labels tie server responses to outgoing commandsNegotiated
multi-prefixNAMES returns all mode prefixes (e.g. @+ for op+voice)Full
away-notifyReal-time away/back status tracked per-nickFull
chghostSingle quiet status line instead of fake QUIT+JOIN floodFull
account-notifyUpdates per-nick account field on services login/logoutFull
account-tagAccount name shown as tooltip on nick and in chat view per-messageFull
extended-joinPopulates account on join without needing WHOISFull
invite-notifyPosts invite lines to channel or server buffer depending on contextFull
setnameReceives real-name changes; /setname changes your own without reconnectFull (send + recv)
userhost-in-namesStrips !user@host from display; stores for future ban/ignore workFull
MonitorSends MONITOR + after welcome; /monitor add|del|list|status commandsFull
WHOXDetects ISUPPORT token; uses WHO %cnfa,42 when available; falls back to plain WHOFull
stsCaches STS policy to disk (sts.ini); enforces TLS on reconnect (HSTS equivalent)Full
standard-repliesRoutes FAIL/WARN/NOTE to the correct buffer with colored [FAIL]/[WARN]/[NOTE] prefixFull
cap-notifyHandles CAP NEW / CAP DEL to stay in sync if server cap set changes at runtimeFull
netsplit batch typeCollapses QUIT flood into one summary line per channelFull
netjoin batch typeCollapses JOIN flood into one summary line per channelFull
UTF8ONLY ISUPPORTDetects token in 005; posts status line to server bufferDetected
WebSocket transportws:// or wss:// per-server; all features (SASL, STS, proxy) work identicallyFull
znc.in/playbackSends PLAY * 0 to ZNC on connect; renders replayed messages with original timestampsZNC only
znc.in/self-messageEchoed messages from other clients show as your outgoing messagesZNC only
soju.im/bouncer-networksLists attached networks after CAP negotiationsoju only
soju.im/readSyncs read position across all clients via MARKREADsoju only

Full = negotiated and actively used. Detected / Negotiated = negotiated, feature-flagged internally. ZNC / soju only = only requested when bouncer type is configured. Full details: ircv3.md.

Part of every build, not a one-time check

Four tools run against the codebase as part of the standard build and CI workflow. The current build is clean across all four.

AddressSanitizer + UndefinedBehaviorSanitizer
$ cmake -B build-san -DUPLINK_BUILD_TESTS=ON -DUPLINK_ENABLE_SANITIZERS=ON $ ./build-san/tests/tst_ircparser && ./build-san/tests/tst_chatformat ********* TstIrcParser ********* PASS : 16 test cases Totals: 18 passed, 0 failed, 1ms ********* TstChatFormat ********* PASS : 17 test cases Totals: 19 passed, 0 failed, 1ms ASan: 0 heap errors · 0 stack errors · 0 use-after-free · 0 leaks UBSan: 0 integer overflows · 0 null dereferences · 0 misaligned access
clang-tidy - bugprone-* + clang-analyzer-*
$ clang-tidy -p build/compile_commands.json \ --checks='-*,bugprone-*,clang-analyzer-*' \ src/irc/ircparser.cpp src/irc/ircclient.cpp \ src/ui/chatrenderer.cpp src/irc/stsstore.cpp 0 open bugs · current build is clean
cppcheck 2.21 - 47 source files
$ cppcheck --enable=all --std=c++20 -I src src/ 47/47 files checked 100% done 0 real bugs found · current build is clean
clazy - Qt-aware static analysis
$ clazy-standalone --checks="$(tr -d '\n' < .clazy)" src/**/*.cpp Level 0 + 1 checks: container detach in loops, QString allocation churn, signal/slot misuse, unneeded heap allocations of QObjects 0 warnings · enforced in CI, any warning fails the pull request
GitHub CodeQL - security-and-quality
Runs automatically on every push to main, every pull request, and weekly. Query suite: security-and-quality (broader than default: covers code quality + security) Semantic analysis · data-flow tracking · taint analysis Catches: buffer overflows, use-after-free, SQL/command injection patterns, unvalidated input, resource leaks, null dereferences Results published to GitHub Security tab → Code scanning alerts

Sanitizer build: cmake -DUPLINK_ENABLE_SANITIZERS=ON (requires GCC or Clang). libFuzzer mode: cmake -DUPLINK_BUILD_FUZZ=ON (requires clang). CodeQL: .github/workflows/codeql.yml (runs in GitHub Actions).

Compiled clean under 8 warning flags

No suppressed warnings. Every warning flag is on for the main binary. Optional -Werror and -fsanitize=address,undefined modes are available as CMake options.

-Wall
Enables all standard warnings. Catches unused variables, missing returns, implicit conversions, and more.
-Wextra
Extra checks beyond -Wall: missing field initializers, unused parameters, sign comparison.
-Wpedantic
Enforces strict ISO C++20 conformance. Rejects GNU extensions and non-standard constructs.
-Wconversion
Warns on implicit type narrowing, a common source of subtle integer bugs at protocol boundaries.
-Wsign-conversion
Warns specifically on signed/unsigned conversions, which matter when mixing Qt's qsizetype with size_t.
-Wshadow
Prevents local variables silently hiding class members, a hard-to-spot bug in deep call stacks.
-Wnon-virtual-dtor
Warns when a class with virtual functions has a non-virtual destructor, preventing silent UB in polymorphic delete.
-Wold-style-cast
Bans C-style casts. All casts are explicit (static_cast, reinterpret_cast) and intentional.
CMakeLists.txt - compiler flags
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(Uplink PRIVATE -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wshadow -Wnon-virtual-dtor -Wold-style-cast ) endif() # Optional strict modes: option(UPLINK_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF) option(UPLINK_ENABLE_SANITIZERS "Enable ASan/UBSan" OFF)

Every push is built, tested, and scanned

Three CI workflows run on every push to main and every pull request. Nothing merges without passing all of them.

Build matrix
Compiled and tested on Linux (Ubuntu 24.04), Windows (MSVC 2025), and macOS on every push. Tests run on all three platforms, not just Linux.
Sanitizer job
Dedicated ASan + UBSan build on every push. Tests run under full memory and undefined-behavior instrumentation.
CodeQL analysis
GitHub's semantic code scanner runs on every push, every PR, and weekly. Uses the security-and-quality query suite for deep data-flow and taint analysis.
Qt static analysis
clazy, KDE's Qt-aware clang plugin, checks the full source tree on every push and PR for Qt-specific mistakes: container detaches, string allocation churn, signal/slot misuse. Any warning fails the job.
Release workflow
Tag pushes trigger platform builds that produce Linux, Windows, and macOS binaries attached to GitHub Releases automatically.

Strict layer separation

The UI never touches the socket. The IRC layer never touches widgets. Data flows one direction through Qt signals, with no circular dependencies.

┌─ IrcParser src/irc/ircparser.cpp (100 lines) Pure function. IrcParser::parse(QString) → IrcMessage. No state, no Qt I/O. Handles tags, prefix, params, trailing, CRLF stripping, server-time decoding. ├─ IrcClient src/irc/ircclient.cpp (1 677 lines) Owns the QSslSocket. Drives CAP LS 302 handshake, SASL, STS, all commands. Emits signals; the UI never calls methods directly on the socket. ├─ SessionModel src/model/sessionmodel.cpp In-memory state: servers, channels, nick lists, message history. Receives IrcClient signals, transforms them into model updates. ├─ ChatRenderer src/ui/chatrenderer.cpp (872 lines) Pure functions: mIRC → HTML, linkify, HTML attribute escaping. All XSS-critical paths tested. No HTML injection possible from message content. └─ MainWindow / ChannelPane src/ui/mainwindow.cpp (4 644 lines) Reads model. Sends commands via IrcClient slots. Never touches socket or parser. ────────────────────────────────────────────────────────────── Signal flow: IrcClient ──► SessionModel ──► MainWindow (Qt signal/slot, never direct call) SSL: QSslSocket default · plain optional per-server · STS enforces upgrade Config: TOML via toml++ · passwords via OS keychain (QtKeychain 0.14)
🔒

No HTML injection from IRC

All IRC message text passes through htmlEscape() before rendering. mIRC colour/bold codes are translated to inline CSS spans, so untrusted input can never inject raw HTML.

🔌

SSL default, not optional

All connections use QSslSocket. Plain TCP is available as an explicit opt-in. STS policy enforcement means once a server advertises STS, plain connections are refused permanently.

🛡️

Credentials in the OS keychain

Server passwords and SASL credentials are stored via QtKeychain, the system credential store (libsecret / Windows Credential Manager / macOS Keychain), not plaintext config files.

📐

Bounded batch processing

IRCv3 batches are bounded at 8 simultaneous and 1 000 messages each. A server that opens a batch and never closes it cannot grow client RAM without limit.

✂️

Long message splitting

Outgoing messages that exceed the 512-byte IRC line limit are automatically split at word boundaries before sending. No truncated lines, no server errors.

🚧

Paste flood guard

Multi-line pastes prompt before sending when they exceed the flood threshold, preventing accidental channel spam from paste operations.

Light on memory

A full IRCv3 client (TLS, SASL, link previews, DCC) with a resident footprint smaller than a browser tab. No Electron, no bundled Chromium. Your mileage varies with how many networks, channels, and scrollback you keep open, but the baseline stays light.

~60 MB
Resident memory
System monitor sorted by memory: Uplink at 60.5 MiB, below Chromium and Mailspring
Applications sorted by memory. Uplink at 60.5 MiB, under Chromium (133.9) and Mailspring (61.7).

What "drilled-down" actually means here

Here's what the numbers above add up to.

Correct IRC parsing

13 unit tests cover every edge case of the RFC 1459 / IRCv3 message format: tags, prefix types, multi-param, trailing-only, CRLF stripping, server-time decoding, tag unescaping, and malformed input. Confirmed by 50 real-world message inputs in the fuzz corpus.

Safe HTML rendering

17 unit tests cover XSS-critical paths: <, &, ", mIRC bold/italic/underline/colour with proper reset, linkification that only wraps http/https (not ftp), highlight-word rendering, and empty-input edge cases. A dedicated fuzz harness also hammers the renderer with hostile input: mIRC codes, emoji/ZWJ runs, RTL overrides, invalid UTF-8. No raw IRC text reaches the browser engine unescaped.

Analysis clean

The test suite runs under AddressSanitizer and UndefinedBehaviorSanitizer on every push. Zero heap errors, zero stack errors, zero integer overflows, zero null dereferences. clang-tidy, cppcheck, and GitHub CodeQL also run each build, all clean.

Protocol depth

38 IRCv3 capabilities, each with an implementation that was tested against a live Ergo IRCd instance. Bouncer-specific caps are only requested when a bouncer type is configured.

No silent surprises

8 warning flags catch narrowing, shadowing, sign mismatches, C-style casts, and non-virtual destructors at compile time, before they become runtime bugs. The codebase compiles clean at all 8 levels with GCC 16.

AI in development

Yes, we use AI coding tools. Here's why that doesn't change what you're getting.

UplinkIRC is being built by a group of volunteers, some with professional development backgrounds, and over 100 years of combined time on IRC networks. AI helped us write code faster. It didn't decide what the security model should be, which IRCv3 caps to support, or how to handle credentials. Those calls came from people who've been using and building on IRC for decades. The tool doesn't replace the judgment; it just types faster than we do.

Security is deliberate

TLS enforced by default; invalid certs disconnect immediately. Passwords stored in the OS keychain, not config files. Config written atomically with 0600 permissions. CTCP replies rate-limited per nick. DCC filenames sanitized against CTCP injection, stalled transfers detected and aborted. Link previews block RFC 1918 and loopback to prevent LAN probing. Reactions accepted only for messages actually held in the buffer, so fabricated msgids can't grow memory. None of this is accidental.

Protocol knowledge is real

37 IRCv3 protocol features with full CAP LS 302 negotiation, SASL PLAIN and EXTERNAL, STS policy enforcement, bouncer-specific extensions for ZNC and soju. You don't get these right by prompting an LLM. You get them right by reading the specs and testing against live servers.

Tested and shipped

Unit tests for IRC parsing and HTML rendering, including malformed input. CI pipeline across Linux, Windows, macOS, and FreeBSD. Every release ships with a real changelog. This is a maintained project with discipline, not a one-shot dump.

The code is public

The project is open source, the code is auditable, and the security decisions are documented. Anyone who wants to evaluate it is welcome to read it. The question isn't "was AI used", it's "is this safe?" The answer is in the codebase.