fix: hand the age identity to age on stdin — fd paths resolve only in cast's process #35
No reviewers
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:apply
scope:capture
scope:coolify-api
scope:fleet
scope:manifest
scope:secrets
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/cast#35
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/age-key-stdin"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #34.
What was broken
CAST_AGE_KEY_FILE_PROD=<(pm read …)— the documented recipe for injecting a prod key that never touches disk — could never work. The shell's<(…)expands to/proc/self/fd/N(zsh) or/dev/fd/N(bash), a path meaningful only inside the process holding the fd. cast passed that string to a freshly-spawnedage, which resolved it against its own fd table and failed with ENOENT — for every password manager, on every shell. The workaround people reach for is a plaintext key file on disk, the exact thing the design forbids.The fix
Exactly what the issue prescribes: node does own the fd, so
decryptSecretsnow reads the identity itself and hands it to age as-i -on stdin:The key still never becomes a file, never appears in argv, and never enters the environment. Per the issue's notes,
-i /dev/stdinis deliberately not used (node closes the pipe before age re-opens it by path → ENXIO), and no--age-keyflag was added.Verification
/proc/self/fd/Nas the key path. Against the old code it fails with the exact error from the live incident (age: error: reading "/proc/self/fd/22": failed to open file); with the fix it decrypts.node -e '…decryptSecrets(store, key)…' store.env.age <(cat key.txt)→ decrypts correctly.npm run checkclean.Also documents the process-substitution recipe in the README's Secrets, and attended applies section, since it now works as designed.
🤖 Generated with Claude Code