Skip to content

sol-21/envdrift

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

17 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ›‘οΈ EnvDrift

Sync .env files without leaking secrets.

npm version VS Code License: MIT Tests

EnvDrift is a CLI tool that automatically syncs your .env file to .env.example while intelligently scrubbing sensitive values. It detects secrets from 30+ providers including AWS, Stripe, GitHub, OpenAI, and database connection strings.

πŸ†š VS Code Extension

EnvDrift also has a VS Code extension for real-time drift detection!

Install VS Code Extension

Features:

  • πŸ“Š Status Bar - See drift status at a glance
  • ⚑ Auto-Check - Automatically detects drift when files change
  • πŸ”„ One-Click Sync - Sync directly from VS Code
  • πŸ“‹ Diff View - Side-by-side comparison
  • ⚠️ Problems Panel - Integrates with VS Code diagnostics

Install: Search "EnvDrift" in VS Code Extensions or run:

code --install-extension sol-21.envdrift-vscode

✨ Features

  • πŸ” Smart Detection - Identifies sensitive values by key name AND value patterns
  • 🏒 Provider Detection - Recognizes secrets from AWS, Stripe, GitHub, PostgreSQL, MongoDB, OpenAI, and 30+ more
  • πŸ“Š JSON Output - Machine-readable output for CI/CD tooling
  • πŸ”‡ Quiet Mode - Suppress all output except errors
  • πŸ“ Multi-file Support - Scan .env, .env.local, .env.development, etc.
  • πŸ”„ Diff Command - Visual comparison between files
  • πŸ‘οΈ Watch Mode - Auto-sync on file changes
  • πŸŽ›οΈ Interactive Mode - Approve each change individually
  • 🎯 Strict Mode - Scrub ALL values for maximum security
  • πŸ‘€ Dry Run - Preview changes before modifying files
  • βš™οΈ Config File - Project-level .envdriftrc.json for team consistency
  • πŸ”’ Ignore List - Keep certain keys unmodified
  • πŸ”€ Merge Mode - Add new keys without overwriting existing entries
  • πŸ’¬ Comment Preservation - Keeps your documentation intact
  • πŸš€ CI/CD Ready - Proper exit codes and minimal output mode
  • πŸͺ Git Hooks - Pre-commit hook to prevent drift

πŸ“¦ Installation

# Use directly with npx (no install needed)
npx envdrift sync

# Or install globally
npm install -g envdrift

# Or add to your project
npm install --save-dev envdrift

πŸš€ Quick Start

# Check for drift between .env and .env.example
npx envdrift check

# Sync .env.example with smart scrubbing
npx envdrift sync

# Preview changes without modifying files
npx envdrift sync --dry-run

# Scrub ALL values (paranoid mode)
npx envdrift sync --strict

# Interactive sync - approve each change
npx envdrift sync --interactive

# Watch for changes and auto-sync
npx envdrift sync --watch

πŸ“– Commands

envdrift check

Detect drift between your .env and .env.example files.

envdrift check
envdrift check --input .env.local --output .env.local.example
envdrift check --ci        # CI mode with proper exit codes
envdrift check --json      # JSON output for tooling
envdrift check --quiet     # Minimal output
envdrift check --all       # Check all .env files

Options:

Option Description
-i, --input <file> Input file (default: .env)
-o, --output <file> Output file (default: .env.example)
--ci CI mode - minimal output, exit code 1 on drift
--json Output results as JSON
-q, --quiet Suppress all output except errors
-a, --all Check all .env files (.env, .env.local, etc.)

envdrift sync

Sync and scrub your .env.example file.

envdrift sync
envdrift sync --dry-run           # Preview changes
envdrift sync --strict            # Scrub all values
envdrift sync --interactive       # Approve each change
envdrift sync --watch             # Auto-sync on changes
envdrift sync --json              # JSON output
envdrift sync --merge --sort      # Merge and sort keys
envdrift sync --ignore NODE_ENV DEBUG

Options:

Option Description
-i, --input <file> Input file (default: .env)
-o, --output <file> Output file (default: .env.example)
-d, --dry-run Preview changes without modifying files
-s, --strict Scrub ALL values regardless of key name
--ci CI mode - minimal output
-m, --merge Add new keys without removing existing
--sort Sort keys alphabetically
--ignore <keys...> Keys to never scrub
--no-preserve-comments Don't preserve comments
--json Output results as JSON
-q, --quiet Suppress all output except errors
-I, --interactive Interactive mode - approve each change
-w, --watch Watch mode - auto-sync on file changes

envdrift diff

Show visual diff between .env and .env.example.

envdrift diff
envdrift diff --changes-only    # Only show differences
envdrift diff --json            # JSON output

Options:

Option Description
-i, --input <file> Input file (default: .env)
-o, --output <file> Output file (default: .env.example)
--json Output results as JSON
-q, --quiet Suppress all output except errors
-c, --changes-only Only show changes, hide unchanged keys

envdrift scan

Scan project for all .env files.

envdrift scan
envdrift scan --json

Options:

Option Description
--json Output results as JSON
-q, --quiet Suppress all output except errors

envdrift init

Initialize EnvDrift in your project with an interactive wizard.

envdrift init           # Interactive wizard
envdrift init --yes     # Skip wizard, use defaults
envdrift init --hook    # Also setup pre-commit hook
envdrift init --force   # Overwrite existing config

Options:

Option Description
-f, --force Overwrite existing config file
--hook Setup git pre-commit hook
-y, --yes Skip wizard, use default config

βš™οΈ Configuration

Create .envdriftrc.json in your project root (or use envdrift init wizard):

{
  "input": ".env",
  "output": ".env.example",
  "strict": false,
  "ignore": ["NODE_ENV", "DEBUG", "LOG_LEVEL"],
  "alwaysScrub": ["INTERNAL_API_KEY"],
  "sensitiveKeywords": ["custom_secret"],
  "customPatterns": [
    { "name": "MyService", "pattern": "^myservice_[a-z0-9]{32}$" }
  ],
  "preserveComments": true,
  "merge": false,
  "sort": false,
  "placeholderFormat": "YOUR_{KEY}_HERE"
}

Configuration Options

Option Type Default Description
input string .env Input file path
output string .env.example Output file path
strict boolean false Scrub all values
ignore string[] [] Keys to never scrub
alwaysScrub string[] [] Keys to always scrub
sensitiveKeywords string[] [] Custom sensitive keywords
customPatterns array [] Custom regex patterns for secret detection
preserveComments boolean true Preserve comments
merge boolean false Merge mode
sort boolean false Sort keys alphabetically
groupByPrefix boolean false Group keys by prefix
placeholderFormat string YOUR_{KEY}_HERE Placeholder template

πŸ” Provider Detection

EnvDrift automatically detects and scrubs secrets from these providers:

Provider Pattern
AWS Access Key ID (AKIA...), Secret Access Key
Stripe sk_live_*, sk_test_*, pk_*, rk_*, whsec_*
GitHub ghp_*, gho_*, ghu_*, ghs_*, ghr_*, github_pat_*
GitLab glpat-*, glptt-*
OpenAI sk-... (48 chars)
Anthropic sk-ant-*
Clerk sk_live_*, sk_test_*, pk_live_*, pk_test_*
Supabase JWT tokens starting with eyJ...
Twilio Account SID (AC...), Auth Token
SendGrid SG.*.*
Mailgun key-*
Mailchimp *-us* API keys
Slack xox[baprs]-*, webhook URLs
Discord Webhook URLs
Google API Keys (AIza*), OAuth Client IDs
NPM npm_* tokens
Heroku UUID-format API keys
Databases PostgreSQL, MySQL, MongoDB, Redis, SQLite connection strings
JWT eyJ*.*.* tokens
Private Keys PEM format (-----BEGIN PRIVATE KEY-----)

πŸš€ CI/CD Integration

GitHub Actions

name: EnvDrift Check

on: [push, pull_request]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npx envdrift check --ci

JSON Output for Tooling

# Get drift status as JSON
npx envdrift check --json

# Get sync preview as JSON
npx envdrift sync --dry-run --json

# Get diff as JSON
npx envdrift diff --json

Example JSON output:

{
  "synced": false,
  "missingInExample": ["NEW_API_KEY"],
  "missingInEnv": ["OLD_KEY"],
  "envKeyCount": 10,
  "exampleKeyCount": 9
}

Git Pre-commit Hook

# Setup automatically
npx envdrift init --hook

# Or add manually to .git/hooks/pre-commit
npx envdrift check --ci

🎯 Example Workflows

Daily Development

# Watch for changes and auto-sync
npx envdrift sync --watch

Before Committing

# Check for drift
npx envdrift check

# If drift detected, sync
npx envdrift sync

# Or preview first
npx envdrift sync --dry-run

Team Onboarding

# Initialize project with config + pre-commit hook
npx envdrift init --hook

Maximum Security

# Scrub ALL values, no exceptions
npx envdrift sync --strict

Interactive Review

# Approve each change individually
npx envdrift sync --interactive

Multi-file Projects (Next.js, Vite)

# Scan all .env files
npx envdrift scan

# Check all .env files at once
npx envdrift check --all

# Sync specific file
npx envdrift sync -i .env.local -o .env.local.example

πŸ“š Programmatic API

EnvDrift can be used as a library in your Node.js projects:

Installation

npm install envdrift

Basic Usage

import {
  parseEnvContent,
  detectDrift,
  generateSyncedExample,
  detectProviderSecret,
  isSensitiveKey,
} from 'envdrift';

// Parse .env file content
const envContent = `
API_KEY=sk_live_abc123
DATABASE_URL=postgres://user:pass@localhost/db
NODE_ENV=development
`;

const entries = parseEnvContent(envContent);
console.log(entries);
// [
//   { key: 'API_KEY', value: 'sk_live_abc123', line: 2 },
//   { key: 'DATABASE_URL', value: 'postgres://user:pass@localhost/db', line: 3 },
//   { key: 'NODE_ENV', value: 'development', line: 4 }
// ]

Drift Detection

import { parseEnvContent, extractKeys, detectDrift } from 'envdrift';
import fs from 'fs';

const envContent = fs.readFileSync('.env', 'utf-8');
const exampleContent = fs.readFileSync('.env.example', 'utf-8');

const envKeys = extractKeys(parseEnvContent(envContent));
const exampleKeys = extractKeys(parseEnvContent(exampleContent));

const drift = detectDrift(envKeys, exampleKeys);

console.log(drift);
// {
//   isSynced: false,
//   missingInExample: ['NEW_KEY'],
//   missingInLocal: ['REMOVED_KEY'],
//   envKeys: [...],
//   exampleKeys: [...]
// }

Generate Synced Example

import { parseEnvContent, generateSyncedExample } from 'envdrift';
import fs from 'fs';

const envContent = fs.readFileSync('.env', 'utf-8');
const envEntries = parseEnvContent(envContent);

const exampleContent = fs.readFileSync('.env.example', 'utf-8');
const exampleEntries = parseEnvContent(exampleContent);

const result = generateSyncedExample(envEntries, exampleEntries, {
  strictMode: false,
  ignore: ['NODE_ENV', 'DEBUG'],
  preserveComments: true,
});

console.log(result.content);  // Scrubbed .env.example content
console.log(result.added);    // Keys added
console.log(result.removed);  // Keys removed
console.log(result.entries);  // Detailed entry info with scrub reasons

// Write to file
fs.writeFileSync('.env.example', result.content);

Secret Detection

import { detectProviderSecret, isSensitiveKey } from 'envdrift';

// Detect secrets by value pattern
detectProviderSecret('sk_live_abc123xyz');
// Returns: 'Stripe Secret Key'

detectProviderSecret('ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
// Returns: 'GitHub Personal Access Token (Classic)'

detectProviderSecret('postgres://user:pass@localhost/db');
// Returns: 'PostgreSQL Connection String'

detectProviderSecret('hello-world');
// Returns: null (not a known secret pattern)

// Check if key name is sensitive
isSensitiveKey('DATABASE_PASSWORD');  // true
isSensitiveKey('API_KEY');            // true
isSensitiveKey('APP_NAME');           // false
isSensitiveKey('MY_CUSTOM_SECRET', ['custom_secret']);  // true (with custom keywords)

Custom Patterns

import { detectProviderSecret } from 'envdrift';

// Use custom patterns
const customPatterns = [
  { name: 'MyService', pattern: '^myservice_[a-z0-9]{32}$' },
  { name: 'InternalToken', pattern: '^internal_token_[A-Z0-9]+$' },
];

detectProviderSecret('myservice_abc123def456ghi789jkl012mno345', customPatterns);
// Returns: 'Custom: MyService'

API Reference

parseEnvContent(content: string, preserveComments?: boolean): EnvEntry[]

Parse .env file content into structured entries.

extractKeys(entries: EnvEntry[]): string[]

Extract just the key names from parsed entries.

detectDrift(envKeys: string[], exampleKeys: string[]): DriftResult

Compare two sets of keys and return drift information.

generateSyncedExample(envEntries, exampleEntries, options?): SyncResult

Generate scrubbed .env.example content.

detectProviderSecret(value: string, customPatterns?): string | null

Check if a value matches known secret patterns.

isSensitiveKey(key: string, customKeywords?): boolean

Check if a key name indicates sensitive data.

configToSyncOptions(config: EnvDriftConfig): SyncOptions

Convert config file format to sync options.

Types

interface EnvEntry {
  key: string;
  value: string;
  line: number;
  comment?: string;
  precedingComments?: string[];
}

interface DriftResult {
  isSynced: boolean;
  missingInExample: string[];
  missingInLocal: string[];
  envKeys: string[];
  exampleKeys: string[];
}

interface SyncOptions {
  strictMode?: boolean;
  ignore?: string[];
  alwaysScrub?: string[];
  customSensitiveKeywords?: string[];
  customPatterns?: { name: string; pattern: string }[];
  preserveComments?: boolean;
  merge?: boolean;
  sort?: boolean;
  placeholderFormat?: string;
}

πŸ“ License

MIT Β© sol-21

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors