Last Monday, I ran a routine Dune query on the privacy-focused blockchain chat app ChatLock. The dashboard showed a sudden spike in calls to its getPublicShare endpoint—from zero to over 15,000 in under 48 hours. Something was wrong. The project’s whitepaper promised end-to-end encryption and “off-chain sharing with access control.” Yet here, the data screamed: public access was wide open.
Context: The Privacy Promise ChatLock launched in early 2024 with a clear selling point: encrypted direct messages stored on-chain, with a share function that only reveals the decryption key to a specific recipient via a signed link. The protocol uses a permission ledger smart contract to track which addresses can read a shared message. The share function requires a setSharePermission call that writes an entry {chatId, allowedAddress}. Only the holder of the correct key can read the encrypted payload. The model is sound—on paper.
But the on-chain data told a different story. Over the past three months, the number of getPublicShare calls that returned a decrypted message without a prior setSharePermission entry grew exponentially. By cross-referencing the two tables—permission logs and share endpoints—I found a critical mismatch: 10,347 message IDs had no corresponding permission record, yet were retrieved successfully by unauthenticated addresses.
Core: The Missing Line I traced the issue to a single function in the chat app’s frontend proxy contract. The developer wrote:
function publicShare(bytes32 chatId) public view returns (string memory) {
// Missing: require(permissionLedger[msg.sender][chatId] == true);
return store.retrieve(chatId);
}
A one-line omission. No permission check before returning the decrypted payload. The team had intended to include a check that msg.sender had been granted access, but the line was never added. The result? Anyone who knew the chatId—and because chat IDs are deterministic hashes of the conversation data, they could be guessed—could fetch the message. No license required. No permission needed.
On-chain, I identified 2,300 unique chatIds that were called without a prior permission record. Assuming each shared message contains an average of 1.5KB of text, that’s roughly 15 MB of private conversation data exposed. Worse: the exposed data included timestamps, wallet addresses, and plaintext message bodies. Some messages contained transaction details, account passwords, and even seed phrases shared in confidence.
Contrarian: The Myth of Discovery The usual narrative is “bug reported, bug fixed, crisis averted.” But the on-chain data shows the bug existed since deployment—over 170 days. The team’s internal monitoring never caught it. A third-party security researcher noticed the permissionless getter because they were crawling every function call on the chain. The team patched within 6 hours of the report, but the data exposure window was half a year.
Correlation does not equal causation, but the spike in getPublicShare calls in the last month correlates with a marketing push that drove new users to the share feature. The more people used the “share” button, the more messages became public. The team’s patched contract now includes the missing require statement, but the historical data remains exposed—copied, cached, and potentially sold. High APY doesn’t always mean high returns; sometimes it means high risk.
Takeaway: What to Watch Next Week The ChatLock token pumped 12% after the patch announcement—investors cheered the quick fix. But I’ll be watching the daily new permission ledger entries for the next seven days. If the number drops back to zero after the patch, it means the team is truly enforcing access control. If it stays flat, the fix might be cosmetic. Trust is a variable, data is a constant. The data will tell us if the security culture is real—or just another vanity metric.
Trust is a variable, data is a constant. The data will tell us if the security culture is real—or just another vanity metric.