Skip to main content
The runner is a Rust dynamic library that handles the final step of secret injection. When you run relic run, the CLI decrypts your secrets and passes them to the runner, which spawns your command in an isolated environment with those secrets as environment variables. The runner is written in Rust (compiled as a C-compatible dynamic library) because secret injection requires low-level control over process environments, memory, and signals that JavaScript runtimes do not provide.

How It Works

1

CLI prepares the data

The CLI decrypts secrets and serializes them as JSON along with the command to run.
2

FFI call

The CLI calls the runner’s run_with_secrets function via Bun’s dlopen FFI. Two null-terminated C strings are passed: the command and the secrets.
3

Environment setup

On Unix, the runner clears the child process environment entirely, then re-adds only essential system variables (PATH, HOME, USER, SHELL, TERM, LANG, LC_ALL, LC_CTYPE, TMPDIR, TZ). Secrets are injected on top.
4

Process spawn

The child process is spawned with the clean environment. The runner forwards SIGTERM and SIGINT so your process can shut down gracefully.
5

Cleanup

After the child exits, secret values are zeroed in memory (via Zeroizing). The TypeScript side also zeroes its buffers.

Security Measures

Platform Support

Windows support is limited. The child process inherits the full parent environment instead of starting clean. Full Windows support with an environment allowlist is planned.

Prebuilt Binaries

The runner is compiled as a C-compatible dynamic library (cdylib): Prebuilt binaries ship in apps/cli/prebuilds/<platform>/. In development, the CLI loads from packages/runner/target/release/.

Building from Source

The compiled library will be at target/release/librelic_runner.dylib (macOS) or target/release/librelic_runner.so (Linux).
Last modified on May 2, 2026