Security-relevant usage guidance for TrussC and its official addons. If you ship an application built on TrussC, please read this before accepting any file, URL, or network input from outside your own build.
This doc focuses on what you need to know as an application author. Implementation-level audit findings and their fixes live in the repo's commit history (prefix
fix(…)/deps:).
TrussC's parsers and loaders fall into three tiers. Your application's threat model should match the tier of each input source.
| Tier | Meaning | Examples |
|---|---|---|
| Safe | Hardened or defense-in-depth covered. Okay for untrusted input. | Xml (pugixml), Json (nlohmann), OscReceiver, TcpClient byte stream. |
| Caution | Works for well-formed files. Malformed or crafted input may cause crashes or resource exhaustion. Okay for files you ship; review before exposing to network/drop-in. | Image::load (stb_image), VideoPlayer, tcxHap MOV parser, tcxObj loader, tcxGltf. |
| Unsafe | Upstream does not claim memory safety. Must not be fed attacker-controlled data. | Font::load / anything going through stb_truetype. |
core/include/stb/stb_truetype.h ships with this note from the author:
NO SECURITY GUARANTEE — This library does no range checking of the offsets found in the file, meaning an attacker can use it to read arbitrary memory.
TrussC's Font class and anything that renders text through it uses
stb_truetype. This is fine for fonts bundled with your application. It is
not fine if your application lets a user drop in a font, downloads a font
from a URL, or otherwise loads fonts the author did not ship.
If you need to accept user-supplied fonts, options in order of preference:
- Reject the feature (only ship fonts you built with).
- Run font rendering in a sandbox/subprocess.
- Replace the backend with FreeType (no built-in support today — patches
welcome; tracked informally in
docs/ROADMAP.md).
Best-effort validation is applied (atom size bounds, progress checks). Crafted
files can still trigger early-return on parse, so make sure your app tolerates
MovParser::open() returning false. Do not trust any field read from the
file until it has been validated.
Mesh loaders. Index/offset validation is limited; crafted files can cause out-of-bounds reads. Treat these as "caution" tier.
TlsClient and WebSocketClient verify the server certificate chain against
a trust anchor. A failed verification aborts the handshake.
On the first handshake, TlsClient resolves a trust anchor set using the first
of the following that succeeds:
- User-supplied PEM — if you called
setCACertificate()/setCACertificateFile()(onTlsClient) orsetTlsCACertificate()(onWebSocketClient). This replaces the default set; nothing else is consulted. Use this for private CAs or when you want to pin to a specific issuer. - OS trust store — on POSIX, a well-known bundle path
(
/etc/ssl/cert.pem,/etc/ssl/certs/ca-certificates.crt,/etc/pki/tls/certs/ca-bundle.crt, …). On Windows, the systemROOTstore viaCertOpenSystemStoreW. This is the typical path and picks up OS updates automatically. - Bundled Mozilla roots — a copy of
https://curl.se/ca/cacert.pemis embedded intcxTlsat build time and used as a fallback when no OS store is readable. Refresh withaddons/tcxTls/scripts/update_ca_bundle.sh; this should be done every few months so the bundled set tracks Mozilla's distrust decisions.
The source that was used is logged at notice level on first connect, e.g.:
[notice] TlsClient: loaded 150 CAs from /etc/ssl/cert.pem
[notice] TlsClient: loaded 130 CAs from bundled cacert.pem (Certificate data from Mozilla last updated on: …)
If all three sources fail, an error is logged and every subsequent handshake will fail until an explicit PEM is provided or verification is turned off.
Self-signed certs on localhost, staging boxes, etc.:
// tcxTls
TlsClient tls;
tls.setVerifyNone(); // skip verification entirely
tls.connect(host, port);
// tcxWebSocket
WebSocketClient ws;
ws.setTlsVerifyNone(); // skip verification
// or
ws.setTlsCACertificate(pem); // supply a custom CA (PEM string)
ws.connect("wss://internal.example/");Never ship setVerifyNone() to end users — it silently allows MITM.
tcxTls pins mbedTLS to the v3.6.x LTS branch via FetchContent. When bumping
to a newer patch release, also update the version row in docs/LICENSE.md and
the note in docs/ROADMAP.md.
tcxCurluses libcurl withCURLOPT_SSL_VERIFYPEER/VERIFYHOSTat their defaults (both enabled). Automatic redirect following is off.tcxWebSocketrefuses to silently downgradewss://to an unverified session. A user who wants that must callsetTlsVerifyNone()explicitly.
getDataPath(p)does not reject..segments. Do not pass attacker- controlled strings to it.HotReloadHostinvokes the CMake build tool viastd::system(); the build directory path is derived from the canonicalized executable path, not user input. Only a developer who places their build under a path containing shell metacharacters is exposed.IdeHelperintrusscliexecutessystem()with the imported project path. Do not runtrusscliagainst project paths you don't trust.
Not adversarial-input related, but a memory-safety pitfall worth knowing: the
Node tree and GPU state are owned by the main thread. Touching them from a
worker thread — most commonly inside a network onReceive handler, which fires
on the receive thread — is a data race that crashes. Use runOnMainThread(...)
or an Event listener registered with Deliver::Main. See
ARCHITECTURE.md → 5.E Threading for the full
contract (which callbacks run off the main thread, the safe patterns, and the
debug-only main-thread assert).
Security issues should be reported privately via GitHub Security Advisories on
the repo, or by email to the maintainer (see README.md). Please do not open
public issues for security bugs.