Security audit of TigerBeetle, a financial transactions database designed for mission-critical safety and performance. Each finding includes a detailed write-up and a patch.
The original automated report was reviewed by hand against the repository's actual threat model and coding style (docs/TIGER_STYLE.md). Three of the originally surfaced findings were dropped as false positives or as disputes with intentional design choices; the remaining ten have been re-graded and their write-ups annotated with the corresponding threat-model context.
Total findings: 10 -- Medium: 3, Low: 7
Three additional reports were investigated and discarded:
- An alleged PID 1 sandbox bypass in
src/stdx/unshare.zig— the PID-1 path is reached only by the re-exec'd inner child of a previousunshare, which has already inherited the parent's network namespace and loopback setup. The proposed patch would actively break the design. - Two reports of "unknown VSR command crashes the replica" (
message_buffer.zigandmessage_header.zig) — the crash is deliberate, exposed asFatalReason.unknown_vsr_commandand documented as "crashing for safety". This is a design choice, not a bug.
| # | Finding | Severity |
|---|---|---|
| 001 | devhub PAT exposed in git clone argv |
Low |
| 002 | Unchecked Mach-O slice bounds abort the multiversion build | Low |
| # | Finding | Severity |
|---|---|---|
| 003 | Oversized AMQP frame trips a receive-buffer assertion | Low |
| 004 | Unknown AMQP method traps the client decoder | Low |
| 014 | Unsupported AMQP field tag terminates the process | Low |
| 015 | Generator-side counterpart of finding 004 (fixes spec_parser.py) |
Low |
| # | Finding | Severity |
|---|---|---|
| 007 | deinit leaves the JavaScript external pointing at freed memory |
Medium |
| # | Finding | Severity |
|---|---|---|
| 008 | Malformed WAL prepare size crashes tigerbeetle inspect |
Low |
| 010 | Idle accepted sockets exhaust the connection slot pool | Medium |
| 013 | Oversized checkpoint trailer reference writes past block arrays | Medium |
Findings 003, 004, 014, and 015 all live on the CDC AMQP receive path. The broker is part of the operator-configured topology rather than an arbitrary network attacker, and the receive loop in src/cdc/amqp.zig already calls fatal() on any Decoder.Error.Unexpected. So in their patched form these findings primarily replace ad-hoc traps (assertion trips, @enumFromInt on attacker-controlled bytes, deep fatal calls in table decoding) with a single well-defined error path that routes through the established decoder-error channel. The DoS outcome (process exit) is largely the same in patched and unpatched code; what changes is attributability, error-message quality, and the ability to later move to reconnect-on-protocol-error without further refactoring.