Project

envameleon

0.0
The project is in a healthy, maintained state
Scrub, mask, or drop Linux's /proc/self/environ view without changing Ruby's ENV.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 13.2
~> 1.56
~> 3.7
 Project Readme

ENVameleon — Leave no ENVidence.

ENVameleon

Leave no ENVidence.

Hide the first process environment shown by Linux without changing Ruby's ENV.

About · Get started · Usage · How it works · Development

Table of Contents

  • About
  • Getting Started
  • Native Gems
  • Usage
  • How It Works
  • Process Inheritance
  • Security Limits
  • Development
  • License

About

Linux exposes the environment passed at process start through /proc/self/environ. ENVameleon lets a Ruby process mask, scrub, or drop that view after boot. Ruby's live ENV stays intact.

Why this matters

Suppose your app keeps its credentials in an encrypted file. Your container runner passes the unlock key through the process environment. The encrypted file may seem safe even if an attacker can read any file that the app can access.

Guess again. Linux also exposes /proc/self/environ as a file. It may hold the unlock key passed at process launch. An attacker who reads both files can decrypt the credentials, recover a session signing key, and forge sessions.

ENVameleon removes this copy of the key from the proc view after app boot.

The gem has no runtime dependencies. Loading it does not change anything. Your app chooses when to call one of its three methods.

Getting Started

Requirements

  • CRuby 3.2 or newer
  • Linux for changes to /proc/self/environ
  • The Linux CAP_SYS_RESOURCE right only for ENV.drop_proc_data

On macOS and Windows, all three methods are safe no-ops.

Installation

Install the gem:

gem install envameleon

Or add it to your Gemfile:

gem "envameleon"

Then load it:

require "envameleon"

Back to top.

Native Gems

ENVameleon publishes precompiled native gems for its supported CRuby minors on:

  • GNU/Linux and musl Linux on x86-64 and AArch64

The macOS and Windows gems contain the documented pure-Ruby no-ops. There is no native code to compile on those systems. Linux is split into -linux-gnu and -linux-musl gems so a glibc binary is never mistaken for a musl binary. These native gems require RubyGems 3.3.22 or newer; musl users should use Bundler 2.5.6 or newer.

Each precompiled Linux gem contains a separate binary for every supported Ruby minor and has no extension build hook. When Linux runs a newer Ruby API, RubyGems or Bundler selects the generic source gem instead. That gem runs extconf.rb and requires a C compiler, Make, and the Ruby headers. macOS and Windows continue to select their compiler-free no-op gems. If neither a native nor source fallback applies on Linux, require "envameleon" fails closed instead of silently exposing the process environment.

Every GitHub release includes the gems and SHA256SUMS. Release gems also have GitHub build-provenance attestations, which can be checked with:

gh attestation verify envameleon-0.2.0-x86_64-linux-gnu.gem --repo kpumuk/envameleon

Back to top.

Usage

require "envameleon"

ENV.mask_proc_data
# SECRET=example becomes SECRET=e*****e in /proc/self/environ

ENV.scrub_proc_data
# /proc/self/environ now holds only NUL bytes

ENV.drop_proc_data
# /proc/self/environ is now zero bytes long
Method Result in /proc/self/environ File length Permissions
ENV.mask_proc_data Names plus the first and last value byte Unchanged None
ENV.scrub_proc_data NUL bytes Unchanged None
ENV.drop_proc_data Empty Zero CAP_SYS_RESOURCE

Choose the least power you need.

Important

ENV.drop_proc_data requires CAP_SYS_RESOURCE on Linux. Without it, the method raises Errno::EPERM.

Masking works on raw bytes, not characters. It replaces all value bytes except the first and last with *. Values shorter than three bytes stay unchanged.

Back to top.

How It Works

CRuby moves its active environment during startup. Linux still keeps the old range used by /proc/self/environ. ENVameleon reads that range from /proc/self/stat.

Masking and scrubbing first check that Ruby no longer uses the old range. They then change those bytes in place. If Ruby still uses any of them, the method raises an error and changes nothing.

Dropping uses PR_SET_MM_ENV_END to move the end of the kernel's view to its start. This makes /proc/self/environ truly zero-length. Linux requires the CAP_SYS_RESOURCE right for that call.

Process Inheritance

Call a method before fork and each child inherits the changed proc view. Ruby ENV remains available in the parent and children. The test suite checks this with a second fork for all three methods.

spawn, system, and exec are different. They give the new program a fresh proc environment. That program must load ENVameleon and call a method itself.

Forking servers

You may call a method in the parent before it forks. You may also call it from a worker boot hook. These examples use scrubbing, which needs no extra right.

Unicorn

require "envameleon"

after_fork do |_server, _worker|
  ENV.scrub_proc_data
end

Puma

Use before_worker_boot for cluster workers. In single mode, call the method during app boot.

require "envameleon"

before_worker_boot do
  ENV.scrub_proc_data
end

Sidekiq

Standard Sidekiq uses threads. Its startup hook runs before work begins. Sidekiq Enterprise Swarm runs the hook in each child.

require "envameleon"

Sidekiq.configure_server do |config|
  config.on(:startup) { ENV.scrub_proc_data }
end

Karafka

The app.running event runs in the server process. In Swarm mode, it runs in each forked node.

require "envameleon"

Karafka::App.monitor.subscribe("app.running") do
  ENV.scrub_proc_data
end

If a child calls ENV.drop_proc_data, it must still have the needed Linux right.

Back to top.

Security Limits

Warning

CAP_SYS_RESOURCE grants broad powers outside ENVameleon.

ENV.drop_proc_data needs this Linux capability. Grant it only if you accept its wider access. Masking and scrubbing do not need it.

ENVameleon changes only what /proc/self/environ shows. It does not erase every copy of a secret. It gives no protection from memory disclosure, debuggers, or core dumps.

Dropping leaves the old bytes in memory. Masking keeps variable names and the outer bytes of each value. Short values remain fully visible. Scrubbing overwrites the old proc range, but a secret may still exist elsewhere.

Back to top.

Development

Install the checksum-locked development bundle and run the test/unit suite. The test task compiles the extension on Linux and exercises the pure-Ruby no-ops elsewhere:

bundle install
bundle exec rake test
bundle exec rake standard

On Linux, the drop test runs when CAP_SYS_RESOURCE is available. Otherwise, that one test is omitted.

Build the macOS and Windows no-op gems and the Linux source fallback with:

bundle exec rake gem

Native builds run inside the versioned image selected by the checksum-locked rake-compiler-dock gem. The image installs the exact Bundler version recorded in Gemfile.lock; Bundler then validates its own locked checksum and installs every other dependency from the prepared local cache:

bundle cache --all-platforms
bundle exec rake gem:x86_64-linux-gnu
ruby .github/scripts/verify_native_gem.rb pkg/envameleon-0.2.0-x86_64-linux-gnu.gem x86_64-linux-gnu

License

Distributed under the MIT License. See LICENSE.txt.

Back to top.