diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..009b64c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 boxpositron + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6617ff --- /dev/null +++ b/README.md @@ -0,0 +1,189 @@ +# envsitter + +Safely inspect and match `.env` secrets **without ever printing values**. + +`envsitter` is designed for LLM/agent workflows where you want to: + +- List keys present in an env source (`.env` file or external provider) +- Check whether a key’s value matches a candidate value you provide at runtime ("outside-in") +- Do bulk matching (one candidate against many keys, or candidates-by-key) +- Produce deterministic fingerprints for comparisons/auditing + +## Security model (what this tool does and does not do) + +- Values are read in-process for comparisons, but **never returned** by the library API and **never printed** by the CLI. +- Deterministic matching uses **HMAC-SHA-256** with a local pepper. + - This avoids publishing raw SHA-256 hashes that are easy to dictionary-guess. +- Candidate secrets should be passed via stdin (`--candidate-stdin`) to avoid shell history. + +Non-goals: + +- This tool is not a secret manager. +- This tool does not encrypt or relocate `.env` values; it operates on sources in-place. + +## Install + +```bash +npm install envsitter +``` + +Or run the CLI without installing globally: + +```bash +npx envsitter keys --file .env +``` + +## Pepper (required for deterministic fingerprints) + +`envsitter` uses a local "pepper" as the HMAC key. + +Resolution order: + +1. `process.env.ENVSITTER_PEPPER` (or `ENV_SITTER_PEPPER`) +2. Pepper file at `.envsitter/pepper` (auto-created if missing) + +The pepper file is created with mode `0600` when possible, and `.envsitter/` is gitignored. + +## CLI usage + +### List keys + +```bash +envsitter keys --file .env +``` + +Filter by key name (regex): + +```bash +envsitter keys --file .env --filter-regex "/(KEY|TOKEN|SECRET)/i" +``` + +### Fingerprint a single key + +```bash +envsitter fingerprint --file .env --key OPENAI_API_KEY +``` + +Outputs JSON containing the key’s fingerprint and metadata (never the value). + +### Match a candidate against a single key (recommended via stdin) + +```bash +node -e "process.stdout.write('candidate-secret')" \ + | envsitter match --file .env --key OPENAI_API_KEY --candidate-stdin --json +``` + +Exit codes: + +- `0` match found +- `1` no match +- `2` error/usage + +### Match one candidate against multiple keys + +```bash +node -e "process.stdout.write('candidate-secret')" \ + | envsitter match --file .env --keys OPENAI_API_KEY,ANTHROPIC_API_KEY --candidate-stdin --json +``` + +### Match one candidate against all keys + +```bash +node -e "process.stdout.write('candidate-secret')" \ + | envsitter match --file .env --all-keys --candidate-stdin --json +``` + +### Match candidates-by-key (bulk assignment) + +Provide a JSON object mapping key -> candidate value. + +```bash +envsitter match-by-key --file .env \ + --candidates-json '{"OPENAI_API_KEY":"sk-...","ANTHROPIC_API_KEY":"sk-..."}' +``` + +For safer input, pass the JSON via stdin: + +```bash +cat candidates.json | envsitter match-by-key --file .env --candidates-stdin +``` + +### Scan for value shapes (no values returned) + +```bash +envsitter scan --file .env --detect jwt,url,base64 +``` + +Optionally restrict which keys to scan: + +```bash +envsitter scan --file .env --keys-regex "/(JWT|URL)/" --detect jwt,url +``` + +## Library API + +### Basic usage + +```ts +import { EnvSitter } from 'envsitter'; + +const es = EnvSitter.fromDotenvFile('.env'); + +const keys = await es.listKeys(); +const fp = await es.fingerprintKey('OPENAI_API_KEY'); +const match = await es.matchCandidate('OPENAI_API_KEY', 'candidate-secret'); +``` + +### Bulk matching + +```ts +import { EnvSitter } from 'envsitter'; + +const es = EnvSitter.fromDotenvFile('.env'); + +// One candidate tested against a set of keys +const matches = await es.matchCandidateBulk(['OPENAI_API_KEY', 'ANTHROPIC_API_KEY'], 'candidate-secret'); + +// Candidates-by-key +const byKey = await es.matchCandidatesByKey({ + OPENAI_API_KEY: 'sk-...', + ANTHROPIC_API_KEY: 'sk-...' +}); +``` + +### External sources (hooks) + +You can load dotenv-formatted output from another tool/secret provider: + +```ts +import { EnvSitter } from 'envsitter'; + +const es = EnvSitter.fromExternalCommand('my-secret-provider', ['export', '--format=dotenv']); +const keys = await es.listKeys(); +``` + +## Development + +```bash +npm install +npm run typecheck +npm test +``` + +Run a single test file: + +```bash +npm run build +node --test dist/test/envsitter.test.js +``` + +Run a single test by name: + +```bash +npm run build +node --test --test-name-pattern "outside-in" dist/test/envsitter.test.js +``` + +## License + +MIT. See `LICENSE`. diff --git a/package.json b/package.json index c6f6afb..666f27e 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "envsitter", - "version": "0.1.0", - "private": true, + "version": "0.0.1", + "private": false, "type": "module", "description": "Safely inspect and match .env secrets without exposing values", "license": "MIT", "bin": { - "envsitter": "./dist/cli.js" + "envsitter": "dist/cli.js" }, "exports": { ".": {