Skip to content

Repository files navigation

EnvShield

Zero-dependency, JIT-decrypted environment variable encryption for Python, Node.js, and C.

EnvShield encrypts your .env secrets at rest using AES-256-GCM and decrypts them strictly Just-In-Time in memory. No plaintext keys on disk. No pip install cryptography. No npm install dotenv-vault. Just native OS primitives and raw libcrypto bindings.


Why This Exists

Every major supply-chain breach in the last year shares the same root cause: plaintext secrets sitting where they shouldn't be.

  • Grafana (May 2026) — A stolen GitHub token gave attackers full codebase access.
  • Trivy (March 2026) — Compromised GH Actions scraped /proc/*/environ to exfiltrate PATs, AWS keys, and crypto wallets from CI runners.

EnvShield was built to kill this attack vector dead:

  1. Secrets are never stored in plaintext.env.enc contains AES-256-GCM ciphertexts.
  2. Master keys live in the OS keyring — GNOME Keyring, macOS Keychain, or Windows Credential Locker. Not in files. Not in env vars.
  3. Active environment wiping — If a CI runner injects a key via ENV_SHIELD_KEY, EnvShield reads it once, then memsets the raw C environ block to null bytes. Any malware polling /proc finds nothing.

Architecture

┌──────────────────────────────────────────────────────────────────┐
│                        Developer Machine                         │
│                                                                  │
│   .env (plaintext)                                               │
│       │                                                          │
│       ▼                                                          │
│   envshield-cli.py .env  →  .env.enc (AES-256-GCM ciphertext)   │
│   setup wizard           →  ~/.envshield/config  {"mode":"tpm"}  │
│                          →  Master key → TPM / Keyring / file    │
│                                                                  │
│   ┌─── Your Application ────────────────────────────────────┐    │
│   │  import envshield                                       │    │
│   │  api_key = os.environ['SECRET_API_KEY']                 │    │
│   │           │                                             │    │
│   │           ▼                                             │    │
│   │  1. Read ~/.envshield/config → determine PK source      │    │
│   │  2. Fetch PK from configured source (tpm/keyring/env)  │    │
│   │  3. Decrypt ciphertext JIT via native libcrypto GCM     │    │
│   │  4. Wipe ENV_SHIELD_KEY from /proc (if env mode)        │    │
│   │  5. memset(0) the PK bytes from process memory          │    │
│   │  6. Return plaintext (key material destroyed)           │    │
│   └─────────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────────┘

Getting Started — Interactive Setup Wizard

The fastest way to get running is the interactive wizard. It detects your system, lets you pick a key storage mode, generates keys, and sets up integrations:

Linux / macOS

bash scripts/envshield-setup.sh

Windows (PowerShell)

powershell -ExecutionPolicy Bypass -File scripts\envshield-setup.ps1

The wizard will walk you through:

  1. System detection — Checks for Python 3, libcrypto, TPM 2.0, and OS keyring availability.
  2. Mode selection — TPM (hardware), OS Keyring (software), CI/CD env var, or file-based.
  3. Key generation — Generate a new 256-bit key or import an existing one.
  4. Encryption — Encrypt a .env file or store individual service tokens.
  5. Git integration — Optionally configure the Git credential helper.

Tip: For CI/CD pipelines, select the "CI/CD Environment Variable" mode. The wizard will display the key for you to paste into your provider's secrets UI.


Quick Start (Manual)

1. Encrypt a .env file

python3 cli/envshield-cli.py .env

This generates:

  • .env.enc — JSON containing IV + auth tag + ciphertext per variable.
  • Master key → stored in your OS keyring automatically.

Headless / CI mode: Pass --file to write the key to env-shield.key instead.

python3 cli/envshield-cli.py --file .env

2. Decrypt in Python

Drop the python/envshield/ directory into your project (or pip install . from python/):

import envshield   # patches os.environ on import
import os

# Transparently decrypted JIT — the value never touches os.environ
api_key = os.environ['SECRET_API_KEY']

The master key source is determined by ~/.envshield/config (written by the setup wizard):

Mode Config Value Source Use Case
TPM 2.0 {"mode": "tpm"} Hardware-sealed blob via tpm2_unseal Production workstations, locked-down servers
OS Keyring {"mode": "keyring"} GNOME Keyring / macOS Keychain / Windows Cred Locker Desktops with session-encrypted keystores
Env Var {"mode": "env"} ENV_SHIELD_KEY (auto-wiped from /proc after read) CI/CD runners (GitHub Actions, GitLab CI, Jenkins)
File {"mode": "file"} Local env-shield.key file Development / testing only

Set ENVSHIELD_DEBUG=1 for detailed step-by-step logging to stderr.

3. Decrypt in Node.js

require('./node/envshield');

// process.env is intercepted via Proxy
const apiKey = process.env.SECRET_API_KEY;

4. Decrypt in C

#include "envshield.h"

char *key = envshield_get("SECRET_API_KEY");
// Use key...
free(key);  // caller owns the allocation

Protecting Integration Tokens (GitHub, AWS, NPM, etc.)

The --store command encrypts a single token and saves it globally under ~/.envshield/:

# GitHub PAT
python3 cli/envshield-cli.py --store github "ghp_xxxxxxxxxxxx"

# AWS Access Key
python3 cli/envshield-cli.py --store aws "AKIAIOSFODNN7EXAMPLE"

# NPM Token
python3 cli/envshield-cli.py --store npm "npm_xxxxxxxxxxxx"

Master keys are stored in the most secure available backend (TPM 2.0 > OS keyring) under envshield-<service>.

Git Credential Helper

EnvShield ships a native Git credential helper that decrypts your PAT JIT for every git push / git pull:

# 1. Store the token:
python3 cli/envshield-cli.py --store github "ghp_your_token"

# 2. Register the helper:
git config --global credential.helper \
    "/usr/bin/env python3 $(realpath cli/git-credential-envshield.py)"

The helper implements the standard Git credential protocol. It maps hosts to service names automatically:

Host Service
github.com github
gitlab.com gitlab
bitbucket.org bitbucket

Active Environment Wiping

When ENV_SHIELD_KEY is passed as an environment variable (typical in CI/CD), EnvShield performs a C-level memory scrub immediately after reading it:

# What happens internally:
libc = ctypes.CDLL(None)
environ_ptr = ctypes.POINTER(ctypes.c_char_p).in_dll(libc, "environ")
# Walk the array, find the entry, memset it to 0x00
libc.memset(environ_ptr[i], 0, len(raw_entry))

Result: getenv("ENV_SHIELD_KEY") returns NULL. Child processes don't inherit it. Background malware scraping the environ array finds nothing.

Note: /proc/self/environ is a kernel-level snapshot created at execve() time and is immutable. The wipe targets the live C environ array, which is what getenv(), child process inheritance, and most scraping tools actually read.

You can also call the wipe function directly:

from envshield import wipe_environ_key

wipe_environ_key('AWS_SECRET_ACCESS_KEY')
wipe_environ_key('GITHUB_TOKEN')

Master Key Storage

The master key source is explicitly configured — EnvShield does not guess. The setup wizard writes ~/.envshield/config with the selected mode.

Configuration File

// ~/.envshield/config
{"mode": "tpm"}

Valid modes: tpm, keyring, env, file.

Runtime Lifecycle

Every time your application reads an encrypted variable, EnvShield executes this exact sequence:

  1. Load .env.enc ciphertext into memory.
  2. Read ~/.envshield/config to determine the PK source.
  3. Fetch the master key from the configured source only.
  4. Decrypt the ciphertext JIT via native libcrypto AES-256-GCM.
  5. Wipe ENV_SHIELD_KEY from /proc/<pid>/environ (if env mode — memset to null bytes).
  6. Destroy the master key bytes from process memory (memset(0) on the mutable buffer).
  7. Return the plaintext. Key material no longer exists in the process.

Mode Details

TPM 2.0 (Hardware-Backed — {"mode": "tpm"})

Seals the master key to the TPM's Storage Root Key (SRK). Only unsealable on the same physical machine.

sudo apt install tpm2-tools   # Ubuntu/Debian
Property Detail
Seal target Owner hierarchy SRK (tpm2_createprimary -C o)
Operations tpm2_createtpm2_loadtpm2_unseal
Portability None (by design — key is bound to the silicon)
Disk artifact ~/.envshield/tpm/<service>/key.pub + key.priv (useless without the TPM)

Recovery: If the TPM is cleared or the hardware changes, the sealed key is irrecoverable. Use --file to create an offline backup before provisioning.

OS Software Keyring ({"mode": "keyring"})

Platform Backend Command
Linux GNOME Keyring / KDE Wallet via secret-tool secret-tool lookup service envshield
macOS Keychain via security security find-generic-password -s envshield -w
Windows Credential Locker via advapi32.dll CredReadW / CredWriteW

Environment Variable ({"mode": "env"})

For headless CI/CD. EnvShield reads ENV_SHIELD_KEY once, then memsets the raw C environ block to null bytes.

env:
  ENV_SHIELD_KEY: ${{ secrets.ENV_SHIELD_KEY }}

File Fallback ({"mode": "file"})

For local development only. Writes env-shield.key to the working directory.

python3 cli/envshield-cli.py --file .env

Do not commit this file. Add it to .gitignore.


Distributing the Library

The Python package has zero external dependencies. Install it locally:

cd python && pip install .

Then use it in any script:

from envshield.core import encrypt, decrypt
from envshield import keyring

# Encrypt a value
key = os.urandom(32)
ciphertext = encrypt("my_secret", key)

# Store the key in the OS keyring
keyring.store_key("my-service", key.hex())

# Later: retrieve and decrypt
key_hex = keyring.get_key("my-service")
plaintext = decrypt(ciphertext, bytes.fromhex(key_hex))

Security Model

Property Implementation
Algorithm AES-256-GCM (authenticated encryption with associated data)
Crypto Backend OS-native libcrypto.so via Python ctypes / Node crypto / C libssl
Key Storage TPM 2.0 (hardware) > OS keyring (software) — never plaintext on disk in production
Memory Safety Decrypted values are transient — never written to os.environ or process.env
Environment Wiping C-level memset(0) on the raw environ block after key extraction
Dependencies Zero. No pip. No npm. No cgo. Pure native bindings.

Building the C Library

Prerequisites

# Ubuntu / Debian
sudo apt install build-essential libssl-dev

# RHEL / Fedora
sudo yum install gcc openssl-devel

# macOS (Homebrew)
brew install openssl

Compilation

# Static library (libenvshield.a)
make

# Shared library (libenvshield.so)
make shared

# Build and run tests
make test

# Install to /usr/local
sudo make install

Linking Against Your Application

# Static linking
gcc -o myapp myapp.c -L. -lenvshield -lssl -lcrypto

# Shared linking
gcc -o myapp myapp.c -L. -lenvshield -lssl -lcrypto
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

Usage in C

#include "envshield.h"

int main() {
    char *key = envshield_getenv("SECRET_API_KEY");
    if (key) {
        printf("Key: %s\n", key);
        envshield_free(key);  // securely wipes + frees
    }
    return 0;
}

Important: Always call envshield_free() on the returned pointer. It uses volatile memory zeroing before free() to ensure the plaintext doesn't linger in freed heap blocks.

macOS Notes

Homebrew installs OpenSSL to a non-standard path. Uncomment the OPENSSL_DIR lines in the Makefile or pass it explicitly:

make OPENSSL_DIR=/usr/local/opt/openssl@3

File Structure

env-shield-project/
├── Makefile                         # C library build system
├── cli/
│   ├── envshield-cli.py            # Encryption CLI (AES-256-GCM via libcrypto ctypes)
│   └── git-credential-envshield.py # Git credential helper (standard protocol)
├── python/
│   ├── setup.py                    # pip-installable package
│   └── envshield/
│       ├── __init__.py             # os.environ interceptor + env wiping
│       ├── core.py                 # AES-256-GCM encrypt/decrypt via ctypes
│       └── keyring.py             # Cross-platform keyring (TPM + OS + Windows)
├── node/
│   └── envshield.js                # process.env Proxy interceptor
├── c/
│   ├── envshield.h                 # Public API header
│   └── envshield.c                 # Native C interceptor (links libssl)
├── scripts/
│   ├── envshield-setup.sh          # Interactive setup wizard (Linux/macOS)
│   └── envshield-setup.ps1         # Interactive setup wizard (Windows)
├── test.sh                         # E2E integration tests
└── README.md

Testing

# Full E2E (Python + Node.js + C)
./test.sh

# C library only
make test

License

MIT

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages