Conversation
Also implements token hints storage
src/serve/routes.ts
Outdated
| const existing = await sbp('chelonia.db/get', `_private_kv_${contractID}_${key}`) | ||
| // This type of SAK authorization is only allowed for creating new keys | ||
| if (existing) return Boom.unauthorized('Invalid shelter auth', 'shelter') |
There was a problem hiding this comment.
🔴 TOCTOU race condition allows SAK-authenticated user to overwrite existing KV entries
The SAK authorization check for the POST /kv/{contractID}/{key} endpoint reads the existing value outside the serialization queue, creating a time-of-check-time-of-use (TOCTOU) vulnerability that bypasses the "SAK auth is only for creating new keys" restriction.
Root Cause and Exploitation
The authorization check at line 873 reads existing and, if it's null, allows the SAK-authenticated request through (since SAK is only meant for creating new keys). However, this check happens before the request enters the queue at line 899.
A race can occur:
- Request A (SAK auth) hits line 873 — key doesn't exist, so auth passes;
isOwnerremainsfalse - Request B (owner/bearer auth) enters the queue first and creates the key
- Request A enters the queue at line 899 — key now exists
- Line 901:
isOwner && !existing→false && !existing→ skipped (no guard) - If
if-match: *was sent, the ETag check at line 926 passes through - Request A overwrites the key, bypassing the intended restriction
The isOwner flag at line 901 only guards owners from writing to non-existent keys. There is no corresponding guard for !isOwner (SAK auth) writing to existing keys inside the queue.
Impact: A SAK-authenticated user can overwrite KV entries they should only have been allowed to create, not update. This is an authorization bypass that could lead to data corruption.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
🟡 Missing deletion of _private_deletionTokenHint_ in backend/deleteContract
When deleting a contract in backend/deleteContract, line 392 deletes _private_deletionTokenDgst_${cid} but the corresponding _private_deletionTokenHint_${cid} is never deleted. This is inconsistent with backend/deleteFile (src/serve/server.ts:282-283) which correctly deletes both. The result is that the hint data leaks and persists in the database after the contract is deleted.
(Refers to line 392)
Was this helpful? React with 👍 or 👎 to provide feedback.
Also implements token hints storage