Use GitHub's private vulnerability reporting on this repository — the Security tab → Report a vulnerability. That opens a private advisory visible only to the maintainers; please use it rather than a public issue for anything with a security impact.
Include what you would want to receive: the affected version or commit, the transport and build
configuration (which LIBTRACER_* options, which policy, which target), and the smallest
reproducer you have. A frame dump or a failing test is worth more than a description.
We aim to acknowledge a report within a week. libtracer is solo-maintained and pre-1.0, so please assume best effort rather than a contractual window; if a report is time-critical, say so in the first message.
libtracer is pre-1.0 and the wire protocol is still marked DRAFT
(docs/spec/v1.md). Only the latest released version receives fixes.
There are no maintained release branches, and no backports to earlier tags.
This section exists because the biggest security risk in a library like this one is a correct component used under an incorrect assumption. The design decisions below are ratified in ADRs and linked; where the shipped code differs from the ratified design, that is called out explicitly, because the gap is the part an integrator most needs to know.
libtracer implements authorization: a device-held mapping from an opaque subject token to rights on a vertex, enforced locally, as NFSv4-style ACEs with inheritance (ADR-0018, ADR-0020).
It does not implement authentication. The core contains zero crypto and never verifies a
credential. Where the token comes from is the integrator's subject_resolver_t — a function from
caller context to opaque bytes. The FWD terminus passes the inbound link as the caller context.
The consequence, stated in ADR-0018 and repeated here because it is the load-bearing one:
An ACL is meaningful exactly to the degree the transport authenticates the token.
On a link that does not authenticate its peer, the ACL is advisory. It will stop an honest mistake. It will not stop an attacker, because an attacker chooses which link to connect on and the resolver's answer is derived from that link.
As shipped:
- ACL enforcement is off until a subject resolver is installed. With no resolver,
graph_t::acl_allowsdoes not gate at all — local, in-process use is trusted by construction. - A vertex whose effective ACL is empty is unrestricted.
NO_MATCHis deliberately notDENY; the open-by-default decision belongs to the caller, not the policy.
There is one sharp edge in the other direction: because any present ACE closes an otherwise-open vertex, writing an ACE that matches nobody locks the vertex rather than leaving it open. Verify that a stored ACE's subject is one your resolver can actually return.
Design/implementation gap. ADR-0045 §2 specifies that the default ACL ships closed except the auth subtree. That is the ratified target, not the current behaviour — there is no
anonymoussubject and no/device/auth/*incore/today. Do not deploy on the assumption that a fresh graph is closed. It is open.
ADR-0045 ratifies how authentication will work — as ordinary graph reads and writes against
app-defined vertices (read /device/auth/challenge → nonce; write /device/auth/login → the
connection's subject rebinds through the existing resolver seam), with no wire verb, no protocol
phase, and crypto kept app-side. None of that is implemented in core/. The vertices are
app-defined by design, so an integrator can build the flow today; the core provides the seam and
nothing else.
A FWD frame arriving over an authenticated link carries that link's subject. Board 2
authenticates board 1's link, not the browser behind board 1. Multi-hop, origin-attributed
identity does not exist and is explicitly out of scope
(ADR-0045 §5). If your
deployment needs it, layer signed payloads at the application level.
| Transport | Confidentiality |
|---|---|
| QUIC / WebTransport | TLS 1.3 by construction (ADR-0043) |
| WebSocket / TCP | plaintext |
| CAN | none — out of scope; a physically-trusted enclosure-internal harness |
The QUIC dialer exposes quic_dial_tls_t::insecure_no_verify, which skips server-certificate
validation entirely. It exists to reach a self-signed development certificate and is named to
be greppable. It must never be set in a deployed build.
For plaintext MCU links the intended answer is a Noise-pattern channel (the module catalog's
security_noise slot) rather than dragging X.509 and a CA store onto a 16 KB-class device.
That module is not implemented. Until it is, a WS/TCP link is in the clear, and an
authenticated session on such a link can be hijacked mid-flight by an on-path attacker.
Not missing — rejected (ADR-0045 §5). It reintroduces a central authority into a deliberately decentralized system, and certificate-chain parsing is precisely the footprint the target class cannot carry. The identity roadmap is raw ed25519 keypairs with trust-on-first-use — the public key is the identity, the SSH/WireGuard model rather than certificate enrollment — which slots into the ACL model unchanged. That roadmap is not implemented.
The optional trailer CRC detects bit flips on a lossy link. It is not a message authentication code and takes no adversarial stance (ADR-0004). An attacker who can modify a frame can recompute it.
The token space is opaque bytes with exactly one carved-out spelling: EVERYONE@ is
reserved, and the core enforces the reservation on the resolver's output — a caller that
resolves to it is refused at every gate, fail-closed
(#908). This matters for the deployments
most at risk: those whose resolver passes a caller-supplied identity through (a username, a
certificate CN, a peer name) could otherwise mint a principal indistinguishable from the
wildcard.
OWNER@ is not a special subject. Earlier revisions of the reference docs said it was; no
evaluator ever special-cased it, and it has been withdrawn
(#1033). Treat it as an ordinary token
with no owner semantics attached.
A lenient read of an access-control document does not lose a field — it changes what the document
grants. A type sent as a big-endian u16 0x0001 (DENY) has 0x00 in its low byte, so a
width-tolerant load turns a refusal into a grant; a dropped expires_ns turns a time-limited
grant permanent. parse_acl therefore rejects over-wide numeric payloads, wrong-typed values,
unknown keys, repeated keys and broken key/value pairing
(#906). Unknown-key tolerance is granted
for SETTINGS and deliberately not for ACLs.
A short checklist that follows from the above:
- Install a
subject_resolver_t. Without one there is no enforcement at all. - Do not let the resolver pass caller-supplied identity through unfiltered.
- Terminate untrusted links on TLS-carrying transports (QUIC / WebTransport), or accept that the ACL on a plaintext link is advisory.
- Never ship
insecure_no_verify. - Check that every stored ACE names a subject your resolver can return — an ACE that matches nobody locks the vertex.
- Do not attribute a multi-hop
FWDto its origin. It carries the last hop's subject.