envguardian

module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT

README

EnvGuardian

CI Go Report Card Go Reference

[!WARNING] Pre-release software — security hardening is in progress. Do not use EnvGuardian for real secrets yet. The v0.1.0 development tag has a known path-traversal vulnerability in repository-controlled file mappings. Do not install its automatic git hooks. See SECURITY.md and the tracked remediation plan.

EnvGuardian is intended to let a team commit its .env to git encrypted, so local configuration can travel with the repository. It encrypts with age recipients and keeps access in a plaintext, reviewable recipients file. EnvGuardian is a key-management and git-integration layer over age, not a cryptographic implementation.

The current code is an unsupported prototype. There is no supported release, Homebrew package, or published binary distribution. Contributors can inspect the CLI from source:

go run ./cmd/envguardian version

Do not install hooks from v0.1.0. The v0.2.0 release candidate contains the Stage A path-containment and explicit-acceptance controls, the Stage B transactional sealing core, the Stage C fail-closed verification paths, Stage D detached SSH signatures, Stage E secret-safe/atomic hygiene and coverage gates, and Stage F revocation, rotation, merge, and multi-file work. v0.1.1 was not cut before those v0.2 features landed. The project remains unsupported until final release verification and repository protection are complete.

Current implementation status

The prototype contains init, encrypt, decrypt, add-recipient, revoke, rotation, list-recipients, check, check-local, install-hooks, diff, and merge. Their presence does not mean they are ready to protect real secrets:

  • Managed plaintext and ciphertext paths are resolved at config load, confined to the repository, checked through existing-parent symlinks, and excluded from .git/.
  • v0.2 configuration accepts multiple distinct plaintext/ciphertext mappings. Every replacement is planned before mutation and the shared lock contains one exact entry per configured ciphertext.
  • Existing ciphertext is decrypted and semantically compared before any replacement. A simultaneous recipient change and divergent local plaintext is rejected instead of silently choosing the local file.
  • lock.toml version 2 has exactly one entry per configured ciphertext and binds the recipient fingerprint to that ciphertext's SHA-256 digest. The digest covers already-public ciphertext, never plaintext.
  • add-recipient plans before writing and commits ciphertext, detached signature, recipients, and lock as one rollback-capable logical transaction. Missing local plaintext is recovered in memory from the existing ciphertext.
  • Every new or replaced ciphertext gets a sibling .sig made through ssh-keygen -Y sign. The signature binds the ciphertext digest, recipient fingerprint, config path, and complete file mapping. Verification accepts only a current SSH recipient. Sealing therefore requires an SSH private-key file; age-only identities can still decrypt.
  • check verifies committed repository integrity: config and paths, recipients, lock digest/fingerprint, ciphertext signature, ciphertext decryption and dotenv validity, gitignore state, and the rotation ledger. It requires an identity; --structural-only is the explicit fork-PR mode when CI secrets are absent. It deliberately does not compare uncommitted local plaintext because CI cannot observe a developer's .env.
  • check-local compares the developer's plaintext with decryptable ciphertext and fails on a missing plaintext unless --allow-missing is explicit.
  • Automatic hooks compare the exact incoming commit with a local accepted commit. Changes to config, recipients, ciphertext, or signature require an explicit decrypt --accept-changes; hook decryption reads and authenticates committed blobs rather than unreviewed working-tree paths.
  • The pre-commit hook verifies config, recipients, lock, ciphertext, and detached signature from the Git index, rejects staged plaintext, detects partial staging, and requires an identity when managed state changes. Commits touching no managed file run structural verification only.
  • diff --install registers a repository-local, two-sided external Git diff driver. It reports + KEY, - KEY, and ~ KEY; comments and reordering are ignored, and values or other plaintext derivatives are never emitted.
  • User-visible parser, identity, age, SSH, and Git diagnostics omit secret input and untrusted upstream output. All production file writes use the atomic writer, and CI enforces crypt, config, keys, and dotenv at 85% plus 80% whole-repository statement coverage.
  • revoke NAME removes access through the planner and records affected dotenv key names in the public rotation ledger. rotation status and rotation done KEY track external credential rotation; re-encryption cannot erase access to ciphertext already present in Git history.
  • merge --install registers local semantic drivers. Git pauses even a clean key-level resolution so merge --continue can re-encrypt, re-sign, rebuild the complete lock transactionally, and stage the generated artifacts.
  • age still provides confidentiality, not sender authentication. EnvGuardian's separate detached SSH signature establishes ciphertext authorship. The v0.1.x missing-signature warning is retired: invalid, non-recipient, and missing signatures all fail closed in v0.2.

The authoritative status and sequencing are in docs/PLAN.md. The old M0/M1/M2/M3 plan is historical; current implementation status is kept in the tracked remediation plan.

Threat model

Windows permission limitation: EnvGuardian writes plaintext atomically, but Go's 0600 mode has no Windows ACL equivalent. The current development build does not install or verify a restrictive DACL, so other local accounts may retain access through inherited directory permissions. Do not use EnvGuardian for real secrets on Windows until native ACL enforcement lands.

The intended confidentiality property is narrow: repository read access alone does not reveal plaintext without a recipient identity, assuming age itself is used correctly. See the full threat model, including the automatic-decryption acceptance boundary and detached-signature trust model.

EnvGuardian does not protect against:

  • Current recipients. Anyone in recipients.toml can read everything by design.
  • Former recipients, historically. Git history retains prior ciphertext. Removing a recipient does not rotate the upstream credential.
  • A compromised developer machine. The private key and decrypted .env both live on disk.
  • A malicious contributor. age does not authenticate the ciphertext sender. A contributor can create ciphertext that decrypts for every listed recipient, but cannot create a valid EnvGuardian signature without a current recipient's SSH private key. Unsigned v0.1.x migration artifacts remain visibly weaker and still depend on explicit review/acceptance.
  • A malicious repository configuration in v0.1.0. Automatic decryption can write outside the repository; see the advisory in SECURITY.md.

Before any future supported release, deployments must enforce pull-request reviews, signed commits, protected main, required CI, and review of the security-sensitive paths in .github/CODEOWNERS.

Non-goals

Not this Use instead
A runtime secrets manager Vault, AWS Secrets Manager
Production secret injection Your cloud provider's parameter store
A server / SaaS There is no server.
Storage for large or binary secrets Object storage with its own encryption
A compliance or audit system IAM with audit logs

.env parsing conformance

There is no .env standard. EnvGuardian's parser is stricter on ambiguity and preserves source formatting. CI explicitly runs the pinned joho/godotenv differential suite. Python, Node, and Docker columns are documented reference notes, not CI-verified claims. See docs/dotenv-conformance.md.

Contributing and security

Read CONTRIBUTING.md before changing code. Report security issues using SECURITY.md, not a public issue. Changes are recorded in CHANGELOG.md.

License

MIT

Directories

Path Synopsis
cmd
envguardian command
Command envguardian is the CLI entry point for EnvGuardian.
Command envguardian is the CLI entry point for EnvGuardian.
internal
atomic
Package atomic writes files atomically: content is written to a temp file in the same directory, fsync'd, and renamed over the destination, so a reader (or a crash) never observes a half-written file.
Package atomic writes files atomically: content is written to a temp file in the same directory, fsync'd, and renamed over the destination, so a reader (or a crash) never observes a half-written file.
authenticity
Package authenticity creates and verifies detached OpenSSH signatures over public ciphertext metadata.
Package authenticity creates and verifies detached OpenSSH signatures over public ciphertext metadata.
cli
Package cli wires up the cobra command tree for EnvGuardian.
Package cli wires up the cobra command tree for EnvGuardian.
config
Package config reads and writes .envguardian/config.toml, which maps each plaintext file to its committed ciphertext, and resolves the conventional paths of the .envguardian directory.
Package config reads and writes .envguardian/config.toml, which maps each plaintext file to its committed ciphertext, and resolves the conventional paths of the .envguardian directory.
coveragegate command
Command coveragegate enforces EnvGuardian's statement-coverage release floors from a Go coverprofile.
Command coveragegate enforces EnvGuardian's statement-coverage release floors from a Go coverprofile.
crypt
Package crypt is a thin wrapper over filippo.io/age.
Package crypt is a thin wrapper over filippo.io/age.
dotenv
Package dotenv parses and writes .env files while preserving comments, blank lines, and key order.
Package dotenv parses and writes .env files while preserving comments, blank lines, and key order.
gitint
Package gitint implements repository ignore checks, managed dotfile updates, and the value-in-memory three-way dotenv merge decision table.
Package gitint implements repository ignore checks, managed dotfile updates, and the value-in-memory three-way dotenv merge decision table.
keys
Package keys loads and saves the recipients file and resolves the local decryption identity.
Package keys loads and saves the recipients file and resolves the local decryption identity.
rotation
Package rotation owns the public, committed ledger of dotenv key names that must be rotated after a recipient is revoked.
Package rotation owns the public, committed ledger of dotenv key names that must be rotated after a recipient is revoked.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL