Repository navigation
fix(security): serialize activity log hash chain writes (#542) - #867
Conversation
… lock (#542) Activity log hash chain writes were not serialized — concurrent calls to logActivity() could read the same previousHash and fork the chain. Adds pg_advisory_xact_lock (key 5829174063) to both logActivity() and logActivityWithTx(), matching the pattern already used by security audit chain (commit 9a2856e). Broadcast and workflow triggers now fire outside the transaction to avoid holding the lock unnecessarily. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 21 minutes and 28 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThe PR refactors activity logging to exclude PII from hash-chain computations, introduces advisory-lock-based serialization to prevent concurrent hash-chain mutations, reorders Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Logger as Activity Logger
participant Lock as Advisory Lock<br/>(DB)
participant TX as Transaction<br/>(DB)
participant Chain as Hash Chain<br/>(DB)
rect rgba(100, 150, 200, 0.5)
Note over Client,Chain: OLD FLOW (Non-serialized)
Client->>Logger: logActivity(data)
Logger->>TX: Read latest hash
Logger->>Logger: Compute new hash (with PII)
Logger->>TX: Insert log entry
TX->>Chain: Write to activity_logs
TX-->>Logger: Commit
Logger->>Client: Return (possible race conditions)
end
rect rgba(150, 200, 100, 0.5)
Note over Client,Chain: NEW FLOW (Serialized, PII-excluded)
Client->>Logger: logActivity(data)
Logger->>Lock: Acquire pg_advisory_xact_lock
Lock-->>Logger: Lock acquired
Logger->>TX: Begin transaction
Logger->>Chain: Read latest hash
Logger->>Logger: Compute new hash (PII excluded)
Logger->>TX: Insert log entry
TX->>Chain: Write to activity_logs
TX-->>Logger: Commit
Logger->>Lock: Release lock
Lock-->>Logger: Lock released
Logger->>Client: Return (serialized, safe)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- P1: Fix compliance test mock — add db.transaction and sql to the activity-logger-compliance.test.ts mock so logActivity() inserts actually execute instead of silently failing (30 tests) - P2: Reorder callers so createPageVersion() runs before logActivityWithTx() in page-mutation-service and page-service, preventing the advisory lock from being held across disk I/O - Remove dead getLatestLogHash() (replaced by getLatestLogHashWithTx) - Remove duplicate "reads latest hash after acquiring lock" test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Master removed userId/actorEmail from HashableLogData (#541) for right-to-erasure compliance. Resolved by keeping advisory lock structure from this branch while adopting master's PII-free hash computation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve conflict: keep advisory lock structure from this branch while adopting master's PII-free HashableLogData type (userId and actorEmail excluded for right-to-erasure compliance). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update for #865 (Redis export rate limit), #866 (activity log PII exclusion), #867 (activity chain serialization), #868-870 (audit service wiring), #863 (GDPR cron jobs), #861 (password auth removed). Fix code review comments: AI usage log deletion is explicit call not FK cascade; note shared-page assistant messages survive account deletion; resolve P2 #8 and #9 contradictions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: update compliance doc and prototype panes to reflect implemented fixes DSAR export, message hard-delete, AI log purge on account deletion, and audit chain verification are now implemented — update stale gap claims. Note in-progress work on pu/hash-chain-pii and pu/export-rate-limit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: reflect merged PRs and fix stale review comments Update for #865 (Redis export rate limit), #866 (activity log PII exclusion), #867 (activity chain serialization), #868-870 (audit service wiring), #863 (GDPR cron jobs), #861 (password auth removed). Fix code review comments: AI usage log deletion is explicit call not FK cascade; note shared-page assistant messages survive account deletion; resolve P2 #8 and #9 contradictions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: fix stale password auth reference, add userId:null caveat Section 7.1 referenced "local email+password auth" — password auth was removed in #861; on-prem now uses magic links + passkeys. GdprPane message deletion cards now note shared-page assistant messages with userId: null may survive account deletion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(prototype): restructure panes so resolved items live in Current Resolved items were sitting in Gaps/End Game with "Fixed"/"Done" labels, breaking the narrative flow. Now: - Current: hash chain integrity, distributed rate limit, message hard-delete all live in their natural subsections - Gaps: only genuine gaps remain (cookie consent, data residency, SIEM, audit coverage, agent trails) - End Game: only future work (no "Done" items cluttering the roadmap) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
pg_advisory_xact_locktologActivity()andlogActivityWithTx()to serialize hash chain writes, preventing concurrent fork vulnerability5829174063(distinct from security audit's8370291546) to avoid cross-chain contentioncreatePageVersion()beforelogActivityWithTx()in page-mutation-service and page-service so the advisory lock is not held across disk I/O (compression + fs.writeFile)getLatestLogHash()(replaced bygetLatestLogHashWithTx)db.transactionandsqlso all 30 tests actually execute insertsContext
The security audit chain was fixed with advisory locking in commit 9a2856e (#541-544), but the activity log chain was missed. Concurrent calls to
logActivity()could read the samepreviousHashand both insert entries pointing to it, forking the chain.Test plan
describe('hash chain serialization (#542)')verify lock acquisition, key value, andlogActivityWithTxlock behavior🤖 Generated with Claude Code