@yaos-git/env-lock
v126.1.0
Published
Security-first encrypted environment injection with multi-lock team access
Maintainers
Readme
Table of Contents
Getting Started
Architecture
TUI
Development
Overview
env-lock is a CLI tool that lets you commit encrypted secrets to version control. It uses a "Multi-Lock" system — a shared Master Key encrypted with individual user credentials — so any team member with a valid password can decrypt the same secrets at runtime.
Secrets are decrypted directly into process memory. Plain-text never touches disk.
What Makes This Project Unique
- Commit Secrets to Git: Encrypted
.env.encfiles are safe to version control - Multi-Lock Access: Multiple passwords unlock the same secrets without re-encryption
- Memory-Only Runtime: Decrypted values injected into child process env, never written to disk
- Schema Validation: Zod validates that all required env vars are present after decryption
Installation
# Install globally from npm
npm install -g @yaos-git/env-lock
# Or install as a dev dependency
npm install -D @yaos-git/env-lockFrom Source
# Clone the repository
git clone https://github.com/YAOSGit/env-lock.git
cd env-lock
# Install dependencies
npm install
# Build the project
npm run build
# Link globally (optional)
npm linkQuick Start
# 1. Initialize — creates Master Key and your first user slot
env-lock init alice
# 2. Seal your .env file into encrypted .env.enc
env-lock seal .env
# 3. Run your app with secrets decrypted into memory
env-lock run -- npm run devNote: After sealing, you can delete the plain-text
.envand commit.env.enc+env-lock.jsonto git.
CLI Usage
env-lock init <slot-id> Create lockbox and first user slot
env-lock seal <file> Encrypt a plain-text .env → .env.enc
env-lock run -- <command> Decrypt secrets into memory and spawn command
env-lock --help, -h Show help message
env-lock --version, -v Show version information| Command | ENV_LOCK_PASSWORD | Behavior |
|---------|:-------------------:|----------|
| init | Optional | Prompts for password if not set |
| seal | Optional | Prompts for password if not set |
| run | Required | No interactive prompt — exits if missing |
Examples
# Initialize the lockbox with a slot id
env-lock init alice
# Encrypt your environment file
env-lock seal .env
# Run a dev server with decrypted secrets
env-lock run -- npm run dev
# Run Docker Compose with secrets
env-lock run -- docker compose up
# Launch the TUI for secret editing and slot management
env-lock-tuiThe Lockbox Pattern
env-lock uses a slot-based encryption architecture to support multiple users:
- A randomly generated Master Key (MK) encrypts the actual secrets (AES-256-GCM)
- Each team member has a slot containing the MK wrapped with their personal key
- Personal keys are derived from passwords via Argon2id or PBKDF2
- Any valid slot can "unwrap" the MK, which then decrypts the environment variables
Benefits
- Adding a new member creates a new slot — the secrets themselves are never re-encrypted
- Removing a member just deletes their slot
- Rotating the Master Key is independent of user credentials
File Structure
| File | Committed to Git | Description |
|------|:----------------:|-------------|
| .env.enc | Yes | AES-256-GCM encrypted secrets payload |
| env-lock.json | Yes | Lockbox metadata (salts, iterations, wrapped keys) |
| .env.keys | No | Optional local MK cache for automated dev environments |
Encryption Details
| Layer | Algorithm | Purpose | |-------|-----------|---------| | Key Derivation | Argon2id / PBKDF2 | Turn passwords into cryptographic keys | | Secret Encryption | AES-256-GCM | Authenticated encryption of env vars | | Key Wrapping | AES-256-GCM | Wrap Master Key per user slot |
All cryptographic operations use node:crypto built-ins. No third-party crypto libraries.
Secret Editor
Launch with env-lock-tui. Opens a two-panel terminal UI built with Ink.
- Header displays active panel with tab indicators:
● Secrets/○ Slots - Auto-unlocks with
ENV_LOCK_PASSWORDif available; otherwise prompts interactively - All available keyboard shortcuts are shown in the footer
Secrets Panel
View, edit, add, and delete environment variables. The selected item is marked with a ▸ indicator. Saving re-encrypts the payload in place — plain-text never touches disk.
Slots Panel
View existing user slots, add new members, and remove members. Adding a member wraps the Master Key with a new key derived from the member's password. Delete operations require confirmation via a dialog.
Keybindings
| Key | Action |
|-----|--------|
| Tab | Switch between Secrets and Slots panels |
| Up / Down | Navigate list |
| e | Edit selected item |
| a | Add new item |
| d | Delete selected item (with confirmation) |
| s | Save changes |
| q | Quit |
Slot Management
Slot management is integrated into the TUI (Slots panel). From the TUI you can:
- View all existing user slots
- Add a new member — prompts for a password, derives a key, wraps the Master Key, and saves the slot to
env-lock.json - Remove a member — requires confirmation, then deletes the slot from
env-lock.json
Available Scripts
Development Scripts
| Script | Description |
|--------|-------------|
| npm run dev | Run TypeScript checking + test watcher concurrently |
| npm run dev:typescript | Run TypeScript type checking in watch mode |
| npm run dev:test | Run Vitest in watch mode |
Build Scripts
| Script | Description |
|--------|-------------|
| npm run build | Bundle the CLI with esbuild |
Lint Scripts
| Script | Description |
|--------|-------------|
| npm run lint | Run type checking, linting, formatting, and audit |
| npm run lint:check | Check code for linting issues with Biome |
| npm run lint:fix | Check and fix linting issues with Biome |
| npm run lint:format | Format all files with Biome |
| npm run lint:types | Run TypeScript type checking only |
| npm run lint:audit | Run npm audit |
Testing Scripts
| Script | Description |
|--------|-------------|
| npm test | Run all tests (unit, react, types, e2e) |
| npm run test:unit | Run unit tests |
| npm run test:react | Run React component tests |
| npm run test:types | Run type-level tests |
| npm run test:e2e | Run end-to-end tests |
Tech Stack
Core
- TypeScript 5 — Type-safe JavaScript
- node:crypto — AES-256-GCM encryption
- Zod — Runtime schema validation
- React 19 — UI component library
- Ink 6 — React for CLIs
Build & Development
UI Components
Project Structure
env-lock/
├── src/
│ ├── app/ # Application entry points
│ │ ├── cli.ts # CLI entry point (Commander)
│ │ ├── editor-cli.tsx # TUI entry point (env-lock-tui)
│ │ ├── app.tsx # Main TUI application (tab shell)
│ │ ├── index.tsx # React app root
│ │ └── providers.tsx # Provider wrapper
│ ├── components/ # React (Ink) components
│ │ ├── PasswordPrompt/ # Password input with masked characters
│ │ ├── SecretList/ # Env var list with edit/add/delete
│ │ └── SlotList/ # User slot list with add/remove
│ ├── crypto/ # Cryptographic modules
│ │ ├── aes/ # AES-256-GCM encrypt/decrypt
│ │ ├── kdf/ # Key derivation (Argon2id/PBKDF2)
│ │ ├── masterKey/ # Master Key generation
│ │ └── slot/ # User slot wrap/unwrap
│ ├── hooks/ # React hooks
│ │ ├── useLockbox/ # Lockbox unlock & slot operations
│ │ └── useSecrets/ # Secret CRUD & re-encryption
│ ├── providers/ # React context providers
│ │ ├── LockboxProvider/ # Lockbox state & Master Key context
│ │ └── SecretProvider/ # Decrypted secrets context
│ ├── types/ # TypeScript type definitions
│ │ ├── Envelope/ # Encrypted envelope type
│ │ ├── EnvSchema/ # Environment schema type
│ │ ├── Lockbox/ # Lockbox metadata type
│ │ └── Slot/ # User slot type
│ └── utils/ # Utility functions
│ ├── envParser/ # .env file parser
│ ├── envelope/ # Envelope serialization
│ ├── executor/ # Child process spawner
│ ├── lockbox/ # Lockbox file operations
│ └── prompt/ # Interactive password prompt (CLI)
├── e2e/ # End-to-end tests
├── biome.json # Biome configuration
├── tsconfig.json # TypeScript configuration
├── vitest.unit.config.ts # Unit test configuration
├── vitest.react.config.ts # React test configuration
├── vitest.type.config.ts # Type test configuration
├── vitest.e2e.config.ts # E2E test configuration
├── esbuild.config.js # esbuild bundler configuration
└── package.jsonVersioning
This project uses a custom versioning scheme: MAJORYY.MINOR.PATCH
| Part | Description | Example |
|------|-------------|---------|
| MAJOR | Major version number | 1 |
| YY | Year (last 2 digits) | 26 for 2026 |
| MINOR | Minor version | 0 |
| PATCH | Patch version | 0 |
Example: 126.0.0 = Major version 1, released in 2026, minor 0, patch 0
Style Guide
Conventions for contributing to this project. All rules are enforced by code review; Biome handles formatting and lint.
Exports
- Named exports only — no
export default. Every module usesexport function,export const, orexport type. import type— always useimport typefor type-only imports..jsextensions — all relative imports use explicit.jsextensions (ESM requirement).
File Structure
src/
├── app/ # Entry points and root component
├── components/ # React components (PascalCase directories)
│ └── MyComponent/
│ ├── index.tsx
│ ├── MyComponent.types.ts
│ └── MyComponent.test.tsx
├── crypto/ # Cryptographic primitives (lowercase directories)
├── hooks/ # Hook re-exports (public API for context hooks)
├── providers/ # React context providers (PascalCase directories)
│ └── MyProvider/
│ ├── index.tsx
│ ├── MyProvider.types.ts
│ └── MyProvider.test.tsx
├── types/ # Shared type definitions (PascalCase directories)
│ └── MyType/
│ ├── index.ts
│ └── MyType.test-d.ts
└── utils/ # Pure utility functions (camelCase directories)
└── myUtil/
├── index.ts
└── myUtil.test.tsComponents & Providers
- Components use
functiondeclarations:export function MyComponent(props: MyComponentProps) {} - Providers use
React.FCarrow syntax:export const MyProvider: React.FC<Props> = ({ children }) => {} - Props are defined in a co-located
.types.tsfile using thetypekeyword. - Components receive data via props — never read
process.stdoutor global state directly.
Types
- Use
typefor all type definitions — neverinterface. - Shared types live in
src/types/TypeName/index.tswith a co-locatedTypeName.test-d.ts. - Local types live in co-located
.types.tsfiles — never inline in implementation files. - No duplicate type definitions — import from the canonical source.
Constants
- Named constants go in
.consts.tsfiles. - No magic numbers in implementation files — extract to named constants.
Testing
- Every module has a co-located test file.
- Components:
ComponentName.test.tsx - Hooks:
hookName.test.tsx - Utils:
utilName.test.ts - Types:
TypeName.test-d.ts(type-level tests usingexpectTypeOf/assertType)
License
ISC
