chore: Zig security round; results so far

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 7569dda650b41ce7138dfa38b9a362f8468a4e0c parent 28a5fc7
5 files changed, +133 −26
  • builders/reviews/ocaml.1.md +7 −0
  • builders/reviews/zig.3.md +1 −1
  • results/haskell.json +102 −0
  • results/ocaml.json +7 −7
  • results/zig.json +16 −18
diff --git a/builders/reviews/ocaml.1.md b/builders/reviews/ocaml.1.md
new file mode 100644
index 0000000..c981a53
--- /dev/null
+++ b/builders/reviews/ocaml.1.md
@@ -0,0 +1,7 @@
+1. [security] `apps/ocaml/bin/main.ml:86` — The HTTP server uses Cohttp-eio’s unbounded request buffer (`max_size:max_int`), with no header-size limit. A single request containing an enormous header line can exhaust process memory before the callback’s 1 MiB body limit applies, denying service to every visitor. Bound request-line and header parsing.
+
+2. [correctness] `apps/ocaml/static/app.js:34` — Every response removes the entire list, including any active editor. With a delayed toggle/add response, double-click another todo and start typing before that response arrives: the editor disappears and the unsaved text is lost. Preserve active edits across response rendering.
+
+3. [correctness] `apps/ocaml/src/app.ml:120` — `String.trim` does not remove Unicode whitespace such as U+00A0; the edit path at line 128 has the same problem. Submitting only non-breaking spaces creates a visually empty todo, and editing to that value does not delete it. Apply Unicode-aware trimming consistently to additions and edits.
+
+4. [spec] `apps/ocaml/Makefile:33` — `clean-deps` removes the opam switches but leaves precompiled native dependencies; the arm64 build continues linking the emulated root’s prebuilt SQLite library. Consequently, the next build does not compile every third-party library from source as the brief requires. Build SQLite in the app’s cache and remove its compiled artifacts in `clean-deps`, retaining its downloaded source.
\ No newline at end of file
diff --git a/builders/reviews/zig.3.md b/builders/reviews/zig.3.md
index ea30bf8..f6e8b4b 100644
--- a/builders/reviews/zig.3.md
+++ b/builders/reviews/zig.3.md
@@ -1 +1 @@
-1. [security] [apps/zig/src/app.zig:218](apps/zig/src/app.zig#L218): A POST to `/todos` with body `title=a%80%80%80%80` passes a five-byte slice to `std.unicode.utf8Decode`, whose unsupported-length branch is `unreachable`. In the configured ReleaseSafe build, this aborts the entire server, denying service to every visitor. Validate UTF-8 before trimming and reject malformed titles without panicking.
\ No newline at end of file
+No findings.
\ No newline at end of file
diff --git a/results/haskell.json b/results/haskell.json
new file mode 100644
index 0000000..215e823
--- /dev/null
+++ b/results/haskell.json
@@ -0,0 +1,102 @@
+{
+ "Slug": "haskell",
+ "Language": "Haskell",
+ "Status": "LIVE",
+ "URL": "https://todo-haskell.bfj.oncarlos.com",
+ "Version": "7881a7e",
+ "Stack": "",
+ "Source": "",
+ "BehaviourJSOn": {
+ "Passed": 35,
+ "Failed": 0,
+ "Skipped": 0
+ },
+ "BehaviourJSOff": {
+ "Passed": 32,
+ "Failed": 0,
+ "Skipped": 3
+ },
+ "TTFBEmpty": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "TTFB50": {
+ "N": 0,
+ "P50": 0,
+ "P95": 0,
+ "P99": 0,
+ "Min": 0,
+ "Max": 0
+ },
+ "Wake": null,
+ "Load": null,
+ "Weight": {
+ "HTMLBytes": 0,
+ "JSBytes": 0
+ },
+ "Code": {
+ "Server": 0,
+ "Templates": 0,
+ "JS": 0,
+ "Tests": 0,
+ "Deps": 0
+ },
+ "Build": {
+ "ColdSeconds": 0,
+ "WarmSeconds": 0,
+ "EditSeconds": 0,
+ "Bytes": 0,
+ "Host": ""
+ },
+ "TokensBuild": {
+ "Input": 140,
+ "Output": 79279,
+ "CacheRead": 7860561,
+ "CacheWrite": 161967,
+ "Turns": 73,
+ "Seconds": 3280.837,
+ "CostUSD": 4.4539882
+ },
+ "TokensFix": {
+ "Input": 0,
+ "Output": 0,
+ "CacheRead": 0,
+ "CacheWrite": 0,
+ "Turns": 0,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "TokensSecurity": {
+ "Input": 0,
+ "Output": 0,
+ "CacheRead": 0,
+ "CacheWrite": 0,
+ "Turns": 0,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "TokensReview": {
+ "Input": 0,
+ "Output": 0,
+ "CacheRead": 0,
+ "CacheWrite": 0,
+ "Turns": 0,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "Readability": {
+ "Naming": 0,
+ "Structure": 0,
+ "Traceability": 0,
+ "Errors": 0,
+ "Comments": 0,
+ "Overall": 0,
+ "Notes": ""
+ },
+ "ReviewFindings": null,
+ "Notes": null
+}
diff --git a/results/ocaml.json b/results/ocaml.json
index 67c94a8..72f20a4 100644
--- a/results/ocaml.json
+++ b/results/ocaml.json
@@ -1,20 +1,20 @@
{
"Slug": "ocaml",
- "Language": "",
- "Status": "",
- "URL": "",
- "Version": "",
+ "Language": "OCaml",
+ "Status": "LIVE",
+ "URL": "https://todo-ocaml.bfj.oncarlos.com",
+ "Version": "b927326",
"Stack": "",
"Source": "",
"BehaviourJSOn": {
- "Passed": 0,
+ "Passed": 35,
"Failed": 0,
"Skipped": 0
},
"BehaviourJSOff": {
- "Passed": 0,
+ "Passed": 32,
"Failed": 0,
- "Skipped": 0
+ "Skipped": 3
},
"TTFBEmpty": {
"N": 0,
diff --git a/results/zig.json b/results/zig.json
index 89cd706..661865a 100644
--- a/results/zig.json
+++ b/results/zig.json
@@ -3,7 +3,7 @@
"Language": "Zig",
"Status": "LIVE",
"URL": "https://todo-zig.bfj.oncarlos.com",
- "Version": "576c3b8",
+ "Version": "e5042a9",
"Stack": "",
"Source": "",
"BehaviourJSOn": {
@@ -71,34 +71,32 @@
"CostUSD": 0.7475604000000002
},
"TokensSecurity": {
- "Input": 8,
- "Output": 1030,
- "CacheRead": 749296,
- "CacheWrite": 3731,
- "Turns": 4,
- "Seconds": 55.56,
- "CostUSD": 0.20033920000000016
+ "Input": 16,
+ "Output": 4078,
+ "CacheRead": 1544666,
+ "CacheWrite": 8418,
+ "Turns": 8,
+ "Seconds": 137.2,
+ "CostUSD": 0.45790120000000023
},
"TokensReview": {
- "Input": 899608,
- "Output": 3958,
- "CacheRead": 760064,
+ "Input": 834801,
+ "Output": 3599,
+ "CacheRead": 691712,
"CacheWrite": 0,
"Turns": 3,
"Seconds": 0,
"CostUSD": 0
},
"Readability": {
- "Naming": 4,
- "Structure": 4,
+ "Naming": 5,
+ "Structure": 5,
"Traceability": 5,
"Errors": 4,
"Comments": 5,
- "Overall": 8,
- "Notes": "Strongest: traceability. A toggle is easy to follow from app.js's form submit through handle/post, itemAction, Store.toggle's single UPDATE, and back via view.render, with clear module boundaries (main/app/db/view) and good why-comments on header lifetime, keep-alive, token hashing, and signal-safe shutdown. Weakest: a few small opacities. post() is one long if-chain mixing routing and logic; readerExpectContinue errors are remapped to WriteFailed with no explanation; receiveHead failures and the ROLLBACK/deleteFile errors are silently dropped; sqlite errors collapse to a single error.Sqlite with logging only at some layers; the health_body comment references an unexplained 'make bump'; and the 'Each of these answers false' doc sits on toggle alone but means three functions."
+ "Overall": 9,
+ "Notes": "Strongest: a request is trivially traceable (app.js form → handle → post → itemAction → Store.toggle → page/303), helped by the Response/Visitor/Form structs and tight module split. Comments earn their place: why headers are copied before reading the body, why keep-alive drops after an oversized body, why a single mutexed SQLite connection. Tests double as documentation. Weakest: errors in db.zig collapse to one opaque error.Sqlite with logging as the only detail; readerExpectContinue's failure is remapped to WriteFailed, hiding the cause; post() mixes routing and handling in one long if-chain, and get() parses the query inline. Minor: static() and run() names are terse."
},
- "ReviewFindings": [
- "[security] [apps/zig/src/app.zig:218](apps/zig/src/app.zig#L218): A POST to `/todos` with body `title=a%80%80%80%80` passes a five-byte slice to `std.unicode.utf8Decode`, whose unsupported-length branch is `unreachable`. In the configured ReleaseSafe build, this aborts the entire server, denying service to every visitor. Validate UTF-8 before trimming and reject malformed titles without panicking."
- ],
+ "ReviewFindings": null,
"Notes": null
}