envpkt 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -24
- package/dist/cli.js +468 -476
- package/dist/index.d.ts +37 -30
- package/dist/index.js +342 -344
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -141,9 +141,9 @@ See [`examples/`](./examples/) for more configurations.
|
|
|
141
141
|
|
|
142
142
|
Sealed packets embed age-encrypted secret values directly in `envpkt.toml`. This makes your config fully self-contained — no external secrets backend needed at runtime.
|
|
143
143
|
|
|
144
|
-
> **
|
|
144
|
+
> **No `age` install needed.** envpkt seals, unseals and makes keys in-process with [age-encryption](https://github.com/FiloSottile/typage), the TypeScript implementation by age's author. The format is standard [age](https://github.com/FiloSottile/age): `age -d` decrypts what envpkt seals, and envpkt unseals values sealed by the `age` CLI. Every path that decrypts a sealed value (`boot()`, `exec`, `env export`, `env github`, the GitHub Action) needs only the private key.
|
|
145
145
|
>
|
|
146
|
-
>
|
|
146
|
+
> envpkt reads plain age key files, like the ones `envpkt keygen` and `age-keygen` write. A passphrase-protected key file, an age plugin key or an SSH key fails with an error that names the file; `envpkt doctor` shows which key it found and why it can't use it.
|
|
147
147
|
|
|
148
148
|
### Setup
|
|
149
149
|
|
|
@@ -180,7 +180,7 @@ At runtime, sealed values are automatically decrypted:
|
|
|
180
180
|
```typescript
|
|
181
181
|
import { boot } from "envpkt"
|
|
182
182
|
|
|
183
|
-
const result = boot() // decrypts sealed values, injects into process.env
|
|
183
|
+
const result = await boot() // decrypts sealed values, injects into process.env
|
|
184
184
|
```
|
|
185
185
|
|
|
186
186
|
Mixed mode is supported — sealed values take priority, with fnox as fallback for keys without `encrypted_value`.
|
|
@@ -190,7 +190,7 @@ Mixed mode is supported — sealed values take priority, with fnox as fallback f
|
|
|
190
190
|
Whether envpkt can replace a cloud secret store depends on one thing: **do you control how the process starts?**
|
|
191
191
|
|
|
192
192
|
- **You launch it** (server, container, VM, k8s, systemd) → `envpkt exec -- yourapp` or `boot()` injects the decrypted values straight into the process environment. No cloud secret store needed: the sealed `envpkt.toml` ships with the app, and only the age key has to reach the runtime (N secrets collapse to 1 key).
|
|
193
|
-
- **A platform launches it** (Cloudflare Workers, AWS Lambda, Vercel) → you don't own the launch,
|
|
193
|
+
- **A platform launches it** (Cloudflare Workers, AWS Lambda, Vercel) → you don't own the launch, so the platform's secret store is unavoidable. envpkt becomes the source of truth that populates and audits that store — not the injector.
|
|
194
194
|
|
|
195
195
|
> Rule of thumb: **own the launch → `exec`/`boot`, no store. Platform owns the launch → store required, envpkt feeds and audits it.** Full details: [Runtime injection guide](https://envpkt.dev/guides/runtime-injection/).
|
|
196
196
|
|
|
@@ -201,9 +201,6 @@ A composite action resolves the credentials in `envpkt.toml` into the CI job —
|
|
|
201
201
|
```yaml
|
|
202
202
|
- uses: actions/checkout@v5
|
|
203
203
|
|
|
204
|
-
# Sealed packets are decrypted with the `age` CLI (not preinstalled on runners).
|
|
205
|
-
- run: sudo apt-get update && sudo apt-get install -y age
|
|
206
|
-
|
|
207
204
|
- uses: jordanburke/envpkt@v0
|
|
208
205
|
with:
|
|
209
206
|
config: ./envpkt.toml
|
|
@@ -214,11 +211,11 @@ A composite action resolves the credentials in `envpkt.toml` into the CI job —
|
|
|
214
211
|
- run: ./deploy.sh # sees the resolved vars; secret values redacted in the log
|
|
215
212
|
```
|
|
216
213
|
|
|
217
|
-
**How it works.** Commit sealed (`encrypted_value`) packets to the repo and supply the age private key as the `ENVPKT_AGE_KEY` secret. `boot()`
|
|
214
|
+
**How it works.** Commit sealed (`encrypted_value`) packets to the repo and supply the age private key as the `ENVPKT_AGE_KEY` secret. `boot()` decrypts with the inline key in memory (it never touches disk), then [`env github`](#envpkt-env-github) masks each secret (`::add-mask::`) and writes it to `$GITHUB_ENV`. Identity precedence: `identity.key_file` → `ENVPKT_AGE_KEY_FILE` → `ENVPKT_AGE_KEY` (inline) → `~/.envpkt/age-key.txt`.
|
|
218
215
|
|
|
219
216
|
**Inputs:** `config`, `version` (npm version to run, default `latest`), `strict`, `profile`.
|
|
220
217
|
|
|
221
|
-
>
|
|
218
|
+
> The runner needs no `age` install: envpkt unseals in-process. Reference `@v0` for the moving major tag (re-pointed to each `0.x` release), or pin an exact release like `@v0.13.6` for immutability. `@v1` ships when envpkt reaches 1.0. The action does not set up Node: it runs on the runner's Node, which must be 22.12 or later. Add `actions/setup-node` first if the runner's Node is older.
|
|
222
219
|
|
|
223
220
|
### Anti-rot CI gate
|
|
224
221
|
|
|
@@ -408,16 +405,19 @@ Exit codes: `0` = healthy, `1` = degraded, `2` = critical.
|
|
|
408
405
|
|
|
409
406
|
### `envpkt doctor`
|
|
410
407
|
|
|
411
|
-
One-shot environment check: is
|
|
408
|
+
One-shot environment check: is a config resolvable here, is a usable age key found, and do its sealed secrets decrypt with it? It also reports the built-in age-encryption version.
|
|
412
409
|
|
|
413
410
|
```bash
|
|
414
411
|
envpkt doctor
|
|
415
|
-
#
|
|
416
|
-
#
|
|
417
|
-
#
|
|
412
|
+
# ✓ age built in (age-encryption 0.3.1)
|
|
413
|
+
# ✓ config /path/to/envpkt.toml
|
|
414
|
+
# ✓ secrets 5 resolved, 0 skipped
|
|
415
|
+
# • audit healthy
|
|
416
|
+
#
|
|
417
|
+
# ✓ no issues
|
|
418
418
|
```
|
|
419
419
|
|
|
420
|
-
If
|
|
420
|
+
If a sealed packet has no key, it lists the paths it searched; if the key file can't be used (unreadable, passphrase-protected, a plugin or SSH key), it says why. Exits non-zero when a check fails.
|
|
421
421
|
|
|
422
422
|
### `envpkt resolve`
|
|
423
423
|
|
|
@@ -648,18 +648,18 @@ These are searched after CWD but before the built-in candidate paths. Useful for
|
|
|
648
648
|
|
|
649
649
|
## Library API
|
|
650
650
|
|
|
651
|
-
envpkt is also available as a TypeScript library with a functional programming API built on [functype](https://github.com/jordanburke/functype).
|
|
651
|
+
envpkt is also available as a TypeScript library with a functional programming API built on [functype](https://github.com/jordanburke/functype). Fallible functions return `Either<Error, Result>` or `Option<T>` instead of throwing; `boot()` is the one throwing convenience wrapper. Functions that encrypt or decrypt (`boot`, `bootSafe`, `ageEncrypt`, `ageDecrypt`, `sealSecrets`, `unsealSecrets`, `generateKeypair`) return Promises, so `await` them. Without `await`, `boot()` returns before it injects anything.
|
|
652
652
|
|
|
653
653
|
```typescript
|
|
654
654
|
import { boot, bootSafe, loadConfig, computeAudit, scanFleet, resolveConfig } from "envpkt"
|
|
655
655
|
|
|
656
656
|
// Boot API — load config, resolve catalog, audit, inject secrets
|
|
657
|
-
const result = boot({ configPath: "envpkt.toml", inject: true })
|
|
657
|
+
const result = await boot({ configPath: "envpkt.toml", inject: true })
|
|
658
658
|
console.log(result.audit.status) // "healthy" | "degraded" | "critical"
|
|
659
659
|
console.log(result.configSource) // "flag" | "env" | "cwd" | "search"
|
|
660
660
|
|
|
661
661
|
// Safe variant returns Either instead of throwing
|
|
662
|
-
const safe = bootSafe({ configPath: "envpkt.toml" })
|
|
662
|
+
const safe = await bootSafe({ configPath: "envpkt.toml" })
|
|
663
663
|
safe.fold(
|
|
664
664
|
(err) => console.error("Boot failed:", err._tag),
|
|
665
665
|
(result) => console.log(`${result.injected.length} secrets injected`),
|
|
@@ -693,7 +693,7 @@ console.log(`${fleet.total_agents} agents, ${fleet.total_secrets} secrets`)
|
|
|
693
693
|
import { boot } from "envpkt"
|
|
694
694
|
|
|
695
695
|
// Resolve and inject credentials before agent startup
|
|
696
|
-
const result = boot({ configPath: "envpkt.toml", inject: true })
|
|
696
|
+
const result = await boot({ configPath: "envpkt.toml", inject: true })
|
|
697
697
|
console.log(`${result.audit.status} — ${result.injected.length} secrets loaded`)
|
|
698
698
|
|
|
699
699
|
// Now start your agent framework — process.env is populated
|
|
@@ -764,23 +764,31 @@ matchEnvVar("OPENAI_API_KEY", "sk-test123").fold(
|
|
|
764
764
|
### Seal API
|
|
765
765
|
|
|
766
766
|
```typescript
|
|
767
|
-
import { ageEncrypt, ageDecrypt, sealSecrets, unsealSecrets } from "envpkt"
|
|
767
|
+
import { ageEncrypt, ageDecrypt, describeIdentityError, sealSecrets, unsealSecrets, unwrapAgentKey } from "envpkt"
|
|
768
768
|
|
|
769
769
|
// Encrypt a single value
|
|
770
|
-
const encrypted = ageEncrypt("sk-my-api-key", "age1ql3z7hjy...")
|
|
770
|
+
const encrypted = await ageEncrypt("sk-my-api-key", "age1ql3z7hjy...")
|
|
771
771
|
encrypted.fold(
|
|
772
772
|
(err) => console.error("Encrypt failed:", err.message),
|
|
773
773
|
(ciphertext) => console.log(ciphertext), // -----BEGIN AGE ENCRYPTED FILE-----
|
|
774
774
|
)
|
|
775
775
|
|
|
776
|
+
// Decrypting takes secret keys (AGE-SECRET-KEY-…), not a path; unwrapAgentKey reads them from a key file
|
|
777
|
+
const keys = unwrapAgentKey("/path/to/identity.txt").fold(
|
|
778
|
+
(err) => {
|
|
779
|
+
throw new Error(describeIdentityError(err))
|
|
780
|
+
},
|
|
781
|
+
(k) => k,
|
|
782
|
+
)
|
|
783
|
+
|
|
776
784
|
// Decrypt a single value
|
|
777
|
-
const decrypted = ageDecrypt(ciphertext,
|
|
785
|
+
const decrypted = await ageDecrypt(ciphertext, keys)
|
|
778
786
|
|
|
779
|
-
// Seal all secrets in a config's
|
|
780
|
-
const sealed = sealSecrets(config.
|
|
787
|
+
// Seal all secrets in a config's [secret] table
|
|
788
|
+
const sealed = await sealSecrets(config.secret ?? {}, { OPENAI_API_KEY: "sk-..." }, recipientPublicKey)
|
|
781
789
|
|
|
782
790
|
// Unseal all encrypted_value entries
|
|
783
|
-
const values = unsealSecrets(config.
|
|
791
|
+
const values = await unsealSecrets(config.secret ?? {}, keys)
|
|
784
792
|
values.fold(
|
|
785
793
|
(err) => console.error("Unseal failed:", err.message),
|
|
786
794
|
(secrets) => console.log(secrets), // { OPENAI_API_KEY: "sk-..." }
|