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
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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user