feat: add vault run for secure secret injection into child processes#167
Conversation
Greptile SummaryAdds
Confidence Score: 5/5Safe to merge — no secret values leak to stdout/stderr, exit codes propagate correctly, and signal handling uses one-shot listeners. The new src/commands/vault.ts — Important Files Changed
|
vault run for secure secret injection into child processes
Adds a new `workos vault run --secret ENV=name -- <command>` subcommand that fetches secrets from WorkOS Vault by name and injects them as environment variables into a spawned child process. The wrapper itself never prints secret values, so it can be used safely from AI coding agents (Codex, Claude Code, Cursor) where the value would otherwise end up in the model context. - Sequential fetch with fail-fast (no partial injection on error) - Error messages reference vault object names only, never values - `--dry-run` prints the env-var to vault-object mapping (no fetch) - `--env`, `--org`, and JSON mode supported - Forwards SIGINT/SIGTERM (and SIGBREAK on Windows) to the child - Exit code of the child is propagated to the wrapper
…, harden SDK workaround - Export isSdkException from api-error-handler and add context param for enriched 404 messages, eliminating the duplicated type guard in vault-run - Move handleVaultSdkError into createApiErrorHandler with .includes() match to survive V8 message format changes - Remove --org from vault run (was accepted but never threaded to SDK) - Mark --org as required in vault create help-json schema - Replace sequential secret fetching with Promise.all (same fail-fast, better latency) - Use process.once for signal forwarding to prevent listener accumulation - spawnChild returns Promise<number> instead of calling process.exit directly, simplifying tests and making the function composable - Dry-run JSON goes to stdout (primary output); execution-path metadata goes to stderr (child owns stdout) - Resolve base URL from named environment when --env is set - Remove dead code: unused imports, unreachable guards, stale test infra
When --env selects an environment without a custom endpoint, resolveRunBaseUrl fell through to resolveApiBaseUrl() which returns the active environment's endpoint. This sent the selected env's API key to the wrong host (e.g. production key to localhost). Now returns the default WorkOS API base URL when the named env has no endpoint, instead of leaking the active env's endpoint.
vault create/update: --value is now optional. When omitted or set to -, the value is read from stdin. This keeps secrets out of shell history and ps output. vault get/get-by-name: returns metadata only by default. Pass --decrypt to include the decrypted secret value. vault get uses describeObject (never requests decryption); vault get-by-name strips the value from the response before output.
readValueFromStdin was calling trimEnd() which silently strips trailing whitespace. The --value flag preserves the exact string, so stdin should too.
2e89333 to
c7da7f3
Compare
Strip only a trailing \r?\n from stdin values so `echo "secret" |`
works as expected without silently altering intentional whitespace.
Narrow the SDK TypeError match from .includes('is not iterable') to
.includes('errors is not iterable') to avoid misclassifying unrelated
TypeErrors like "undefined is not iterable".
Summary
workos vault run --secret ENV=vault-name -- <command>that fetches secrets from Vault and injects them as env vars into a child process without exposing values in stdout/stderrvault getandvault get-by-namenow return metadata only by default; pass--decryptto include the decrypted valuevault createandvault updateaccept value from stdin when--valueis omitted or set to-, keeping secrets out of shell historyvault createcrash ("errors is not iterable") by requiring--organd handling SDK 422 parsing bug in the shared error handlerisSdkExceptionexported fromapi-error-handler,contextparam for enriched 404 messages, SDK TypeError workaround with.includes()matchvault run--secret ENV_VAR=vault-name(repeatable) maps vault objects to env vars--envselects a specific WorkOS environment (API key + base URL)--dry-runshows metadata without fetching (JSON to stdout, human to stdout)Promise.all(fail-fast on first error)process.oncefor SIGINT/SIGTERM), exit code passthroughspawnChildreturnsPromise<number>for composability and testabilityvault get/vault get-by-name--decryptflag includes the secret valuevault getusesdescribeObject(never requests decryption);vault get-by-namestrips value from responsevault create/vault update--valueis now optional; omit or pass-to read from stdinecho "my-secret" | workos vault create --name db-password --org org_123psoutputvault createfix--orgis now required (Vault API requires non-emptykey_context)createApiErrorHandlerwith.includes()matchTest plan
pnpm test-- 1823 tests passpnpm typecheckpnpm buildworkos vault get <id>returns metadata without valueworkos vault get <id> --decryptreturns metadata with valueecho "secret" | workos vault create --name test --org org_123reads from stdinworkos vault create --name test --org org_123 --value secretstill works inlineworkos vault run --dry-run --secret MY_VAR=test-secret -- echo hiprints metadata tableworkos vault run --secret MY_VAR=test-secret -- sh -c 'echo $MY_VAR'injects the valueworkos vault create --name x --value yshows "Missing required argument: org"