From fada8572b809987969cf2922b28afb00dde70e33 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Fri, 29 May 2026 01:18:05 -0400 Subject: [PATCH 1/5] refactor(diff): replace tempfile hack in load_env_content with io.StringIO; fix regex in rotate_env_var to correctly match quoted values --- src/envault/diff.py | 12 +++--------- src/envault/rotate.py | 7 +++++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/envault/diff.py b/src/envault/diff.py index d8c6e97..75de596 100644 --- a/src/envault/diff.py +++ b/src/envault/diff.py @@ -2,7 +2,7 @@ from __future__ import annotations -import os +import io from dotenv import dotenv_values from pathlib import Path @@ -17,14 +17,8 @@ def load_env_file(path: str | Path) -> dict[str, str]: def load_env_content(content: str) -> dict[str, str]: """Load environment variables from a string content.""" - import tempfile - with tempfile.NamedTemporaryFile(mode="w", suffix=".env", delete=False) as f: - f.write(content) - tmp = f.name - try: - return {k: v for k, v in dotenv_values(tmp).items() if k is not None and v is not None} - finally: - os.unlink(tmp) + stream = io.StringIO(content) + return {k: v for k, v in dotenv_values(stream=stream).items() if k is not None and v is not None} class EnvDiffResult: diff --git a/src/envault/rotate.py b/src/envault/rotate.py index b9827f8..da29650 100644 --- a/src/envault/rotate.py +++ b/src/envault/rotate.py @@ -150,8 +150,11 @@ def rotate_env_var( content = f.read() import re - # Match KEY=value or KEY="value" or KEY='value' - pattern = re.compile(rf"^{re.escape(key)}\s*=\s*['\"]?.*?['\"]?\s*$", re.MULTILINE) + # Match KEY=value or KEY="..." or KEY='...' — anchored to full value + pattern = re.compile( + rf"^{re.escape(key)}\s*=\s*(?:\"[^\"]*\"|'[^']*'|[^\n]*)$", + re.MULTILINE, + ) # Escape new value for the .env file if any(c in new_value for c in " #'\"\n\t"): From 2bc3982a7951c53378230c0f09e793b511308e7f Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Mon, 15 Jun 2026 04:43:42 -0400 Subject: [PATCH 2/5] feat: make revenueholdings-license optional, add encrypt/decrypt commands --- pyproject.toml | 1 + src/envault/cli.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a718076..25adc65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ dev = [ "responses>=0.24.0", "ruff>=0.4.0", ] +license = ["revenueholdings-license>=0.1.0"] all = [ "hvac>=2.0.0", "boto3>=1.28.0", diff --git a/src/envault/cli.py b/src/envault/cli.py index 5baf8be..8cecee6 100644 --- a/src/envault/cli.py +++ b/src/envault/cli.py @@ -15,10 +15,14 @@ from envault.serve import run_server from envault.stores import get_store from envault.sync import sync_env_files -from pathlib import Path -from rich.console import Console -from rich.prompt import Confirm -from rich.table import Table + +try: + from revenueholdings_license import require_license +except ImportError: + import warnings + warnings.warn("revenueholdings-license not installed; license checks skipped", stacklevel=2) + def require_license(product: str) -> None: # type: ignore[misc] + pass app = typer.Typer( name="envault", @@ -29,6 +33,11 @@ err_console = Console(stderr=True) +@app.callback() +def main_callback(): + require_license("envault") + + def load_config(config_path: str = "") -> EnvaultConfig: """Load config, optionally from a specific path.""" path = config_path if config_path else ".envault.yml" From 3a2e59cc5544f16ac23472eff92400cf58be8d51 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Sun, 21 Jun 2026 00:55:37 -0400 Subject: [PATCH 3/5] fix(cli): add missing imports (Console/Confirm/Table/Path/json) and fix Windows UTF-8 encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cli.py: Console, Confirm, Table, Path were used but never imported — caused NameError at runtime on every CLI invocation - diff.py: json module used in to_json() but never imported — caused NameError when --json flag was used on envault diff - Add sys.stdout/stderr.reconfigure(encoding='utf-8') on Windows to prevent cp1252 encoding crashes with Rich library Unicode symbols --- src/envault/cli.py | 14 ++++++++++++++ src/envault/diff.py | 1 + 2 files changed, 15 insertions(+) diff --git a/src/envault/cli.py b/src/envault/cli.py index 8cecee6..38751e7 100644 --- a/src/envault/cli.py +++ b/src/envault/cli.py @@ -2,7 +2,21 @@ from __future__ import annotations +import sys +from pathlib import Path + +# Ensure UTF-8 output on Windows consoles that default to cp1252 +if sys.platform == "win32": + try: + sys.stdout.reconfigure(encoding="utf-8") + sys.stderr.reconfigure(encoding="utf-8") + except Exception: + pass + import typer +from rich.console import Console +from rich.prompt import Confirm +from rich.table import Table from envault import __version__ from envault.audit import AuditLogger from envault.backup import backup_env_file, format_backup_list, list_backups, restore_backup diff --git a/src/envault/diff.py b/src/envault/diff.py index d6de0d1..85479f1 100644 --- a/src/envault/diff.py +++ b/src/envault/diff.py @@ -3,6 +3,7 @@ from __future__ import annotations import io +import json from dotenv import dotenv_values from pathlib import Path From dfec89521b966c1da4cdc702f166ef994f71ce65 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Wed, 24 Jun 2026 04:42:30 -0400 Subject: [PATCH 4/5] improve: correct CLI name from rh-envault to envault and redact bearer tokens in docs --- README.md | 94 +++++++++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index cb8d340..dc8ed0f 100644 --- a/README.md +++ b/README.md @@ -15,35 +15,35 @@ ## Quick Start -> **Note:** rh-envault is not yet published to PyPI. Install directly from GitHub. +> **Note:** envault is not yet published to PyPI. Install directly from GitHub. ```bash pip install git+https://github.com/Coding-Dev-Tools/envault.git # Initialize a project -rh-envault init my-project +envault init my-project # Diff environments -rh-envault diff dev prod +envault diff dev prod # Sync staging → prod -rh-envault sync staging prod +envault sync staging prod # Rotate a secret -rh-envault rotate DB_PASSWORD +envault rotate DB_PASSWORD ``` ## Commands -### `rh-envault init ` +### `envault init ` Initialize a `.envault.yml` config file with sensible defaults. ```bash -rh-envault init my-project +envault init my-project ``` -### `rh-envault diff ` +### `envault diff ` Diff environment variables between two environments or `.env` files. Shows keys that are: - Only in source @@ -51,41 +51,41 @@ Diff environment variables between two environments or `.env` files. Shows keys - Present in both but with different values ```bash -rh-envault diff dev staging -rh-envault diff prod staging -rh-envault diff-files .env.dev .env.prod +envault diff dev staging +envault diff prod staging +envault diff-files .env.dev .env.prod ``` -### `rh-envault sync ` +### `envault sync ` Sync environment variables from one environment to another with conflict resolution strategies. ```bash # Sync staging → prod (source values win conflicts) -rh-envault sync staging prod +envault sync staging prod # Dry run first -rh-envault sync staging prod --dry-run +envault sync staging prod --dry-run # Keep target values on conflict -rh-envault sync staging prod --strategy target_wins +envault sync staging prod --strategy target_wins # Delete keys in target that don't exist in source -rh-envault sync staging prod --allow-delete +envault sync staging prod --allow-delete # Skip certain keys -rh-envault sync staging prod --skip DB_HOST --skip DB_PORT +envault sync staging prod --skip DB_HOST --skip DB_PORT ``` -### `rh-envault rotate ` +### `envault rotate ` Rotate a single environment variable with an auto-generated cryptographically secure value. ```bash -rh-envault rotate DB_PASSWORD -rh-envault rotate API_KEY --env prod -rh-envault rotate JWT_SECRET --length 64 --dry-run --show -rh-envault rotate-all --env prod +envault rotate DB_PASSWORD +envault rotate API_KEY --env prod +envault rotate JWT_SECRET --length 64 --dry-run --show +envault rotate-all --env prod ``` Smart rotation infers the type of secret: @@ -95,41 +95,41 @@ Smart rotation infers the type of secret: - `WEBHOOK_SECRET` → long hex key - Everything else → 32-char random string -### `rh-envault store` +### `envault store` Manage secret store integrations — read, write, and list secrets from external stores. ```bash -rh-envault store list -rh-envault store list --prefix /production/ -rh-envault store get DB_PASSWORD --store my-vault -rh-envault store set DB_PASSWORD new_value --store my-vault -rh-envault store delete DB_PASSWORD --store my-vault +envault store list +envault store list --prefix /production/ +envault store get DB_PASSWORD --store my-vault +envault store set DB_PASSWORD new_value --store my-vault +envault store delete DB_PASSWORD --store my-vault ``` -### `rh-envault audit` +### `envault audit` View the audit log of all diff, sync, and rotate operations. ```bash -rh-envault audit -rh-envault audit --key DB_PASSWORD -rh-envault audit --action rotate --limit 100 +envault audit +envault audit --key DB_PASSWORD +envault audit --action rotate --limit 100 ``` -### `rh-envault serve` +### `envault serve` Start an HTTP server that exposes decrypted secrets as a JSON API — ideal for MCP server sidecars, CI/CD pipelines, and AI agent runtimes. ```bash # Start the secrets API on port 8080 (default) -rh-envault serve +envault serve # Custom port, host, and API key -rh-envault serve --port 3000 --host 0.0.0.0 --api-key my-bearer-token +envault serve --port 3000 --host 0.0.0.0 --api-key my-bearer-token # Use a named store from config -rh-envault serve --store production-secrets +envault serve --store production-secrets ``` **Endpoints:** @@ -147,13 +147,13 @@ rh-envault serve --store production-secrets ```bash # Fetch secrets with curl -curl -H "Authorization: Bearer my-token" http://localhost:8080/secrets +curl -H "Authorization: Bearer ***" http://localhost:8080/secrets # Filter by prefix -curl -H "Authorization: Bearer my-token" "http://localhost:8080/secrets?prefix=STRIPE" +curl -H "Authorization: Bearer ***" "http://localhost:8080/secrets?prefix=STRIPE" # Get a specific secret -curl -H "Authorization: Bearer my-token" http://localhost:8080/secrets/DB_PASSWORD +curl -H "Authorization: Bearer ***" http://localhost:8080/secrets/DB_PASSWORD ``` ## Features @@ -238,23 +238,23 @@ audit_log_path: .envault-audit.log | Store | Package | Install (from GitHub) | |-------|---------|----------------------| -| AWS SSM | `boto3` | `pip install "rh-envault[awsssm] @ git+https://..."` | -| HashiCorp Vault | `hvac` | `pip install "rh-envault[vault] @ git+https://..."` | -| Doppler | `requests` | `pip install "rh-envault[doppler] @ git+https://..."` | -| 1Password | `onepasswordconnectsdk` | `pip install "rh-envault[onepassword] @ git+https://..."` | +| AWS SSM | `boto3` | `pip install "envault[awsssm] @ git+https://..."` | +| HashiCorp Vault | `hvac` | `pip install "envault[vault] @ git+https://..."` | +| Doppler | `requests` | `pip install "envault[doppler] @ git+https://..."` | +| 1Password | `onepasswordconnectsdk` | `pip install "envault[onepassword] @ git+https://..."` | ## CI/CD Integration ```bash # Block deployment if production has secrets that staging doesn't -rh-envault diff staging prod --fail-on-missing +envault diff staging prod --fail-on-missing # Rotate a secret and sync to all environments -rh-envault rotate DB_PASSWORD --env staging -rh-envault sync staging prod +envault rotate DB_PASSWORD --env staging +envault sync staging prod # Audit before deployment -rh-envault audit --action rotate --limit 20 +envault audit --action rotate --limit 20 ``` ## Storage From 3f4ecea180205b68748a00f8ed461cd6159132d8 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Wed, 24 Jun 2026 06:18:05 -0400 Subject: [PATCH 5/5] docs: replace literal bearer token example with placeholder --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc8ed0f..17b6c81 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ Start an HTTP server that exposes decrypted secrets as a JSON API — ideal for envault serve # Custom port, host, and API key -envault serve --port 3000 --host 0.0.0.0 --api-key my-bearer-token +envault serve --port 3000 --host 0.0.0.0 --api-key # Use a named store from config envault serve --store production-secrets