Improve README for match ops and LLM contract

This commit is contained in:
David Ibia
2026-01-12 20:54:14 +01:00
parent f8538c8bca
commit 2b4ff5bf81

View File

@@ -5,9 +5,10 @@ Safely inspect and match `.env` secrets **without ever printing values**.
`envsitter` is designed for LLM/agent workflows where you want to: `envsitter` is designed for LLM/agent workflows where you want to:
- List keys present in an env source (`.env` file or external provider) - List keys present in an env source (`.env` file or external provider)
- Check whether a keys value matches a candidate value you provide at runtime ("outside-in") - Compare a keys value to a candidate value you provide at runtime ("outside-in")
- Do bulk matching (one candidate against many keys, or candidates-by-key) - Do bulk matching (one candidate against many keys, or candidates-by-key)
- Produce deterministic fingerprints for comparisons/auditing - Produce deterministic fingerprints for comparisons/auditing
- Ask boolean questions about values (empty/prefix/suffix/regex/type-ish checks) without ever returning the value
Related: https://github.com/boxpositron/envsitter-guard — an OpenCode plugin that blocks agents/tools from reading or editing sensitive `.env*` files (preventing accidental secret leaks), while still allowing safe inspection via EnvSitter-style tools (keys + deterministic fingerprints; never values). Related: https://github.com/boxpositron/envsitter-guard — an OpenCode plugin that blocks agents/tools from reading or editing sensitive `.env*` files (preventing accidental secret leaks), while still allowing safe inspection via EnvSitter-style tools (keys + deterministic fingerprints; never values).
@@ -48,6 +49,14 @@ The pepper file is created with mode `0600` when possible, and `.envsitter/` is
## CLI usage ## CLI usage
Commands:
- `keys --file <path> [--filter-regex <re>]`
- `fingerprint --file <path> --key <KEY>`
- `match --file <path> (--key <KEY> | --keys <K1,K2> | --all-keys) [--op <op>] [--candidate <value> | --candidate-stdin]`
- `match-by-key --file <path> (--candidates-json <json> | --candidates-stdin)`
- `scan --file <path> [--keys-regex <re>] [--detect jwt,url,base64]`
### List keys ### List keys
```bash ```bash
@@ -116,27 +125,6 @@ node -e "process.stdout.write('/^sk-[a-z]+-/i')" \
envsitter match --file .env --key OPENAI_API_KEY --op exists --json envsitter match --file .env --key OPENAI_API_KEY --op exists --json
``` ```
### Output contract (for LLMs)
General rules:
- Never output secret values; treat all values as sensitive.
- Prefer `--candidate-stdin` over `--candidate` to avoid shell history.
- Exit codes: `0` match found, `1` no match, `2` error/usage.
JSON outputs:
- `keys --json` -> `{ "keys": string[] }`
- `fingerprint` -> `{ "key": string, "algorithm": "hmac-sha256", "fingerprint": string, "length": number, "pepperSource": "env"|"file", "pepperFilePath"?: string }`
- `match --json` (single key) ->
- default op (not provided): `{ "key": string, "match": boolean }`
- with `--op`: `{ "key": string, "op": string, "match": boolean }`
- `match --json` (bulk keys / all keys) ->
- default op (not provided): `{ "matches": Array<{ "key": string, "match": boolean }> }`
- with `--op`: `{ "op": string, "matches": Array<{ "key": string, "match": boolean }> }`
- `match-by-key --json` -> `{ "matches": Array<{ "key": string, "match": boolean }> }`
- `scan --json` -> `{ "findings": Array<{ "key": string, "detections": Array<"jwt"|"url"|"base64"> }> }`
### Match one candidate against multiple keys ### Match one candidate against multiple keys
```bash ```bash
@@ -178,6 +166,27 @@ Optionally restrict which keys to scan:
envsitter scan --file .env --keys-regex "/(JWT|URL)/" --detect jwt,url envsitter scan --file .env --keys-regex "/(JWT|URL)/" --detect jwt,url
``` ```
## Output contract (for LLMs)
General rules:
- Never output secret values; treat all values as sensitive.
- Prefer `--candidate-stdin` over `--candidate` to avoid shell history.
- Exit codes: `0` match found, `1` no match, `2` error/usage.
JSON outputs:
- `keys --json` -> `{ "keys": string[] }`
- `fingerprint` -> `{ "key": string, "algorithm": "hmac-sha256", "fingerprint": string, "length": number, "pepperSource": "env"|"file", "pepperFilePath"?: string }`
- `match --json` (single key) ->
- default op (not provided): `{ "key": string, "match": boolean }`
- with `--op`: `{ "key": string, "op": string, "match": boolean }`
- `match --json` (bulk keys / all keys) ->
- default op (not provided): `{ "matches": Array<{ "key": string, "match": boolean }> }`
- with `--op`: `{ "op": string, "matches": Array<{ "key": string, "match": boolean }> }`
- `match-by-key --json` -> `{ "matches": Array<{ "key": string, "match": boolean }> }`
- `scan --json` -> `{ "findings": Array<{ "key": string, "detections": Array<"jwt"|"url"|"base64"> }> }`
## Library API ## Library API
### Basic usage ### Basic usage
@@ -192,6 +201,18 @@ const fp = await es.fingerprintKey('OPENAI_API_KEY');
const match = await es.matchCandidate('OPENAI_API_KEY', 'candidate-secret'); const match = await es.matchCandidate('OPENAI_API_KEY', 'candidate-secret');
``` ```
### Match operators via the library
```ts
import { EnvSitter } from 'envsitter';
import type { EnvSitterMatcher } from 'envsitter';
const es = EnvSitter.fromDotenvFile('.env');
const matcher: EnvSitterMatcher = { op: 'partial_match_prefix', prefix: 'sk-' };
const ok = await es.matchKey('OPENAI_API_KEY', matcher);
```
### Bulk matching ### Bulk matching
```ts ```ts
@@ -202,6 +223,9 @@ const es = EnvSitter.fromDotenvFile('.env');
// One candidate tested against a set of keys // One candidate tested against a set of keys
const matches = await es.matchCandidateBulk(['OPENAI_API_KEY', 'ANTHROPIC_API_KEY'], 'candidate-secret'); const matches = await es.matchCandidateBulk(['OPENAI_API_KEY', 'ANTHROPIC_API_KEY'], 'candidate-secret');
// One matcher tested against a set of keys
const prefixMatches = await es.matchKeyBulk(['OPENAI_API_KEY', 'ANTHROPIC_API_KEY'], { op: 'partial_match_prefix', prefix: 'sk-' });
// Candidates-by-key // Candidates-by-key
const byKey = await es.matchCandidatesByKey({ const byKey = await es.matchCandidatesByKey({
OPENAI_API_KEY: 'sk-...', OPENAI_API_KEY: 'sk-...',