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
Independent security review of LeanerCloud/cloud-commitments-cli#889 confirmed a pre-existing codebase-wide pattern (not a regression): approval/revocation tokens are stored PLAINTEXT in approval_token (internal/purchase/approvals.go:315; internal/config/store_postgres.go:944; also internal/email/notifications.go:121, internal/api/handler_plans.go:393). The SHA-256-before-ConstantTimeCompare on validate is only length-normalization, not hashed-at-rest. Harden: store sha256(token), compare hashes, migrate/expire existing rows. Impact: many token paths; effort ~M. Out of scope for LeanerCloud/cloud-commitments-cli#889. Related LeanerCloud/cloud-commitments-cli#1356.
Closing as already fixed on main by #416 (merge commit 0c09e11, "sec(api): hash approval and revocation tokens at rest", merged 2026-09-28). That PR closed duplicate #103, so this one wasn't linked.
On current main, internal/config/approval_token.go provides two helpers:
HashApprovalToken: sha256 hex.
ApprovalTokenMatches: hashes the supplied token, then does a constant-time compare of digests. It fails closed on an empty value.
Every write stores only approval_token_hash:
execution create and retry;
the revocation token minted on approve;
RotatePendingApprovalToken;
RI-exchange records.
Every validate path compares hashes:
approve, cancel, revoke and the async SQS path;
TransitionExecutionStatusTokenPinned;
GetRIExchangeRecordByToken;
RI-exchange approve and reject.
Migration 000100 hashed existing raw tokens into the hash column and blanked the raw values. No production code writes the raw approval_token column anymore.
What's left is #422's contract step: drop the raw columns. @cristim
Activity
IAmJSD commentedon Oct 20, 2026
Claiming: working on this
IAmJSD commentedon Oct 20, 2026
Closing as already fixed on main by #416 (merge commit 0c09e11, "sec(api): hash approval and revocation tokens at rest", merged 2026-09-28). That PR closed duplicate #103, so this one wasn't linked.
On current main,
internal/config/approval_token.goprovides two helpers:HashApprovalToken: sha256 hex.ApprovalTokenMatches: hashes the supplied token, then does a constant-time compare of digests. It fails closed on an empty value.Every write stores only
approval_token_hash:RotatePendingApprovalToken;Every validate path compares hashes:
TransitionExecutionStatusTokenPinned;GetRIExchangeRecordByToken;Migration 000100 hashed existing raw tokens into the hash column and blanked the raw values. No production code writes the raw
approval_tokencolumn anymore.What's left is #422's contract step: drop the raw columns. @cristim