carlos / todomvc-bakeoff Public
chore: Zig security round; results so far
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 files changed,
+133
−26
builders/reviews/ocaml.1.md+7 −0builders/reviews/zig.3.md+1 −1results/haskell.json+102 −0results/ocaml.json+7 −7results/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 |
| } |