Bug fixes: composite PK, nl_to_sql sandbox, FK check, SQL injection, storage correctness

Critical fixes:
- Composite PK: execInsert + validateConstraints use all PK columns
- nl_to_sql: non-SELECT SQL no longer executed directly during validation
- FK check: removed O(N) scanMemTable fallback, uses db.get() with SSTables
- exprToSql: nkIdent wrapped in quotes to prevent SQL injection
- restoreSchema: try/except around tokenize/parse for crash resilience
- recovery.nim: lastTxnId tracking + putUnsafe/deleteUnsafe without WAL
- SCRAM: verifyClientProof length check + DefaultIterationCount restored

Storage fixes:
- lsm.nim: SSTable sort order fixed (ascending), close() flushes all memtables
- compaction.nim: tombstones preserved during compaction
- wal.nim: header written for empty existing files, readEntries checks magic
- btree.nim: B+ tree leaf split keeps boundary key
- bloom.nim: deserialize raises on short data
- mmap.nim: bounds checks for adviseWillNeed/DontNeed + posix.close()

Protocol fixes:
- zerocopy.nim: readString bounds check
- wire.nim: deserializeValue 32-bit underflow check
- auth.nim: JWT JSON escaping for claims
- server.nim: readUint32BE bounds check + specific exception handling
- raft.nim: readData checks + specific exception handling

Tests:
- Added Composite Primary Key test suite (4 tests)

Build: 0 warnings, 0 errors
This commit is contained in:
2026-05-18 11:33:11 +03:00
parent a28c845476
commit 967c0855a5
25 changed files with 376 additions and 171 deletions
+11 -5
View File
@@ -40,7 +40,9 @@ proc hmacSha256*(key, message: string): array[32, byte] =
var ctx = initSha_256()
ctx.update(k.toOpenArray(0, k.len-1))
let hash = ctx.digest()
k = $hash
var kHashed = newString(32)
copyMem(addr kHashed[0], unsafeAddr hash[0], 32)
k = kHashed
while k.len < 64:
k &= "\x00"
@@ -130,6 +132,8 @@ proc generateNonce*(): string =
## Generate a cryptographically secure random nonce (base64-encoded).
when defined(linux) or defined(macosx) or defined(bsd):
let f = open("/dev/urandom")
if f == nil:
raise newException(IOError, "Failed to open /dev/urandom")
defer: f.close()
var bytes = newString(NonceBytes)
let readLen = f.readBuffer(addr bytes[0], NonceBytes)
@@ -209,18 +213,18 @@ proc parseClientFinal*(msg: string): (string, string, seq[byte]) =
## Returns: (channel_binding, nonce, proof_bytes)
var cbind = ""
var nonce = ""
var proofHex = ""
var proofEncoded = ""
for p in msg.split(","):
if p.startsWith("c="):
cbind = p[2..^1]
elif p.startsWith("r="):
nonce = p[2..^1]
elif p.startsWith("p="):
proofHex = p[2..^1]
if nonce.len == 0 or proofHex.len == 0:
proofEncoded = p[2..^1]
if nonce.len == 0 or proofEncoded.len == 0:
raise newException(ValueError, "Missing nonce or proof in client-final-message")
# proof is base64-encoded
var proof = decode(proofHex)
var proof = decode(proofEncoded)
var proofBytes = newSeq[byte](proof.len)
if proof.len > 0:
copyMem(addr proofBytes[0], addr proof[0], proof.len)
@@ -240,6 +244,8 @@ proc buildServerFinal*(serverSignature: openArray[byte]): string =
# ---------------------------------------------------------------------------
proc verifyClientProof*(state: ScramServerState, clientProof: openArray[byte]): bool =
if clientProof.len != 32:
return false
let clientKey = xorBytes(clientProof, @(hmacSha256(state.storedKey, state.authMessage)))
let computedStoredKey = sha256(clientKey)
for i in 0..<32: