chore: Nim review, fix and security rounds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 63ecfdfec760245b65a2e8f435c6a151634674a2 parent e349a8b
8 files changed, +595 −45
  • builders/reviews/nim.1.md +7 −0
  • builders/reviews/nim.2.md +3 −0
  • builders/reviews/nim.3.md +1 −0
  • results/haskell.json +160 −15
  • results/nim.json +104 −0
  • results/ocaml.json +146 −1
  • results/spinel.json +14 −14
  • results/swift.json +160 −15
diff --git a/builders/reviews/nim.1.md b/builders/reviews/nim.1.md
new file mode 100644
index 0000000..65479ff
--- /dev/null
+++ b/builders/reviews/nim.1.md
@@ -0,0 +1,7 @@
+1. [security] `apps/nim/src/app.nim:191` — The HTTP server’s body limit applies only to `Content-Length`. Its chunked parser passes attacker-supplied chunk sizes directly to `recv`, which allocates that size before receiving the data, and never limits the accumulated body. A single chunked POST can exhaust memory and terminate the shared instance before the application’s handler runs. Enforce limits before allocating or buffering chunks.
+
+2. [correctness] `apps/nim/src/app.nim:112` — Both add and edit (line 124) use ASCII-only `strutils.strip()`. Unicode whitespace such as U+00A0 remains: submitting only nonbreaking spaces creates an apparently blank todo, and editing to those spaces fails to delete it. Trim Unicode whitespace consistently with TodoMVC.
+
+3. [correctness] `apps/nim/static/app.js:30` — Every completed request removes the entire list, including any edit currently in progress. Toggle one item, then begin editing another while the response is pending: when it arrives, the edit field disappears and unsaved text is lost. Preserve active edits across unrelated responses.
+
+4. [spec] `apps/nim/Makefile:56` — `clean-deps` deletes Nim caches but leaves Zig’s compiled library cache outside the app. The arm64 compiler wrapper uses Zig’s default global cache (`~/.cache/zig`), so the next build can reuse compiled musl and compiler runtime libraries. Configure Zig’s caches under this app’s `.cache/` and remove their compiled artifacts in `clean-deps`, as the brief requires.
\ No newline at end of file
diff --git a/builders/reviews/nim.2.md b/builders/reviews/nim.2.md
new file mode 100644
index 0000000..e5b72e6
--- /dev/null
+++ b/builders/reviews/nim.2.md
@@ -0,0 +1,3 @@
+1. [security] `apps/nim/src/app.nim:57` — Titles reach `unicode.strip` without UTF-8 validation. A POST to `/todos` with `title=%80` triggers an out-of-bounds `IndexDefect` in Nim’s Unicode implementation. The handler catches only `CatchableError`, so the defect escapes through `asyncCheck` and terminates the shared server, interrupting every visitor. Validate decoded titles before trimming.
+
+2. [correctness] `apps/nim/src/store.nim:181` — `$column_text(st, 1)` reads a NUL-terminated string, although insertion preserves the full byte length. Posting `title=before%00after` stores the complete title but retrieves only `before`; subsequently saving the displayed title loses the suffix. Read SQLite text using `column_bytes` so embedded U+0000 does not truncate it.
\ No newline at end of file
diff --git a/builders/reviews/nim.3.md b/builders/reviews/nim.3.md
new file mode 100644
index 0000000..6849639
--- /dev/null
+++ b/builders/reviews/nim.3.md
@@ -0,0 +1 @@
+1. [correctness] `apps/nim/src/store.nim:181` — `$column_text(st, 1)` reads a NUL-terminated C string, although titles are stored with an explicit byte length. Posting `title=before%00after` therefore displays only `before`; saving that edit permanently loses the suffix. Read the value using `column_bytes` so accepted Unicode titles survive retrieval intact.
\ No newline at end of file
diff --git a/results/haskell.json b/results/haskell.json
index ac1b87b..b0768ec 100644
--- a/results/haskell.json
+++ b/results/haskell.json
@@ -17,26 +17,171 @@
"Skipped": 3
},
"TTFBEmpty": {
- "N": 0,
- "P50": 0,
- "P95": 0,
- "P99": 0,
- "Min": 0,
- "Max": 0
+ "N": 30,
+ "P50": 94.894,
+ "P95": 102.998,
+ "P99": 109.047,
+ "Min": 83.41,
+ "Max": 109.047
},
"TTFB50": {
- "N": 0,
- "P50": 0,
- "P95": 0,
- "P99": 0,
- "Min": 0,
- "Max": 0
+ "N": 30,
+ "P50": 93.402,
+ "P95": 104.227,
+ "P99": 106.57,
+ "Min": 85.989,
+ "Max": 106.57
},
"Wake": null,
- "Load": null,
+ "Load": [
+ {
+ "Scenario": "read",
+ "Concurrency": 1,
+ "RPS": 26.69685811884951,
+ "P50Ms": 36.262171,
+ "P95Ms": 44.0769,
+ "P99Ms": 111.19597300000001,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 2,
+ "RPS": 52.690018903374124,
+ "P50Ms": 37.28053499999999,
+ "P95Ms": 43.251211999999995,
+ "P99Ms": 50.474092999999996,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 4,
+ "RPS": 102.98877191691257,
+ "P50Ms": 38.027154,
+ "P95Ms": 47.025681,
+ "P99Ms": 57.853970999999994,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 8,
+ "RPS": 195.1703423636662,
+ "P50Ms": 38.720434,
+ "P95Ms": 51.520193,
+ "P99Ms": 115.521567,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 16,
+ "RPS": 339.65757221781854,
+ "P50Ms": 43.630158,
+ "P95Ms": 67.872754,
+ "P99Ms": 107.755159,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 32,
+ "RPS": 369.828732782758,
+ "P50Ms": 83.000771,
+ "P95Ms": 114.744872,
+ "P99Ms": 189.50821000000002,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 64,
+ "RPS": 360.8407627205243,
+ "P50Ms": 168.370403,
+ "P95Ms": 213.026833,
+ "P99Ms": 447.776088,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 1,
+ "RPS": 30.497864176625768,
+ "P50Ms": 31.611998000000003,
+ "P95Ms": 37.714902,
+ "P99Ms": 44.652508,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 2,
+ "RPS": 158.76927135066884,
+ "P50Ms": 11.357509,
+ "P95Ms": 19.434861,
+ "P99Ms": 30.646698999999998,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 4,
+ "RPS": 289.76146111717236,
+ "P50Ms": 13.038028,
+ "P95Ms": 18.335882,
+ "P99Ms": 28.368924,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 8,
+ "RPS": 556.813397197575,
+ "P50Ms": 14.036183000000001,
+ "P95Ms": 19.3087,
+ "P99Ms": 26.480953000000003,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 16,
+ "RPS": 1043.2346820426112,
+ "P50Ms": 14.757032,
+ "P95Ms": 21.686774999999997,
+ "P99Ms": 31.180517000000002,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 32,
+ "RPS": 1699.3133769785475,
+ "P50Ms": 16.336224,
+ "P95Ms": 35.68449,
+ "P99Ms": 51.337275,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 64,
+ "RPS": 2012.416983517745,
+ "P50Ms": 28.678796,
+ "P95Ms": 56.738705,
+ "P99Ms": 94.237454,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 128,
+ "RPS": 2038.995130732286,
+ "P50Ms": 59.185701,
+ "P95Ms": 109.540237,
+ "P99Ms": 157.091061,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 256,
+ "RPS": 2089.620372460251,
+ "P50Ms": 114.763561,
+ "P95Ms": 207.5359,
+ "P99Ms": 285.86652499999997,
+ "ErrorRate": 0
+ }
+ ],
"Weight": {
- "HTMLBytes": 0,
- "JSBytes": 0
+ "HTMLBytes": 36847,
+ "JSBytes": 4183
},
"Code": {
"Server": 0,
diff --git a/results/nim.json b/results/nim.json
new file mode 100644
index 0000000..343d512
--- /dev/null
+++ b/results/nim.json
@@ -0,0 +1,104 @@
+{
+ "Slug": "nim",
+ "Language": "Nim",
+ "Status": "LIVE",
+ "URL": "https://todo-nim.bfj.oncarlos.com",
+ "Version": "290dbb4",
+ "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": 57252,
+ "CacheRead": 6685372,
+ "CacheWrite": 131701,
+ "Turns": 73,
+ "Seconds": 847.827,
+ "CostUSD": 3.536282399999999
+ },
+ "TokensFix": {
+ "Input": 34,
+ "Output": 12178,
+ "CacheRead": 2592825,
+ "CacheWrite": 19773,
+ "Turns": 17,
+ "Seconds": 275.995,
+ "CostUSD": 0.9204450000000004
+ },
+ "TokensSecurity": {
+ "Input": 14,
+ "Output": 4228,
+ "CacheRead": 1165905,
+ "CacheWrite": 8706,
+ "Turns": 7,
+ "Seconds": 154.869,
+ "CostUSD": 0.3874449999999987
+ },
+ "TokensReview": {
+ "Input": 1368520,
+ "Output": 6277,
+ "CacheRead": 1217664,
+ "CacheWrite": 0,
+ "Turns": 3,
+ "Seconds": 0,
+ "CostUSD": 0
+ },
+ "Readability": {
+ "Naming": 4,
+ "Structure": 4,
+ "Traceability": 3,
+ "Errors": 2,
+ "Comments": 5,
+ "Overall": 6,
+ "Notes": "Only static/app.js was supplied; no Nim server source, routes, templates or database code were included, so server-side traceability could not be judged and these scores cover the client only. Strongest: comments explain intent precisely (fragment protocol, serial queue, preserving an in-progress edit across re-renders, the rendering flag guarding focusout), and names like post, render, finishEditing, itemOf read cleanly. Weakest: error handling. Any non-OK response triggers a full reload under the assumption the item vanished, hiding server errors; a fetch rejection is caught only by the queue's then(run, run) and otherwise surfaces as an unhandled rejection. Minor: render() assumes the fragment always contains .main and .footer."
+ },
+ "ReviewFindings": [
+ "[correctness] `apps/nim/src/store.nim:181` — `$column_text(st, 1)` reads a NUL-terminated C string, although titles are stored with an explicit byte length. Posting `title=before%00after` therefore displays only `before`; saving that edit permanently loses the suffix. Read the value using `column_bytes` so accepted Unicode titles survive retrieval intact."
+ ],
+ "Notes": null
+}
diff --git a/results/ocaml.json b/results/ocaml.json
index 45e5ba4..cfc8b7c 100644
--- a/results/ocaml.json
+++ b/results/ocaml.json
@@ -33,7 +33,152 @@
"Max": 113.121
},
"Wake": null,
- "Load": null,
+ "Load": [
+ {
+ "Scenario": "read",
+ "Concurrency": 1,
+ "RPS": 26.097771440832684,
+ "P50Ms": 38.226589,
+ "P95Ms": 44.264452000000006,
+ "P99Ms": 50.147892999999996,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 2,
+ "RPS": 56.09235238550346,
+ "P50Ms": 35.695487,
+ "P95Ms": 38.983720999999996,
+ "P99Ms": 44.003782,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 4,
+ "RPS": 109.07833426131818,
+ "P50Ms": 35.775185,
+ "P95Ms": 41.810899,
+ "P99Ms": 49.892291,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 8,
+ "RPS": 215.36901793916604,
+ "P50Ms": 36.418364000000004,
+ "P95Ms": 43.308951,
+ "P99Ms": 52.758119,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 16,
+ "RPS": 406.71250576884023,
+ "P50Ms": 38.005461999999994,
+ "P95Ms": 46.911025,
+ "P99Ms": 59.219037,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 32,
+ "RPS": 867.0518112211872,
+ "P50Ms": 32.428450999999995,
+ "P95Ms": 68.15821,
+ "P99Ms": 111.648028,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 64,
+ "RPS": 909.8830326254017,
+ "P50Ms": 67.644181,
+ "P95Ms": 113.581701,
+ "P99Ms": 149.138608,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 128,
+ "RPS": 916.0918394238943,
+ "P50Ms": 135.63406799999998,
+ "P95Ms": 231.30990300000002,
+ "P99Ms": 287.81981099999996,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 1,
+ "RPS": 99.18566547933501,
+ "P50Ms": 9.530047999999999,
+ "P95Ms": 14.040671,
+ "P99Ms": 18.262036,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 2,
+ "RPS": 169.48285145391733,
+ "P50Ms": 10.978765999999998,
+ "P95Ms": 16.772228,
+ "P99Ms": 21.389759,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 4,
+ "RPS": 308.7607119284671,
+ "P50Ms": 12.235388,
+ "P95Ms": 17.818839999999998,
+ "P99Ms": 21.339118,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 8,
+ "RPS": 228.05756204540134,
+ "P50Ms": 33.938655,
+ "P95Ms": 39.584764,
+ "P99Ms": 52.930212,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 16,
+ "RPS": 447.1144834978313,
+ "P50Ms": 34.847996,
+ "P95Ms": 41.056208,
+ "P99Ms": 50.974013,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 32,
+ "RPS": 857.8113253205815,
+ "P50Ms": 35.817308,
+ "P95Ms": 47.484265,
+ "P99Ms": 67.73130400000001,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 64,
+ "RPS": 895.578060352702,
+ "P50Ms": 54.791554,
+ "P95Ms": 155.255649,
+ "P99Ms": 209.875608,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 128,
+ "RPS": 1359.6478507388142,
+ "P50Ms": 59.477766,
+ "P95Ms": 272.794435,
+ "P99Ms": 369.32636,
+ "ErrorRate": 0.4022860535886588
+ }
+ ],
"Weight": {
"HTMLBytes": 42587,
"JSBytes": 3685
diff --git a/results/spinel.json b/results/spinel.json
index 3f3a518..3d3ef29 100644
--- a/results/spinel.json
+++ b/results/spinel.json
@@ -17,26 +17,26 @@
"Skipped": 3
},
"TTFBEmpty": {
- "N": 0,
- "P50": 0,
- "P95": 0,
- "P99": 0,
- "Min": 0,
- "Max": 0
+ "N": 30,
+ "P50": 50.27,
+ "P95": 55.968,
+ "P99": 56.094,
+ "Min": 46.013,
+ "Max": 56.094
},
"TTFB50": {
- "N": 0,
- "P50": 0,
- "P95": 0,
- "P99": 0,
- "Min": 0,
- "Max": 0
+ "N": 30,
+ "P50": 52.578,
+ "P95": 59.272,
+ "P99": 66.029,
+ "Min": 47.702,
+ "Max": 66.029
},
"Wake": null,
"Load": null,
"Weight": {
- "HTMLBytes": 0,
- "JSBytes": 0
+ "HTMLBytes": 41526,
+ "JSBytes": 2655
},
"Code": {
"Server": 0,
diff --git a/results/swift.json b/results/swift.json
index 6b84f71..31886c4 100644
--- a/results/swift.json
+++ b/results/swift.json
@@ -17,26 +17,171 @@
"Skipped": 3
},
"TTFBEmpty": {
- "N": 0,
- "P50": 0,
- "P95": 0,
- "P99": 0,
- "Min": 0,
- "Max": 0
+ "N": 30,
+ "P50": 98.354,
+ "P95": 105.084,
+ "P99": 105.193,
+ "Min": 84.397,
+ "Max": 105.193
},
"TTFB50": {
- "N": 0,
- "P50": 0,
- "P95": 0,
- "P99": 0,
- "Min": 0,
- "Max": 0
+ "N": 30,
+ "P50": 94.061,
+ "P95": 102.592,
+ "P99": 108.775,
+ "Min": 87.038,
+ "Max": 108.775
},
"Wake": null,
- "Load": null,
+ "Load": [
+ {
+ "Scenario": "read",
+ "Concurrency": 1,
+ "RPS": 27.296510045652877,
+ "P50Ms": 36.656496,
+ "P95Ms": 42.049067,
+ "P99Ms": 52.302723,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 2,
+ "RPS": 52.39227567581642,
+ "P50Ms": 38.237572,
+ "P95Ms": 42.074146,
+ "P99Ms": 46.992537,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 4,
+ "RPS": 104.2877625610778,
+ "P50Ms": 37.759178,
+ "P95Ms": 43.572641000000004,
+ "P99Ms": 55.681293,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 8,
+ "RPS": 204.47678491225412,
+ "P50Ms": 38.14147,
+ "P95Ms": 46.680825,
+ "P99Ms": 62.819841000000004,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 16,
+ "RPS": 379.7445561555676,
+ "P50Ms": 40.535737000000005,
+ "P95Ms": 53.863342,
+ "P99Ms": 69.03654900000001,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 32,
+ "RPS": 539.9130204983195,
+ "P50Ms": 56.092084,
+ "P95Ms": 94.803026,
+ "P99Ms": 142.277387,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 64,
+ "RPS": 445.1102596291372,
+ "P50Ms": 135.003106,
+ "P95Ms": 248.05947,
+ "P99Ms": 311.61767899999995,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "read",
+ "Concurrency": 128,
+ "RPS": 488.7957721223273,
+ "P50Ms": 250.78503800000001,
+ "P95Ms": 463.785598,
+ "P99Ms": 592.699952,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 1,
+ "RPS": 88.76384082449914,
+ "P50Ms": 10.504337000000001,
+ "P95Ms": 15.692309000000002,
+ "P99Ms": 24.551641999999998,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 2,
+ "RPS": 165.16492357833332,
+ "P50Ms": 11.407713999999999,
+ "P95Ms": 17.554529,
+ "P99Ms": 21.497001,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 4,
+ "RPS": 287.77742066701927,
+ "P50Ms": 13.158547,
+ "P95Ms": 19.177643,
+ "P99Ms": 35.318489,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 8,
+ "RPS": 508.0341937720431,
+ "P50Ms": 15.322446,
+ "P95Ms": 21.420319,
+ "P99Ms": 27.503838,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 16,
+ "RPS": 854.9581791952519,
+ "P50Ms": 16.998088000000003,
+ "P95Ms": 30.234149,
+ "P99Ms": 44.735781,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 32,
+ "RPS": 1241.6565248502343,
+ "P50Ms": 23.726373,
+ "P95Ms": 43.463809999999995,
+ "P99Ms": 54.458228999999996,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 64,
+ "RPS": 836.0748744661419,
+ "P50Ms": 63.149631,
+ "P95Ms": 146.399683,
+ "P99Ms": 237.186814,
+ "ErrorRate": 0
+ },
+ {
+ "Scenario": "toggle",
+ "Concurrency": 128,
+ "RPS": 2847.0211770372075,
+ "P50Ms": 24.255105999999998,
+ "P95Ms": 97.30545000000001,
+ "P99Ms": 216.814023,
+ "ErrorRate": 0.5562923250564334
+ }
+ ],
"Weight": {
- "HTMLBytes": 0,
- "JSBytes": 0
+ "HTMLBytes": 41189,
+ "JSBytes": 3110
},
"Code": {
"Server": 0,