From 206004fbcce5900c21ba39dcf58101f1ef45476f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Jul 2026 12:20:40 +0000 Subject: [PATCH] agent: comrak diff-fuzzer for the phase2-comrak-010 audit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EcKPWQ3MQD4NdEHwar96u2 --- results/ledger.sqlite | Bin 122880 -> 126976 bytes scripts/diff-fuzz-comrak.py | 90 ++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 scripts/diff-fuzz-comrak.py diff --git a/results/ledger.sqlite b/results/ledger.sqlite index 6280a9ffd7219a4ee60d7536a7b57a0e12d298aa..d0da3c1cccc992e8679e0621c3aba2707b7913e4 100644 GIT binary patch delta 3529 zcma)8O^jPt6&@$4CXG6ive){HLr+<9%u~So1Q>VA-T;|k!SATJO|J=-n!)ZFRZ|1{0 z&(Pa5!_4B*{dRF~c(-u$&V&5)zS+6i52g><4#_6n{HA^TdI-xnqmvxz`@Qy_tFI`Ry~e$wB9cx%-JCDlMW^Ug^2O!6ZC|#Xx?Qc8o#A(GE-svJTqY69Ku|12=;_UXL`?lq zyE2MMURN>Qzxk8lm5*|tFBZzn^RymXR9dcBm6}Z#>|RK#R4y!6=7&G}=tPCi?uCgu zh)cRfZC^EAYK2l#U&I~CZm1yMFuyB>YI)uo-rb%X{`$!$E?SfXz6!bJM=gj^?NAPv(!X;Cj zT`4R%BfF6~c7ghS5>ecdk0_F#{;xtAyPaCUK3Qu)MWHAZ(~>RPMQEcZ!?xVT!*=FZ z43hasc}PRY!l&2ma-llUNUw*(bGJS>V8$qvUgBa2@(Pk(Q--`i_`Y^Utns5SLlG+- zL^+FkB9@`T%Pk7zW*zh^EOC7iMfoKP^(JLMCa9vFqfHUfriwcouILL_#apBUK5sxB z)$aKCk4YzSKZ&WCv|7l3^YRpbl|f9MlvfggM?L9wM4+PH3I%$6zKCPl>&K)Z$zUr@ zhhhOdVz4k)Yk^+a*;=P4bQgPKpn^geq1I3eZ;NE$MI2+S>3W?-oxS*t^~)QLuRZtN z#YO`NY<85}p{T3+d!Y?rp6COcMcp@8nF^XHh+$3JMnx34K-0q+DJcWIlYS(r39!2s zMVgwjrO^zgop|d`e{BlCn<@%`D6B9N2OcW$)HUTLh7)cT^x`!cf*1+vZQ+I*BZWl*@5#3mXz;b}kIuiHIsciJv^iYJ8(Y~70NUtX zFLQFh^}=i*lZmj%*}plzh&k-b>De{*OrI^4wE1*u_T7~KEVN_Xb*5z45eea~Y3kA5aIXP+t1Wrz@C3 z<#7AoCt>19R3x#^3)hvfK>*%X&~AZVRZ$lv4V}`fjm92al!e#jbt}&lrlt|FYe2+i z7VwUou`oPTEm$hd4GIHA;<^dNs`LdqQN7qQwM4s(&<3t(6$Kfxq*K@8E+bDyeJF3I zyOfVnVBy!djh*CqV>BA9XO54)TFYb?N>0_P(pmnnmuPM65Pkd))8f$RA3w~T82;uj zPYqI=U8VDUOR3i}pt8A@LuYq3EiO_6R%5a?OP@9-)Q26gqxxHQ0|omfcVJ6l%CJ(| zY1vHSnG?axvn=i(9)LmL4;ToY00{ooqNoR9jN1-MfV=UI2;Kx;1I&qFJ5!2W9i@XT zbzsdmv%W7`bM)M$bwdiLw=)>n4&9K{_0%{QdVHzbl;GUVn~4dPkK!#KBY!+ZFc{2T zW+I#!Ug;!*TkIu~&afPL8X`_d75w=jdd=AXlwmwZ>ISoLhOwFu$>aqKYj2G1{wj0( zVAU==ex#i^9- za-~+PnZGJ8t}Hv%;_^~CeN?Lr|8(Q{Y^72uRcn=E37ehL@Riq&Qngwv)vC+IVyRqo sN|s%)9lKPkRGf0zSzanF-&y;??=nYHhn3#iD8~^|S9+4nF0UWVlq#pwq4eFBzAS1IZAgB$IkQkG}E)%o; zEe8S&7!KV45A_b|58V%#vl1}A4v|m~gWW#2-97;)kp=+;{Qw31v4QgixBb-t0}8kD BArt@r diff --git a/scripts/diff-fuzz-comrak.py b/scripts/diff-fuzz-comrak.py new file mode 100644 index 0000000..fc154f8 --- /dev/null +++ b/scripts/diff-fuzz-comrak.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Differential fuzz for comrak attempts: pristine vs candidate must +produce byte-identical HTML (stdout), stderr, and exit code on mutated +markdown. comrak is a single-threaded stdin->stdout filter, so raw byte +comparison needs no canonicalization. + + python3 scripts/diff-fuzz-comrak.py + +Seeds: windows of the pinned Pro Git corpus. Mutations focus on inline/ +block structure the parser and teardown paths exercise: emphasis, code +fences, links, HTML blocks, list markers, plus raw byte noise. +""" + +import random +import subprocess +import sys +from pathlib import Path + +ROOT = Path("/home/user/peltier.io") +BASE = ROOT / "targets/comrak/baseline/release/comrak" +CAND = ROOT / sys.argv[3] +CORPUS = ROOT / "corpora/comrak/progit.md" +WORK = Path(sys.argv[1]) +ITERS = int(sys.argv[2]) + +TOKENS = [ + b"**", b"*", b"__", b"_", b"`", b"```", b"~~~", b"~~", b"[", b"](", b")", + b"![", b"#", b"##", b">", b"- ", b"1. ", b"|", b"---", b"
", b"
", + b"", b"\\", b"&", b"", b" ", b"\t", +] + + +def mutate(data: bytes, rng: random.Random) -> bytes: + b = bytearray(data) + for _ in range(rng.randrange(1, 8)): + if not b: + b = bytearray(rng.choice(TOKENS)) + continue + op = rng.randrange(5) + pos = rng.randrange(len(b) + 1) + if op == 0: + b[pos:pos] = rng.choice(TOKENS) + elif op == 1 and len(b) > 2: + del b[pos:min(len(b), pos + rng.randrange(1, 16))] + elif op == 2 and pos < len(b): + b[pos] = rng.randrange(256) + elif op == 3: + end = min(len(b), pos + rng.randrange(1, 64)) + b[end:end] = b[pos:end] + else: + b[pos:pos] = b"\n\n" + return bytes(b) + + +def run(binary: Path, payload: bytes) -> tuple[bytes, bytes, int]: + r = subprocess.run([str(binary)], input=payload, capture_output=True, timeout=60) + return r.stdout, r.stderr, r.returncode + + +def main() -> int: + rng = random.Random(0xC0FFEE) + corpus = CORPUS.read_bytes() + # Seed windows across the whole book, varied sizes. + seeds = [ + corpus[o : o + n] + for o in range(0, len(corpus) - 65536, len(corpus) // 300) + for n in (512, 4096, 32768) + ] + WORK.mkdir(parents=True, exist_ok=True) + divergences = 0 + for i in range(ITERS): + payload = mutate(rng.choice(seeds), rng) + a = run(BASE, payload) + b = run(CAND, payload) + if a != b: + divergences += 1 + keep = WORK / f"divergence-{i}" + keep.mkdir() + (keep / "input.md").write_bytes(payload) + (keep / "baseline.out").write_bytes(a[0] + b"\n--stderr--\n" + a[1] + f"\nrc={a[2]}".encode()) + (keep / "candidate.out").write_bytes(b[0] + b"\n--stderr--\n" + b[1] + f"\nrc={b[2]}".encode()) + print(f"DIVERGENCE at iter {i}; kept in {keep}") + if (i + 1) % 1000 == 0: + print(f"{i + 1}/{ITERS}, {divergences} divergences", flush=True) + print(f"done: {ITERS} inputs, {divergences} divergences") + return 1 if divergences else 0 + + +if __name__ == "__main__": + sys.exit(main())