Skip to content

Commit ab29f77

Browse files
hyperpolymathclaude
andcommitted
fix(p1): VCLTypeChecker ArgumentError + null-byte sanitisation in VCLBridge
VCLTypeChecker (P1a): - do_normalize/1: rescue ArgumentError from String.to_existing_atom/1 so unknown proof type strings degrade to :unknown instead of crashing the process. Existing "rejects unknown proof type" test now passes cleanly. VCLBridge built-in parser (P1b): - tokenize/1: reject queries containing null bytes with {:error, msg}. Null bytes in entity IDs truncate C strings silently at the Rust FFI boundary, enabling entity ID forgery via truncation. - Add 3 tests in vcl_test.exs covering: null byte in entity ID, null byte in WHERE clause, clean query accepted. STATE updated: both P1 blockers removed, mix-test-failures count corrected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5cbd874 commit ab29f77

4 files changed

Lines changed: 49 additions & 15 deletions

File tree

.machine_readable/6a2/STATE.a2ml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ phase = "Phase 5 — Tropical Consonance + KRL Integration"
1717
[current-position]
1818
milestone = "verisim-modular-experiment tropical bridge v0.2 + bellman_ford proved"
1919
last-session = "2026-04-11"
20-
last-action = "Closed bellman_ford OFFICIAL SORRY 2 — full min-plus Bellman-Ford proof in Tropical_Matrices_Full.thy. All Isabelle sorries gone (0 remaining)."
20+
last-action = "VQL→VCL rename (303 files); P1 hardening: VCLTypeChecker ArgumentError fixed, null-byte sanitisation added to VCLBridge tokenizer"
2121

2222
[tropical-bridge]
2323
status = "v0.2 — RI + RII live, RIII stub (KnotTheory.jl r3_simplify is no-op)"
@@ -42,24 +42,20 @@ benchmarks = "2 Rust files (modality_benchmarks.rs + throughput_benchmarks.rs)"
4242
total-new-tests = "43 new tests + 5 properties (2026-04-04)"
4343

4444
[test-status]
45-
mix-test-failures = "6 pre-existing (VCLTypeChecker binary_to_existing_atom crash, KRaft remove_server timeout)"
45+
mix-test-failures = "1 pre-existing (KRaft remove_server timeout)"
4646
new-test-failures = "0"
4747
cargo-bench-compile = "PASS (throughput_benchmarks.rs — no errors, no warnings in bench file)"
4848
gleam-smoke = "Written; requires compiled lith_nif.so to run connection/lifecycle tests"
4949

5050
[blockers]
51-
- "VCLTypeChecker calls :erlang.binary_to_existing_atom for unknown proof types → ArgumentError (P1 hardening gap)"
52-
- "VCL parser does not strip null bytes from entity IDs (C-string truncation risk at FFI, P1)"
5351
- "Integration test (tests/integration_test.rs) uses old API — does not compile (pre-existing)"
5452
- "modality_benchmarks.rs uses old API — does not compile (pre-existing)"
5553

5654
[route-to-mvp]
5755
next = "Add Tropical_Determinants.thy + ProofOptimalAssignment VCL clause"
58-
then = "Fix VCLTypeChecker binary_to_existing_atom crash (P1 hardening)"
59-
then = "Fix null-byte entity ID sanitisation in VCL parser"
6056
then = "Update integration_test.rs to current API"
57+
then = "Update modality_benchmarks.rs to current API"
6158

6259
[critical-next-actions]
6360
1 = "Tropical_Determinants.thy new file — permanent = optimal assignment"
64-
2 = "Fix VCLTypeChecker to use String.to_existing_atom with rescue guard (P1)"
65-
3 = "Add null-byte sanitisation in VCLBridge built-in parser (P1)"
61+
2 = "Update integration_test.rs to current API"

verisimdb/elixir-orchestration/lib/verisim/query/vcl_bridge.ex

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -347,13 +347,19 @@ defmodule VeriSim.Query.VCLBridge do
347347
end
348348

349349
defp tokenize(input) do
350-
# Simple whitespace-aware tokenizer
351-
tokens =
352-
input
353-
|> String.replace(~r/\s+/, " ")
354-
|> String.split(" ", trim: true)
350+
# Reject null bytes before tokenising: they truncate C strings silently at the
351+
# Rust FFI boundary, allowing entity IDs to be forged via truncation.
352+
if String.contains?(input, "\0") do
353+
{:error, "VCL query contains null bytes"}
354+
else
355+
# Simple whitespace-aware tokenizer
356+
tokens =
357+
input
358+
|> String.replace(~r/\s+/, " ")
359+
|> String.split(" ", trim: true)
355360

356-
{:ok, tokens}
361+
{:ok, tokens}
362+
end
357363
end
358364

359365
defp parse_tokens(tokens) do

verisimdb/elixir-orchestration/lib/verisim/query/vcl_type_checker.ex

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,16 @@ defmodule VeriSim.Query.VCLTypeChecker do
308308
defp normalize_proof_type(_), do: :unknown
309309

310310
defp do_normalize(str) when is_binary(str) do
311-
atom = str |> String.downcase() |> String.to_existing_atom()
311+
# String.to_existing_atom/1 raises ArgumentError for atoms not yet interned.
312+
# Rescue it so unknown proof type names degrade to :unknown instead of crashing.
313+
atom =
314+
str
315+
|> String.downcase()
316+
|> String.to_existing_atom()
317+
312318
if atom in @known_proof_types, do: atom, else: :unknown
319+
rescue
320+
ArgumentError -> :unknown
313321
end
314322
defp do_normalize(atom) when is_atom(atom) do
315323
if atom in @known_proof_types, do: atom, else: :unknown

verisimdb/elixir-orchestration/test/verisim/query/vcl_test.exs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,28 @@ defmodule VeriSim.Query.VCLTest do
246246
assert is_map(ast)
247247
end
248248
end
249+
250+
# ===========================================================================
251+
# Security: null-byte sanitisation (P1 hardening)
252+
# ===========================================================================
253+
254+
describe "null-byte sanitisation" do
255+
test "rejects query containing a null byte" do
256+
# Null bytes in entity IDs truncate C strings at the Rust FFI boundary,
257+
# allowing forged IDs. The built-in parser must reject them.
258+
assert {:error, msg} = VCLBridge.parse("SELECT GRAPH FROM HEXAD abc\0evil")
259+
assert msg =~ "null"
260+
end
261+
262+
test "rejects query with null byte in WHERE clause" do
263+
assert {:error, msg} =
264+
VCLBridge.parse("SELECT GRAPH FROM HEXAD abc-123 WHERE GRAPH.id = 'ok\0bad'")
265+
266+
assert msg =~ "null"
267+
end
268+
269+
test "clean query without null bytes is accepted" do
270+
assert {:ok, _ast} = VCLBridge.parse("SELECT GRAPH FROM HEXAD abc-123")
271+
end
272+
end
249273
end

0 commit comments

Comments
 (0)