A protoc-free build for OxiProto’s own consumers was only half the job — every other prost-build and tonic-build project out there was still shelling out to a C++ binary.
Today we released OxiProto 0.1.4 — the COOLJAPAN Pure-Rust Protocol Buffers stack. This release turns OxiProto from “a protoc-free alternative for its own users” into a drop-in protoc replacement that any Rust project can point at, plus it closes a real decode-time denial-of-service gap.
No C++. No -sys crate. No apt-get install protobuf-compiler. oxiproto-protoc speaks the exact argv surface protoc uses for descriptor-set generation, compiles to a single static binary, and runs anywhere Rust does.
Why OxiProto 0.1.4 is a game changer
0.1.3 made OxiProto’s own build protoc-free. That still left real gaps:
- Every other Rust project using
prost-buildortonic-builddirectly still needed aprotocbinary onPATH— OxiProto being protoc-free didn’t help them at all - OxiProto’s own
oxiproto-codegenandoxiprotobuild scripts still internally calledprost-build::compile_protos, which shells out toprotocunder the hood — a lingering internal dependency behind the protoc-free claim - A maliciously deep nested-message or group payload could exhaust the stack before ever reaching application code — across three separate decode paths, with no shared limit
OxiProto 0.1.4 ends all of that:
oxiproto-protoc— a newprotoc-argv-compatible binary implementing the descriptor-set-generation subset (-I/-o/--include_imports/--include_source_info). Point anyprost-build- ortonic-build-based project’sPROTOCenvironment variable at it and that project compiles without a C++protocinstall — no code changes on their end.- Zero remaining internal
protoccalls —oxiproto-codegen/build.rsnow parses its fixtures withprotoxand generates viacompile_fds;oxiproto/build.rsnow compiles viaoxiproto-build::Builderdirectly. That was the workspace’s last spot that shelled out toprotocviaprost-build::compile_protos. - A shared recursion-depth budget enforced everywhere —
MAX_DECODE_DEPTH = 100(matching theprotobuf/prostnorm) is now checked across all three nested-message decode paths: native reflection, group-skipping, and codegen-generatedmerge(). - A new
oxiproto-examplesworkspace member with three runnable examples, a typedCliErroracross every CLI subcommand, andoxiproto-cli --versionfinally working.
Technical Deep Dive: how oxiproto-protoc replaces protoc for everyone else
1. The argv compatibility layer (oxiproto-cli/src/bin/oxiproto-protoc.rs) parses the exact flags protoc’s callers already send: -I/--proto_path, -o/--descriptor_set_out, --include_imports, --include_source_info, --experimental_allow_proto3_optional, --version, --help. Anything outside the descriptor-set-generation subset — --cpp_out, --plugin, and other codegen flags — is rejected with a clear error instead of silently ignored.
2. The native parser backend — the shim is powered by the same oxiproto-build parser that already handles OxiProto’s own compilation: proto2 and proto3, multi-file import resolution, source_code_info, custom options, and group desugaring.
3. The shared recursion budget — DecodeBuffer::nested() / DecodeBuffer::depth() now expose the depth-budget primitive publicly as oxiproto_core::wire::MAX_DECODE_DEPTH. All three decode paths — DynamicMessage::decode in oxiproto-reflect, DecodeBuffer::skip_field in oxiproto-core, and codegen-generated OxiMessage::merge — now descend through it uniformly, returning WireError::RecursionLimitExceeded instead of risking a stack overflow.
4. Dogfooding completion — with oxiproto-codegen and oxiproto’s own build scripts routed through protox/oxiproto-build::Builder instead of prost-build::compile_protos, the workspace no longer has any internal path that shells out to protoc, directly or indirectly.
Getting Started
Add the runtime and build helper as before:
[dependencies]
oxiproto = "0.1.4"
[build-dependencies]
oxiproto-build = "0.1.4"
New in 0.1.4 — if you maintain (or depend on) a different project that uses prost-build or tonic-build directly, you no longer need a system protoc install for it either:
PROTOC=$(which oxiproto-protoc) cargo build
That single environment variable routes that project’s .proto compilation through OxiProto’s native pure-Rust parser instead of a C++ binary — no changes to that project’s build.rs required.
What’s New in 0.1.4
Added
oxiproto-protoc— theprotoc-argv-compatible binary described aboveoxiproto-examplesworkspace member:encode_decode,reflection, andcodegen_usagerunnable examplesDecodeBuffer::nested()/DecodeBuffer::depth()and publicoxiproto_core::wire::MAX_DECODE_DEPTHoxiproto-core/tests/fuzz_message_decode.rs— message-level property/fuzz suite (arbitrary-bytes-never-panics, round-trips, bit-flip mutation sweep, recursion-limit regression)- Typed
CliErrorenum covering everyoxiproto-clisubcommand failure CONTRIBUTING.mdandSECURITY.md
Changed
- Every
oxiproto-clisubcommand now returnsCliErrorinstead ofBox<dyn std::error::Error> oxiproto-codegenandoxiprotobuild scripts no longer shell out toprotoc(see Deep Dive above)- Dependencies updated:
prost/prost-build/prost-types→ 0.14.4,prost-reflect→ 0.16.5,chrono→ 0.4.45,time→ 0.3.54,clap→ 4.6.4,proptest→ 1.11,base64→ 0.23,syn→ 3,prettyplease→ 0.3 - Every publishable crate now declares
readme = "README.md"so crates.io renders it
Fixed
oxiproto-cli --version/-V(previously rejected as unrecognized)- A broken rustdoc intra-doc link in the new
oxiproto-protocbinary’s crate docs
Security
- Unbounded-recursion stack-overflow DoS fixed across all three nested-message decode paths — see the Deep Dive above for the shared-budget mechanism
Full itemized detail is in the CHANGELOG.
Tips
- Point
PROTOCatoxiproto-protocfor any prost-based project, not just OxiProto’s own.PROTOC=$(which oxiproto-protoc) cargo buildremoves the C++ compiler requirement from that project’s build too — including ones that have nothing to do with OxiProto’s runtime. - Know the argv subset before you rely on it.
oxiproto-protocimplements exactly the descriptor-set-generation flags; a project that needs--cpp_out,--plugin, or other language-codegen flags will get a clear rejection, not silent misbehavior. - Deep or adversarial payloads now fail safely, automatically. Decoding untrusted bytes through any of OxiProto’s three nested-message paths now returns
WireError::RecursionLimitExceededat depth 100 instead of risking a stack overflow — no code changes needed to pick up the protection. - Browse the new examples crate.
examples/(oxiproto-examples, not published) has three runnable, copy-pasteable examples —encode_decode,reflection,codegen_usage— often faster than piecing the API together from docs alone. - Catch typed CLI errors. Every
oxiproto-clisubcommand now returnsCliErrorinstead of a boxed trait object — useful if you embed its subcommands programmatically or need to match on specific failure variants.
This is the foundation
OxiProto is part of NoFFI — the COOLJAPAN initiative to replace every C/C++/Fortran/-sys FFI dependency in the Rust ecosystem with a clean, memory-safe, 100% Pure Rust implementation. Default features remain 100% Pure Rust — no C, C++, or Fortran, no protobuf-compiler system package needed.
OxiProto depends on nothing else in the ecosystem and sits at the bottom of the stack: it’s the foundation for OxiRPC (gRPC), which already tracks this release, and any future crate that speaks the protobuf wire format.
Repository: https://github.com/cool-japan/oxiproto
Star the repo if you want a protobuf toolchain — and now a protoc replacement for everyone else’s build too — that runs anywhere Rust does. 🦀
The era of vendoring a C++ compiler just to speak protobuf is over. Pure Rust serialization is here — fast, safe, and sovereign.
— KitaSan at COOLJAPAN OÜ July 27, 2026