fix: audit batches 3–4 — TLS verify, WS, OFFSET, B-tree, NULL equality
CI / test (push) Has been cancelled
CI / raft-e2e (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled

Close the remaining 2026-08 findings: peer TLS on leader forward, disttxn
SO_ERROR, compaction catalog order, OFFSET without LIMIT, window aggregates,
WebSocket mask/size/auth, SCRAM timing and cbind, B-tree leaf left-max
separators, and SQL three-valued NULL comparisons.
This commit is contained in:
2026-08-28 13:53:02 +03:00
parent e44341e47c
commit 1ed97fb075
21 changed files with 718 additions and 135 deletions
+37 -4
View File
@@ -429,6 +429,8 @@ proc evalExprOld*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContex
let right = evalExprOld(expr.binRight, row, ctx)
case expr.binOp
of irEq:
# SQL three-valued logic: any NULL operand → unknown, not true.
if isNull(left) or isNull(right): return "\\N"
if left == right: return "true"
# Try numeric comparison
try:
@@ -436,6 +438,7 @@ proc evalExprOld*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContex
except CatchableError: discard
return "false"
of irNeq:
if isNull(left) or isNull(right): return "\\N"
# Numeric-first so `!=` is the exact complement of `=` (irEq): string
# inequality alone would make `5 != 5.0` true while `5 = 5.0` is true.
try:
@@ -443,26 +446,37 @@ proc evalExprOld*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContex
except CatchableError:
return if left != right: "true" else: "false"
of irLt:
if isNull(left) or isNull(right): return "\\N"
try:
return if parseFloat(left) < parseFloat(right): "true" else: "false"
except CatchableError: return if left < right: "true" else: "false"
of irLte:
if isNull(left) or isNull(right): return "\\N"
try:
return if parseFloat(left) <= parseFloat(right): "true" else: "false"
except CatchableError: return if left <= right: "true" else: "false"
of irGt:
if isNull(left) or isNull(right): return "\\N"
try:
return if parseFloat(left) > parseFloat(right): "true" else: "false"
except CatchableError: return if left > right: "true" else: "false"
of irGte:
if isNull(left) or isNull(right): return "\\N"
try:
return if parseFloat(left) >= parseFloat(right): "true" else: "false"
except CatchableError: return if left >= right: "true" else: "false"
of irAnd:
if left == "true" and right == "true": return "true"
return "false"
# false AND x = false; unknown AND true/unknown = unknown; else both true.
let lNull = isNull(left)
let rNull = isNull(right)
let lTrue = left == "true"
let rTrue = right == "true"
if (not lNull and not lTrue) or (not rNull and not rTrue): return "false"
if lNull or rNull: return "\\N"
return "true"
of irOr:
if left == "true" or right == "true": return "true"
if isNull(left) or isNull(right): return "\\N"
return "false"
of irAdd, irSub, irMul, irDiv, irMod, irPow:
let v = evalExpr(expr, stringTableToValueRow(row), ctx)
@@ -475,6 +489,7 @@ proc evalExprOld*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContex
of vkString: return v.strVal
else: return "\\N"
of irLike:
if isNull(left) or isNull(right): return "\\N"
proc escapeRe(s: string): string =
result = ""
for ch in s:
@@ -490,6 +505,7 @@ proc evalExprOld*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContex
except CatchableError: discard
return "false"
of irILike:
if isNull(left) or isNull(right): return "\\N"
proc escapeRe(s: string): string =
result = ""
for ch in s:
@@ -505,8 +521,10 @@ proc evalExprOld*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContex
except CatchableError: discard
return "false"
of irIn:
if isNull(left): return "\\N"
if expr.binRight.kind == irekSubquery:
let subRows = requireExecutePlanHook()(ctx, expr.binRight.subqueryPlan)
var sawNull = false
for row in subRows:
# Compare against the first non-internal column only (SQL semantics)
var firstVal = ""
@@ -516,8 +534,14 @@ proc evalExprOld*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContex
firstVal = valueToString(v)
found = true
break
if found and firstVal == left: return "true"
if not found: continue
if isNull(firstVal):
sawNull = true
continue
if firstVal == left: return "true"
if sawNull: return "\\N"
return "false"
if isNull(right): return "\\N"
try:
let lv = parseFloat(left)
let rv = parseFloat(right)
@@ -525,8 +549,10 @@ proc evalExprOld*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContex
except CatchableError: discard
return if left == right: "true" else: "false"
of irNotIn:
if isNull(left): return "\\N"
if expr.binRight.kind == irekSubquery:
let subRows = requireExecutePlanHook()(ctx, expr.binRight.subqueryPlan)
var sawNull = false
for row in subRows:
# Compare against the first non-internal column only (SQL semantics)
var firstVal = ""
@@ -536,8 +562,14 @@ proc evalExprOld*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContex
firstVal = valueToString(v)
found = true
break
if found and firstVal == left: return "false"
if not found: continue
if isNull(firstVal):
sawNull = true
continue
if firstVal == left: return "false"
if sawNull: return "\\N"
return "true"
if isNull(right): return "\\N"
try:
let lv = parseFloat(left)
let rv = parseFloat(right)
@@ -662,6 +694,7 @@ proc evalExprOld*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContex
case expr.unOp
of irNot:
let v = evalExprOld(expr.unExpr, row, ctx)
if isNull(v): return "\\N"
return if v == "true": "false" else: "true"
of irIsNull:
let v = evalExprOld(expr.unExpr, row, ctx)
+13 -4
View File
@@ -411,9 +411,18 @@ proc lowerSelect*(node: Node): IRPlan =
if node.selLimit != nil or node.selOffset != nil:
let limitPlan = IRPlan(kind: irpkLimit)
limitPlan.limitSource = result
limitPlan.limitCount = if node.selLimit != nil and node.selLimit.limitExpr.kind == nkIntLit:
node.selLimit.limitExpr.intVal else: 0
limitPlan.limitOffset = if node.selOffset != nil and node.selOffset.offsetExpr.kind == nkIntLit:
node.selOffset.offsetExpr.intVal else: 0
# limitCount: -1 = unlimited (OFFSET without LIMIT). LIMIT 0 is empty.
# Negative LIMIT/OFFSET are clamped so slicing cannot IndexDefect.
if node.selLimit != nil:
if node.selLimit.limitExpr.kind == nkIntLit:
limitPlan.limitCount = max(0'i64, node.selLimit.limitExpr.intVal)
else:
limitPlan.limitCount = 0
else:
limitPlan.limitCount = -1
if node.selOffset != nil and node.selOffset.offsetExpr.kind == nkIntLit:
limitPlan.limitOffset = max(0'i64, node.selOffset.offsetExpr.intVal)
else:
limitPlan.limitOffset = 0
result = limitPlan
+6
View File
@@ -255,12 +255,18 @@ proc executePlan*(ctx: ExecutionContext, plan: IRPlan): seq[Row] =
of irpkLimit:
let sourceRows = executePlan(ctx, plan.limitSource)
var start = int(plan.limitOffset)
if start < 0: start = 0
if start > sourceRows.len: start = sourceRows.len
if plan.limitCount < 0:
# OFFSET without LIMIT — return the remainder.
return sourceRows[start ..< sourceRows.len]
if plan.limitCount == 0:
return @[]
var endIdx = start + int(plan.limitCount)
if endIdx > sourceRows.len:
endIdx = sourceRows.len
if endIdx < start:
endIdx = start
return sourceRows[start..<endIdx]
of irpkGroupBy:
+8 -6
View File
@@ -11,20 +11,22 @@ import lower
# Row-Level Security
# ----------------------------------------------------------------------
proc hasPrivilege*(ctx: ExecutionContext, tableName, command: string): bool =
if ctx.currentUser.len == 0: return true
let user = ctx.users.getOrDefault(ctx.currentUser)
proc hasPrivilegeFor*(ctx: ExecutionContext, username, tableName, command: string): bool =
## Privilege check for an explicit username (does not mutate ctx.currentUser).
if username.len == 0: return true
let user = ctx.users.getOrDefault(username)
if user.isSuperuser: return true
# Check table-level policies for user or PUBLIC
# For now: if no policies exist, allow everything (backward compatible)
if tableName notin ctx.policies: return true
let policies = ctx.policies[tableName]
# If RLS is enabled (policies exist), check if user matches any policy
for pol in policies:
if pol.command == "ALL" or pol.command == command:
return true
return false
proc hasPrivilege*(ctx: ExecutionContext, tableName, command: string): bool =
if ctx.currentUser.len == 0: return true
hasPrivilegeFor(ctx, ctx.currentUser, tableName, command)
proc passesPolicy*(ctx: ExecutionContext, tableName, command: string, row: Row): bool =
if ctx.currentUser.len == 0: return true
let user = ctx.users.getOrDefault(ctx.currentUser)
+58
View File
@@ -177,6 +177,64 @@ proc computeWindowValues*(rows: seq[Row], expr: IRExpr, ctx: ExecutionContext =
for pos, rowIdx in sortedIdxs:
let (_, fEnd) = resolveFrameBounds(pos, sortedIdxs.len, frameStart, frameEnd)
result[rowIdx] = valueToString(evalExpr(expr.wfArgs[0], rows[sortedIdxs[fEnd]], ctx))
of "sum", "avg", "count", "min", "max":
let countAll = wfName == "count" and
(expr.wfArgs.len == 0 or expr.wfArgs[0].kind == irekStar)
for pos, rowIdx in sortedIdxs:
let (fStart, fEnd) = resolveFrameBounds(pos, sortedIdxs.len, frameStart, frameEnd)
if wfName == "count" and countAll:
result[rowIdx] = $(fEnd - fStart + 1)
continue
if wfName == "count":
var cnt = 0
if expr.wfArgs.len > 0:
for i in fStart .. fEnd:
let s = valueToString(evalExpr(expr.wfArgs[0], rows[sortedIdxs[i]], ctx))
if not isNull(s) and s.len > 0:
inc cnt
result[rowIdx] = $cnt
continue
if expr.wfArgs.len == 0:
result[rowIdx] = "\\N"
continue
var sum = 0.0
var cnt = 0
var minF = 0.0
var maxF = 0.0
var minS = ""
var maxS = ""
var allNumeric = true
for i in fStart .. fEnd:
let s = valueToString(evalExpr(expr.wfArgs[0], rows[sortedIdxs[i]], ctx))
if isNull(s) or s.len == 0: continue
inc cnt
if cnt == 1 or s < minS: minS = s
if cnt == 1 or s > maxS: maxS = s
try:
let f = parseFloat(s)
sum += f
if cnt == 1:
minF = f
maxF = f
else:
if f < minF: minF = f
if f > maxF: maxF = f
except CatchableError:
allNumeric = false
if cnt == 0:
result[rowIdx] = "\\N"
else:
case wfName
of "sum":
result[rowIdx] = if allNumeric: $sum else: "\\N"
of "avg":
result[rowIdx] = if allNumeric: $(sum / float(cnt)) else: "\\N"
of "min":
result[rowIdx] = if allNumeric: $minF else: minS
of "max":
result[rowIdx] = if allNumeric: $maxF else: maxS
else:
result[rowIdx] = "\\N"
else:
# Unknown window function — fill with null
for rowIdx in sortedIdxs: