A compression model is a prediction model. Every byte it can predict gets a short code. Every byte it can’t gets a long one. GRAFT trains that model on nothing but your repo’s own source, then measures how long the codes get when a PR changes a file.
The metric is bits-per-byte. Low BPB means the model isn’t surprised: the new bytes look like bytes it already learned to predict. High BPB means something foreign showed up. Obfuscated eval chains, base64 payloads, binary blobs stuffed into comments, minified implants dropped into build scripts all look radically different from hand-written source at the byte level, regardless of what language or framework they’re pretending to be part of.
There’s no signature database behind this. No rule engine, no labeled attack corpus. The model has one job: learn the byte distribution of this specific repo, then flag what doesn’t fit it. A repo that mixes Python, Rust, and YAML gets a baseline that reflects all three. A pure Go repo gets a Go-shaped baseline. The detector is relative to your codebase, not to a general notion of “malicious code.”
On a baseline trained on ~46 KB of Python and YAML (3000 steps, about 3 minutes on ubuntu-latest), normal source lands at 1.09 BPB mean with 0.48 std. Four attack payloads, a base64 blob, an eval obfuscation chain, a null-byte stuffed config, and minified JS, scored 8 to 26 sigma above that baseline. No overlap with clean code.
Under the hood GRAFT runs on ByteFlow Net (Deng et al., ICLR 2026), via the SUBSTRATE framework, repurposed here as a per-file anomaly scorer instead of a language model. The config is deliberately small: ~3.2M params, 512-byte windows, trainable on CPU in a CI job.
Setup is a GitHub Action. First PR after install trains and caches the baseline; every PR after that reuses it, with weekly retraining to track the repo as it evolves. GitLab, Gitea, and generic CI (Jenkins, Drone, Woodpecker, pre-push hooks) are covered through the same scoring script.
This is a surprise detector, not a semantic one. It has no model of what an eval chain does or what a backdoor accomplishes. It only knows what your repo’s bytes usually look like. That’s also its limit: an attacker who matches your file’s existing whitespace, naming, and import style closely enough can stay inside the normal range. Generated files cut the other way, scoring high while being entirely legitimate, protobuf output and minified vendored JS being the usual offenders. Both cases are configurable: .graftignore, max-files, or a looser threshold-sigma tune the tradeoff per repo.