You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Comes out of #1234's deferral (2026-09-19) and docs/dev/reviews/review-1234-keystore-writer.md. A proposal, not a plan — it needs its own design review before anything is written, for the same reason the keystore wiring did: the last secret-storage design I proposed was forbidden by a sentence one line below the text I had quoted.
Why this exists
#1234's wiring was deferred because a file keystore is indirection without security for a single PSK on a headless daemon: the Argon2id master must itself come from an env var or a prompt, so env→env buys nothing. But the underlying operator problem is real — the only way to give the daemon a PSK today is OPENPULSE_CONTROL_PSK, and psk_key_id is an inert knob that merely warns.
REQ-CTL-05 already anticipates the answer. Its text lists the file types that must be owner-only:
Any file holding key or secret material (identity key, trust store, keystore, PSK file) must be owner-only: 0600 for files, 0700 for the containing directory.
A PSK file is already a named, governed concept in the requirements. Nothing implements it.
load_control_psk (daemon/src/server.rs:2083) currently reads one source and parses 64 hex chars into [u8; PSK_LEN]. The change is to read the file through openpulse_config::secret_file::validate_owner_only (secret_file.rs:15) and hand the same string to the same parser.
no new crate, no KDF, no AEAD, no writer, no prompt dependency
the permission check is machinery that already exists, is already enforced, and is REQ-CTL-05's subject
same security tier as the deferred design, with the moving parts removed
an operator can populate it with printf %s "$hex" > file && chmod 600 file
It composes with the real headless upgrade. systemd LoadCredential= / LoadCredentialEncrypted= places a decrypted secret at $CREDENTIALS_DIRECTORY/control-psk on a non-persistent tmpfs, encrypted at rest to the host key or TPM2. That is a path, so it needs exactly this config field and nothing else. This is the part I would most want reviewed — it may be the whole answer, with psk_file as the mechanism.
The panel.apps/openpulse-panel/src/transport.rs:7 is the second reader. control-channel-security.md:93 already settles its path as OS-keychain-first with an in-UI prompt fallback — so probably daemon-only, but that should be stated rather than assumed, and note transport.rs:64 is cfg(not(wasm32)).
Not a keystore, not a revival of #1234's wiring, and not a security improvement over the environment on its own — it is the same tier with fewer moving parts, whose value is that it makes systemd credentials usable. If review concludes that value is not worth 20 lines plus a config field, closing this is a fine outcome.
Comes out of #1234's deferral (2026-09-19) and
docs/dev/reviews/review-1234-keystore-writer.md. A proposal, not a plan — it needs its own design review before anything is written, for the same reason the keystore wiring did: the last secret-storage design I proposed was forbidden by a sentence one line below the text I had quoted.Why this exists
#1234's wiring was deferred because a file keystore is indirection without security for a single PSK on a headless daemon: the Argon2id master must itself come from an env var or a prompt, so env→env buys nothing. But the underlying operator problem is real — the only way to give the daemon a PSK today is
OPENPULSE_CONTROL_PSK, andpsk_key_idis an inert knob that merely warns.REQ-CTL-05 already anticipates the answer. Its text lists the file types that must be owner-only:
A PSK file is already a named, governed concept in the requirements. Nothing implements it.
The shape
load_control_psk(daemon/src/server.rs:2083) currently reads one source and parses 64 hex chars into[u8; PSK_LEN]. The change is to read the file throughopenpulse_config::secret_file::validate_owner_only(secret_file.rs:15) and hand the same string to the same parser.printf %s "$hex" > file && chmod 600 fileWhy it is better than what was deferred
FileKeystore::savetruncates without temp+rename or fsync, so an interruptedkeystore seton a Pi's SD card leaves a fileopenrejects and the daemon refuses to start. A hex file is either 64 chars or it is not, and the failure is legible.LoadCredential=/LoadCredentialEncrypted=places a decrypted secret at$CREDENTIALS_DIRECTORY/control-pskon a non-persistent tmpfs, encrypted at rest to the host key or TPM2. That is a path, so it needs exactly this config field and nothing else. This is the part I would most want reviewed — it may be the whole answer, withpsk_fileas the mechanism.Open questions for the review
OPENPULSE_CONTROL_PSKandpsk_fileboth potentially set, what wins? The openpulse-keystore has no consumer: wire the keystore-backed PSK loading that server.rs promises (blocks REQ-CTL-03) #1234 review's conclusion was that a present non-selected source should be a refusal to start even when the values are equal, because the equal case is the pre-image of the dangerous one (rotate one, forget a staleexport, and the old secret still works while the operator believes they rotated). Does that hold here, where there is nopsk_sourceselector, or doespsk_fileset ⇒ file wins ⇒ warn-on-env suffice?apps/openpulse-panel/src/transport.rs:7is the second reader.control-channel-security.md:93already settles its path as OS-keychain-first with an in-UI prompt fallback — so probably daemon-only, but that should be stated rather than assumed, and notetransport.rs:64iscfg(not(wasm32)).What this is not
Not a keystore, not a revival of #1234's wiring, and not a security improvement over the environment on its own — it is the same tier with fewer moving parts, whose value is that it makes systemd credentials usable. If review concludes that value is not worth 20 lines plus a config field, closing this is a fine outcome.
Related: #1234 (deferred), #1405 (CTL-05 reachability), #1380 (feature-gated code never type-checked).
🤖 Generated with Claude Code
https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6