Security Finding: HMAC Secret Stored in Plaintext
Severity: High
Component: whisperopencode-push (bundled plugin)
Description
The relay authentication secret (channel_secret) is stored in plaintext in the local state file at:
~/.local/share/opencode/push/whisperopencode-push.json
The state file permissions are set to 0o600 (owner read/write only), which mitigates exposure on single-user systems. However, on shared systems, any local user with the same UID can read the secret.
Impact
If a local attacker gains read access to the state file, they can:
- Impersonate the plugin to the relay server
- Receive push notifications intended for the victim
- Potentially pair new devices without the owner's consent
Evidence
State file structure (from state.js):
data.relay = {
url: res.relay_url,
channel: res.channel_id,
secret: res.channel_secret, // <-- plaintext secret
server,
};
Written with fs.chmod(file, 0o600) which only restricts access on single-UID systems.
Recommendation
Consider encrypting the secret at rest using a user-level key derived from a password or machine-specific secret (e.g., keyed by OPENCODE_TEST_HOME or a local keychain). Alternatively, store only the hash of the secret for verification purposes and keep the plaintext secret in a OS-level credential store.
References
- File:
dist/src/state.js
- Config path:
dist/src/path.js (stateFile function)
Security Finding: HMAC Secret Stored in Plaintext
Severity: High
Component:
whisperopencode-push(bundled plugin)Description
The relay authentication secret (
channel_secret) is stored in plaintext in the local state file at:The state file permissions are set to
0o600(owner read/write only), which mitigates exposure on single-user systems. However, on shared systems, any local user with the same UID can read the secret.Impact
If a local attacker gains read access to the state file, they can:
Evidence
State file structure (from
state.js):Written with
fs.chmod(file, 0o600)which only restricts access on single-UID systems.Recommendation
Consider encrypting the secret at rest using a user-level key derived from a password or machine-specific secret (e.g., keyed by
OPENCODE_TEST_HOMEor a local keychain). Alternatively, store only the hash of the secret for verification purposes and keep the plaintext secret in a OS-level credential store.References
dist/src/state.jsdist/src/path.js(stateFile function)