Version 0.1.0
stephp-cap-std is an experimental PHP extension written in Rust. It provides bindings to the cap-std crate, offering a capability-based security approach for filesystem access. This project is made possible thanks to the ext-php-rs project, which provides the tools to build PHP extensions with Rust.
The primary goal is to enable robust sandboxing within PHP scripts. Unlike native PHP filesystem functions, this extension ensures that once a directory is opened, it is impossible to access files outside of that directory tree (e.g., via ../../), providing strong protection against directory traversal attacks.
- Linux (or a compatible Unix system)
- Rust (via
rustup, 2021 edition) - PHP (version 8.0+, with development headers installed, e.g.,
php-devorphp-devel) - Clang (required for binding generation by
ext-php-rs)
-
Clone the repository:
git clone <repository-url> cd php-cap-std
-
Build the project: Use Cargo to build the dynamic library.
cargo build --release
The generated artifact will be located at
target/release/libstephp_cap_std.so. -
Enable the extension: You can either add the extension to your
php.inifile or include it dynamically during CLI execution.Option A: Command line (for testing)
php -d extension=target/release/libstephp_cap_std.so your_script.php
Option B: Permanent configuration Copy the
.sofile to your PHP extensions directory and add the following line to yourphp.ini:extension=libstephp_cap_std.so
Here is a simple example showing how to open a directory securely and manipulate files within it.
<?php
// 1. Obtain the ambient authority (entry point for system resources)
$auth = stephp_cap_std_ambient_authority();
// 2. Open a root directory for the sandbox (e.g., /tmp/sandbox)
// If the path does not exist, an exception (String) will be thrown.
try {
$dir = stephp_cap_std_open_ambient_dir($auth, '/tmp/sandbox');
// From this point, $dir can ONLY act within /tmp/sandbox.
// Write a file
$dir->write_file("message.txt", "Hello Secure World!");
// Read a file
echo $dir->read_file("message.txt"); // Outputs: Hello Secure World!
// Create a subdirectory
$dir->create_dir("logs");
// Attempting to access the parent directory will fail (cap-std security)
// $dir->read_file("../passwd"); // This will trigger a Rust error/Permission denied
// List contents
$entries = $dir->read_dir(".");
var_dump($entries);
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}Note: The bindings are currently a work in progress and do not yet cover the entire
cap-stdAPI. Many more methods are available than those listed below.
The main exposed classes include:
StephpCapStdAmbientAuthority: Represents the authority to access global system resources.StephpCapStdDir: Represents an opened directory. Methods such as:open(string $path)open_dir(string $path)create_dir(string $path)read_file(string $path)write_file(string $path, string $content)remove_file(string $path)remove_dir(string $path)metadata(string $path)canonicalize(string $path)- ...
StephpCapStdFile: Represents an opened file handle. It provides low-level operations such as:read(int $length)write(string $data)sync_all()- ...
Other classes like StephpCapStdMetadata, StephpCapStdPermissions, and StephpCapStdEntries are also used internally or returned by the methods above.
This extension performs system operations via Rust, bypassing PHP's standard streams. However, PHP maintains an internal cache for file metadata operations (functions like file_exists, is_file, stat, etc.).
If you mix this extension with native PHP functions on the same files, you must be aware of this cache.
Potential conflict example:
$dir->write_file("test.txt", "data");
// If you use the native PHP function immediately after:
if (file_exists("/tmp/sandbox/test.txt")) {
// This might return FALSE if PHP cached the file's absence
// before the Rust write operation.
}Solution: If you encounter inconsistencies when using both systems together, call the native PHP function:
clearstatcache();It is recommended to use the methods provided by the $dir object (such as $dir->exists(), $dir->is_file()) whenever possible, as they query the filesystem directly via Rust, ensuring up-to-date data.
While Rust is extremely fast, crossing the FFI (Foreign Function Interface) boundary between PHP and Rust has a cost. For massive reads/writes of very small files, this might be less performant than a pure native solution, but it provides superior security guarantees.
This project is licensed under the MIT License.