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
fix(security)!: do not embed TOTP secret in unseal token
CRITICAL security fix in enterprise mode. Previous alpha versions
(0.1.0-alpha.{1,2,3}) embedded the operator's literal TOTP secret in
the JWS payload of every minted unseal token. JWS payload is base64
JSON, NOT encrypted — anyone observing a token (CI logs, container
env dumps, kubectl describe pod, stack traces in error reporters)
could extract the secret and, with the master key, mint unseal tokens
for FUTURE deploys indefinitely.
The fix:
Token payload now carries a salt-bound HMAC derivative:
enterprise_epoch = HMAC-SHA256(totpSecret, salt || "epoch-v1")
File commits to:
epoch_commit = HMAC-SHA256(derivedKey, enterprise_epoch || "epoch-commit-v1")
Verification:
expected = HMAC-SHA256(derivedKey, payload.epoch || "epoch-commit-v1")
assert expected == file.EPOCH-COMMIT
The TOTP secret never leaves the operator's machine. A leaked token
reveals only the salt-bound derivative, which is useless for minting
tokens against re-sealed files (different salt → different epoch).
BREAKING:
- Wire format field renamed: TOTP-VERIFIER → EPOCH-COMMIT
- Token payload field renamed: totp_secret → epoch
- buildUnsealToken signature now requires the file's salt
- Files sealed by 0.1.0-alpha.{1,2,3} are NOT readable by 0.1.0-alpha.4
Migration: re-init keys (TOTP rotation is mandatory) and re-seal all
files. See CHANGELOG entry for the full migration playbook.
Regression tests verify:
- Serialized files do not contain TOTP-VERIFIER field
- Minted tokens do not contain the literal secret in any encoding
(hex, base64) or under the field name totp_secret
Both Node and Java sides updated in lockstep. Cross-stack vector
regenerated with the new format.
Reported by an external reviewer who decoded the JWS payload of a
minted token and matched the embedded value bit-for-bit against the
operator's .env.local TOTP secret. Thank you.
0 commit comments