ADR-013: Rust FFI preload-only mode
Table of contents
Status
Accepted
Date
2026-01-12
Context
PHP FFI is powerful but increases attack surface if enabled broadly. Dynamic
FFI::cdef() at runtime allows binding arbitrary native symbols, which is
risky in web contexts.
We need a production-safe operational model that reduces risk and keeps behavior predictable.
Decision
If we ship/use a Rust FFI transport, we require:
- Production deployments run with
ffi.enable=preload(or an equivalent hardened configuration) - FFI bindings are created in preload (e.g.
opcache.preload) and not dynamically in request handling - The PHP layer exposes only a limited wrapper API (no arbitrary symbol access)
- We provide a non-FFI fallback transport and keep it as the default
Consequences
Positive
- Smaller attack surface vs full runtime FFI
- More predictable behavior and better operability
- Easier to audit what native code is actually callable
Negative
- Requires ops work (preload configuration)
- Some hosting environments will still refuse FFI entirely → fallback must work
Alternatives considered
Enable full FFI at runtime
Use ffi.enable=true to allow dynamic FFI calls.
Rejected: unacceptable risk in typical web hosting setups.
Use ext-php-rs or custom PHP extension
Build a native PHP extension in Rust.
Deferred: could be considered later, but increases maintenance and build complexity.