feat(env): dormant decrypt hook + enc:v1 contract for env-secret encryption#1528
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for encrypted environment variables (prefixed with enc:v1:) that are decrypted at runtime using a registered decryptor. The feedback recommends throwing a fatal error instead of silently skipping variables when no decryptor is registered or when decryption fails, to prevent the application from starting up in an insecure or misconfigured state. Corresponding updates to the unit tests and documentation are also suggested.
|
Reviewed; no blockers found. |
8e0282e to
ad6253f
Compare
f04f5a8 to
b58bbe7
Compare
kriszyp
left a comment
There was a problem hiding this comment.
I assume the idea of reading an ~/harper/.env (independent of app, safe from deploy clobbering) is independent of this PR.
| This is an **opt-in, Pro-gated** feature. The cryptography and the private key live entirely in the | ||
| Harper Pro **env-secrets** component. Core only: | ||
|
|
||
| - recognises the `enc:v1:` value prefix ([`utility/envFile.ts`](../utility/envFile.ts) → |
|
Why is this closed? |
I thought this wasn't what you wanted?
|
|
This plus the latent pro feature would enable non-Fabric clusters to use this encryption. And we could sort out an alternative mode that hoists the protection outside the container next. Does that sound like a viable path, @kriszyp ? |
…yption
Adds the core (Apache) half of Pro-gated .env secret encryption: clients
encrypt secret values with a cluster public key, the ciphertext is stored
and replicated verbatim, and the runtime decrypts it into process.env. The
crypto and private key live entirely in the Harper Pro env-secrets
component; core only provides the contract and a dormant hook:
- utility/envFile.ts: ENV_ENCRYPTED_PREFIX ('enc:v1:') + isEncryptedEnvValue
- resources/envSecretDecryptor.ts: register/get/clear hook for a decryptor
- resources/loadEnv.ts: decrypt enc:v1 values via the registered decryptor;
with none registered, skip the value with a warning (so the app fails on a
missing var rather than receiving ciphertext)
- docs/env-secret-encryption.md: envelope format + client flow + threat model
Dormant without Pro: no decryptor is registered, so behavior is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address review: make an undecryptable enc:v1 value non-silent by logging at error level, while keeping load non-fatal (skip). Chosen over a fatal throw so the node still boots — the bad value remains fixable via set_env_value (a fatal throw at boot would deadlock remediation), and a non-Pro node isn't crashed by a replicated encrypted value. Docs updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ad6253f to
c7d1f44
Compare
env is the first consumer of the enc:v1 hook, not the only one — the upcoming secrets store and deploy-credential resolution share it. Rename before this ships in a release so the extension point never needs a breaking rename. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stacked on #1527 (base:
feat/protect-env-files). Core (Apache) half of Pro-gated.envsecret encryption. The companion harper-pro PR implements the cryptography, keypair, andget_secrets_public_keyoperation.What & why
The
.envprotection in #1527 is editor-only ("smoke and mirrors"). This adds the contract for real encryption: clients encrypt a secret value with a cluster-shared public key, the ciphertext (enc:v1:…) is stored and replicated verbatim, and the runtime decrypts it intoprocess.env. A secret value is then never plaintext to the operations API, logs, replication payloads, or on disk.The crypto and the private key live entirely in Harper Pro. Core only provides the contract and a dormant hook — so this PR changes no behavior on its own.
Changes
utility/envFile.ts—ENV_ENCRYPTED_PREFIX(enc:v1:) +isEncryptedEnvValue().resources/envSecretDecryptor.ts(new) —registerEnvSecretDecryptor()/getEnvSecretDecryptor()/clearEnvSecretDecryptor(). A dependency-free per-process registry the Pro component fills in.resources/loadEnv.ts— for eachenc:v1:value, decrypt via the registered decryptor; if none is registered (no Pro), skip the value with a warning so the app fails on a missing var rather than receiving ciphertext. Plaintext values are unchanged.docs/env-secret-encryption.md(new) — theenc:v1:envelope format (hybrid RSA-OAEP-SHA256 + AES-256-GCM), client flow with a Node reference, key model, and threat model.Gating
Dormant without Pro: no decryptor registered →
loadEnvbehaves exactly as before. The Pro env-secrets component (companion PR) registers the decryptor and is gated by component presence (the existing idiomatic Pro gate).Tests
unitTests/utility/envFile.test.js—isEncryptedEnvValuerecognition (prefix, case-sensitivity, non-strings).unitTests/resources/loadEnv.test.js— registry register/get/clear; andloadEnvvia a fake scope: plaintext passthrough, decrypt-with-decryptor, skip-without-decryptor, skip-on-decryptor-error.oxlint, prettier, and full
tscall pass; the env + loadEnv suites are green.Not in this PR (companion harper-pro PR)
Keypair generation/distribution (rides the existing JWT-keypair clone path), the
get_secrets_public_keyoperation, the hybrid decrypt provider that fills this hook, andbin/harper.jscomponent registration.🤖 Generated with Claude Code
Related PRs — env-secret encryption
One
enc:v1:client contract across tiers (public key + envelope; values encrypted before they leave the client):.envprotection in the operations API (masking +get_env_keys/set_env_value/delete_env_value)enc:v1:decrypt hook + envelope contract (stacked on feat(components): protect .env files in the operations API #1527)get_secrets_public_key, runtime decryptor)ClusterSecretsops (key/secrets never on the container)enc:v1encryption)