ADR-013: Rust FFI preload-only mode 

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.