This document states what BerryAPI trusts, what it refuses, and why. It is the threat model for the injected library; it is not a claim that the game is secure, only that this library does not widen the attack surface it is injected into.
libBerryAPI.so is loaded into the Minecraft Bedrock process by a host (an
injector, a JNI descriptor, or an LD_PRELOAD-style loader). Everything the
library does runs with the game's own privileges. The library therefore assumes:
- The host that loads it is trusted. A host that can
dlopenthe library can already run arbitrary code, so the library does not try to defend against its own loader. - The game process's memory is trusted for reading. Signature scanning reads
live code; it fails closed on a fault (a
SIGSEGVguard) rather than crashing, but it cannot make a hostile in-process actor safe.
What the library refuses to do:
- It never downloads or executes code. There is no plugin fetch, no JIT of
remote input, no
requireof a network module. - It never relaxes TLS.
TlsSocketisMBEDTLS_SSL_VERIFY_REQUIREDwith the URL host as the expected name, and there is deliberately no "skip verify" or "insecure" mode. When the CA store cannot be loaded,Connectrefuses. - It fails closed on every budget, capability and signature gate.
Scripts run in a sandboxed Luau VM (luaL_sandbox, no require, no raw
io/os escape). The guarantees are:
- Capability gate, fail closed. Every binding declares the capability it
needs. A binding that needs none must declare
Capability::NoCap(); an unset requirement is not a grant and is not grantable, so "the author forgot to declare a capability" can never read as "no capability required". - Budgets. Each invocation is bounded by a real instruction count, a memory cap and a wall clock. A native binding's time is charged against the wall clock, so a blocking call cannot hide behind a cheap counter.
- Manifest signature. A manifest may require a minimum script-API version and is Ed25519-signed over a canonical, domain-separated, length-prefixed payload, so no field can be spliced into another. An empty trust anchor (with signing required) refuses every manifest.
- No information the client was not already sent. Client-side modules (HUD, overlays, cosmetic resolve, game reads) read only state the local client already holds. There is no ESP, reach or aim-assist surface: the world/entity seam is the client's own view.
- Egress policy. A request is checked against a
UrlPolicy(https only, no IP literals, no private ranges by default) and the script's network grant. A redirect is re-checked on every hop. - HTTP. Chunked decoding,
Content-Lengthenforcement with a truncation flag, CR/LF/NUL rejection in URLs and headers, duplicate client-owned header rejection, IPv6/userinfo/port-range authority parsing. - WebSocket. Random handshake key,
Sec-WebSocket-Acceptverification,Upgrade/Connectionrequired, CSPRNG masking keys under the send lock, a strict client frame parser (masked/non-minimal/top-bit rejected), and a protocol close (1002/1009) instead of a silent desync.
The LAN transports move bytes only; the channel verifies before anything is applied.
- Voice is end-to-end encrypted and authenticated: Ed25519 identity handshake, X25519 + HKDF pairwise key, ChaCha20-Poly1305 frames, and a sliding-window replay filter that snapshots and rolls back when the AEAD tag fails (a forged frame cannot push a real one out of the window).
- Proximity is judged against the listener's own world view, so a speaker the listener's world does not hold is dropped; the module never reveals a player the client was not already sent.
- The relay is blind.
VoiceRelaynever holds a key and never decrypts; it forwards the sealed frame untouched and cannot forge audio. A per-room secret gates registration, so it is not an open amplifier. - Room tokens fail closed. Signature verification is required unless the
deployment explicitly opts in to TLS-only (
allowUnsigned); expiry is required; relay port/expiry are integer-checked; a loopback/private relay host is refused unlessallowPrivateRelayHostis set.
- Every exported C entry catches all exceptions, so a
std::bad_alloccannot callstd::terminateacross the ABI boundary. - A crash handler on an alternate signal stack writes a backtrace with module-relative offsets and the last log line to app-private storage, then re-raises the original signal.
- A consecutive-startup-crash counter lets the next boot enter safe mode (hooks and scripts disabled) rather than crash-looping.
Open a private security advisory on the repository, or contact the maintainer
directly. Please include a minimal reproducer and the BerryAPI version
(BERRY_Version()).