From a memory-safety audit of nobilis on 2 October 2026: its own code has two unsafe blocks (both sound libc calls in main.rs), so what follows is about the code it depends on. Advisories were checked against the OSV/RustSec database for every crate in Cargo.lock, and each one traced to whether it is actually compiled in.
The one place nobilis feeds data from strangers into C code: libopus 1.5, built by libopus_sys and wrapped by opus2 (31 unsafe lines in the wrapper). It decodes:
every voice packet from everybody in a Discord call or stage, and the audio of any Go Live stream being watched;
soundboard sounds in Ogg Opus, which anyone in a server can upload.
All of that arrives after DAVE and transport decryption, so it comes from another participant rather than from the network at large - but any participant is enough. libopus is fuzzed continuously (OSS-Fuzz) and is the decoder every browser ships, so this is a choice to make consciously, not an emergency:
keep the bundled libopus on the newest 1.5.x and watch its advisories (record which version is bundled where it can be checked);
bound what reaches it - packet sizes, frame counts, soundboard file size - before the call;
consider a pure-Rust Opus decoder when one is mature enough for both voice and the Ogg path, or decoding in a separate, sandboxed process.
From a memory-safety audit of nobilis on 2 October 2026: its own code has two `unsafe` blocks (both sound libc calls in `main.rs`), so what follows is about the code it depends on. Advisories were checked against the OSV/RustSec database for every crate in `Cargo.lock`, and each one traced to whether it is actually compiled in.
The one place nobilis feeds data from strangers into C code: **libopus 1.5**, built by `libopus_sys` and wrapped by `opus2` (31 `unsafe` lines in the wrapper). It decodes:
- every voice packet from everybody in a Discord call or stage, and the audio of any Go Live stream being watched;
- soundboard sounds in Ogg Opus, which anyone in a server can upload.
All of that arrives after DAVE and transport decryption, so it comes from another participant rather than from the network at large - but any participant is enough. libopus is fuzzed continuously (OSS-Fuzz) and is the decoder every browser ships, so this is a choice to make consciously, not an emergency:
1. keep the bundled libopus on the newest 1.5.x and watch its advisories (record which version is bundled where it can be checked);
2. bound what reaches it - packet sizes, frame counts, soundboard file size - before the call;
3. consider a pure-Rust Opus decoder when one is mature enough for both voice and the Ogg path, or decoding in a separate, sandboxed process.
Salastil
added this to the v1.0 milestone 2026-10-02 19:03:39 -04:00
Salastil
added the bugCVE labels 2026-10-02 19:03:39 -04:00
Salastil
removed this from the v1.0 milestone 2026-10-03 21:31:18 -04:00
Layers 1 and 2 are done. Layer 3 is still open, so I've left the issue open too.
Layer 1: which libopus, and keeping it current
The bundled source (libopus_sys 0.3.3) is libopus 1.5.2, the newest 1.5.x. I confirmed it file by file against upstream's tags.
Release builds always use the bundled copy, but it reported "libopus unknown" because it has no version file. Now nobilis/.cargo/config.toml names it, and a test fails if libopus_sys changes without that being revisited.
Settings › About shows the decoder each build actually links ("Audio decoder"): 1.5.2 in a release, the system's own when one was installed at build time (1.6.1 on Arch).
The build scripts rebuild a stale bundled libopus that still says "unknown", so neither CI's cache nor a local build directory can keep it.
Layer 2: limiting what reaches the decoder
opus_packet::plausible checks RFC 6716's packet structure in Rust before libopus reads anything: frame sizes, frame counts and length fields, padding, and that the audio fits the decoder's buffer.
Voice: a packet that fails is treated as lost and concealed like any gap.
Soundboard: failing packets are skipped, and files are capped at 1 MB (Discord's own limit is 512 KB). The cap is checked while downloading, not just from the declared size.
Tested: everything libopus's own encoder makes passes (90 combinations of mode, bitrate and frame size); malformed packets of every kind are refused; 20,000 random inputs never panic; a real Ogg Opus file still decodes whole.
Open for later
libopus 1.6.1 is out. Using it would mean vendoring a patched libopus_sys, because opus2 0.4 only accepts 0.3.x. It has more fuzzing-era fixes, but also new decoder code.
Layer 3:opus2 0.4 already has a second backend, backend-mousiki, built on a pure-Rust Opus implementation (mousiki). Switching to it would remove the C decoder entirely, once it proves good enough for voice and soundboard.
Layers 1 and 2 are done. Layer 3 is still open, so I've left the issue open too.
**Layer 1: which libopus, and keeping it current**
- The bundled source (`libopus_sys` 0.3.3) is **libopus 1.5.2**, the newest 1.5.x. I confirmed it file by file against upstream's tags.
- Release builds always use the bundled copy, but it reported "libopus unknown" because it has no version file. Now `nobilis/.cargo/config.toml` names it, and a test fails if `libopus_sys` changes without that being revisited.
- Settings › About shows the decoder each build actually links ("Audio decoder"): 1.5.2 in a release, the system's own when one was installed at build time (1.6.1 on Arch).
- The build scripts rebuild a stale bundled libopus that still says "unknown", so neither CI's cache nor a local build directory can keep it.
**Layer 2: limiting what reaches the decoder**
- `opus_packet::plausible` checks RFC 6716's packet structure in Rust before libopus reads anything: frame sizes, frame counts and length fields, padding, and that the audio fits the decoder's buffer.
- Voice: a packet that fails is treated as lost and concealed like any gap.
- Soundboard: failing packets are skipped, and files are capped at 1 MB (Discord's own limit is 512 KB). The cap is checked while downloading, not just from the declared size.
- Tested: everything libopus's own encoder makes passes (90 combinations of mode, bitrate and frame size); malformed packets of every kind are refused; 20,000 random inputs never panic; a real Ogg Opus file still decodes whole.
**Open for later**
- **libopus 1.6.1** is out. Using it would mean vendoring a patched `libopus_sys`, because `opus2` 0.4 only accepts 0.3.x. It has more fuzzing-era fixes, but also new decoder code.
- **Layer 3:** `opus2` 0.4 already has a second backend, `backend-mousiki`, built on a pure-Rust Opus implementation (`mousiki`). Switching to it would remove the C decoder entirely, once it proves good enough for voice and soundboard.
- nobilis: https://git.salastil.com/Salastil/nobilis/commit/8eca16e
- moho: https://git.salastil.com/Salastil/moho/commit/1782558
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
From a memory-safety audit of nobilis on 2 October 2026: its own code has two
unsafeblocks (both sound libc calls inmain.rs), so what follows is about the code it depends on. Advisories were checked against the OSV/RustSec database for every crate inCargo.lock, and each one traced to whether it is actually compiled in.The one place nobilis feeds data from strangers into C code: libopus 1.5, built by
libopus_sysand wrapped byopus2(31unsafelines in the wrapper). It decodes:All of that arrives after DAVE and transport decryption, so it comes from another participant rather than from the network at large - but any participant is enough. libopus is fuzzed continuously (OSS-Fuzz) and is the decoder every browser ships, so this is a choice to make consciously, not an emergency:
Layers 1 and 2 are done. Layer 3 is still open, so I've left the issue open too.
Layer 1: which libopus, and keeping it current
libopus_sys0.3.3) is libopus 1.5.2, the newest 1.5.x. I confirmed it file by file against upstream's tags.nobilis/.cargo/config.tomlnames it, and a test fails iflibopus_syschanges without that being revisited.Layer 2: limiting what reaches the decoder
opus_packet::plausiblechecks RFC 6716's packet structure in Rust before libopus reads anything: frame sizes, frame counts and length fields, padding, and that the audio fits the decoder's buffer.Open for later
libopus 1.6.1 is out. Using it would mean vendoring a patched
libopus_sys, becauseopus20.4 only accepts 0.3.x. It has more fuzzing-era fixes, but also new decoder code.Layer 3:
opus20.4 already has a second backend,backend-mousiki, built on a pure-Rust Opus implementation (mousiki). Switching to it would remove the C decoder entirely, once it proves good enough for voice and soundboard.nobilis: https://git.salastil.com/Salastil/nobilis/commit/8eca16e
moho: https://git.salastil.com/Salastil/moho/commit/1782558