SPOOKYJUICE
The project Repo ↗
Engineering teardown · build log

From E-Waste
to SpookyWrt

We bought a $99 Chinese mini-router that shipped broken — dead Wi-Fi, no security updates, and an unauthenticated root shell exposed on the LAN. We didn't trust it, so we tore it down to the silicon and rebuilt the whole software stack. It became SpookyWrt. Here's the autopsy, the root-cause analyses, and how two AI agents built it in ~22 hours.

We bought a cheap $99 router that showed up basically broken and unsafe — the Wi-Fi didn't work and anyone on the network could take it over. So we rebuilt its software from scratch into something fast, safe, and ours: SpookyWrt. Here's the story — two AIs did it in about a day.

📖 Reading as an engineer — flip the switch up top for the plain-languagefull technical version.

$99 vendor e-waste→ 4.19 kernel · no Wi-Fi · wide open→ SpookyWrt · hardened · kernel 6.x
~22h
wall-clock
54
commits
10.6k
lines
25
docs
7
CVE-class fixes
2
AI agents
the patient

A router that fought back

The Seeed LinkStar H68K is a fanless Rockchip RK3568 mini-router: quad-core Cortex-A55, 2× 2.5 GbE + 2× 1 GbE, an optional Wi-Fi 6 radio, ~32 GB eMMC — a genuinely capable little box for $99. It ships with a vendor Ubuntu 20.04 / Android 11 image.

And that image is where it all goes wrong. The hardware is fine. The software it shipped with is the junk. Here's what we found on a fresh unit:

The verdict: a fast, cheap, 4-port router with a modern SoC — running an abandoned, insecure OS on a kernel too old to drive its own radios. Half-asleep and wide open. So we replaced all of it.

the operation

Three evolutions

01 · Flash Ubuntu from a Mac — no maskrom, no Windows
The vendor only documents a Windows tool + a maskrom cable. We reverse-engineered the RKFW image format, rebuilt the loader, and booted Ubuntu from a microSD on a Mac. First win: full control, non-destructive to the factory image.
02 · Drag it into the present — 20.04 → 22.04 → 24.04
A live in-place distro upgrade across three LTS releases on the vendor kernel. Every step hit a real gotcha (firefox-snap abort, interface renames, dead DNS, the clock trap). Modern userland — but Wi-Fi was still dead, because the kernel never changed.
03 · The pivot — real OpenWrt on a mainline 6.x kernel = SpookyWrt
The insight that reframed everything: the problem was never the OS, it was the kernel version. We moved to OpenWrt SNAPSHOT (kernel 6.x). Wi-Fi came alive. The 2.5 GbE ports came alive. The hardware we paid for finally woke up — and we built a hardened, branded firmware on top: SpookyWrt.
the interesting part

Root-cause analyses

What was broken — and how we fixed it

This is the engineering meat — five non-obvious problems, each diagnosed to the actual cause, not the symptom.

RCA-1 · the thesis
SYMPTOM: no Wi-Fi on Ubuntu, on Android, even after upgrading to 24.04.
ROOT CAUSE: the MT7921 driver (mt7921e) landed in Linux kernel 5.12. The vendor ships 4.19. Userland upgrades never touch the kernel — it lives in boot.img, not apt.
FIX: a distro with a mainline kernel ≥ 5.12. OpenWrt SNAPSHOT runs 6.x → the radio just works.

Every downstream "Wi-Fi doesn't work" report traces to this one fact. It's why upgrading Ubuntu felt productive but changed nothing: we were repainting a house with a broken foundation.

RCA-2 · the black screen
SYMPTOM: flash the vendor .img to an SD with dd → black screen, no boot.
ROOT CAUSE: the "image" is an RKFW container, not a disk image, and its loader is in the LDR download-mode format. Sector 64 needs an RKNS rksd loader — write the wrong one and the bootROM stays dark.
FIX: unpack the RKFW (handling a 32-bit size-field overflow on the >4 GB payload), rebuild the loader as RKNS, write the partition layout by hand.

This is the single hardest thing in the project and the reason no one else had a clean no-maskrom SD path. It's now three scripts.

RCA-3 · the 6 GHz wall
SYMPTOM: a Wi-Fi 7 USB adapter is plugged in, driver loaded, but the 6 GHz AP never beacons.
ROOT CAUSE (two layers): (1) the mt7925u firmware blob wasn't installed — driver present, radio dead ("MCU is not ready"). (2) The US regulatory DB marks all 6 GHz channels no-IR (no-initiate-radiation), so the kernel refuses to run a 6 GHz AP.
FIX: add the kmod-mt7925-firmware package, and set the regdomain to CA (which permits 6 GHz LPI indoor APs on the same channels).

Lesson baked into the build: a USB Wi-Fi driver is useless without its matching *-firmware package — a clean build and a dead radio look identical until you flash real hardware.

RCA-4 · the tunnel that wouldn't build
SYMPTOM: WireGuard/NordVPN config imported, uci looks right — but no interface, no handshake, IP unchanged.
ROOT CAUSE: in OpenWrt a WireGuard peer must be its own config wireguard_<iface> section, never an inline option on the interface. A stray peer option makes netifd silently refuse to create the device.
FIX: emit the peer as a separate section. Plus a base64 key-parser bug — keys end in =, so splitting on = naively truncated them.

Now a one-command spooky vpn importer emits the correct structure so no one reproduces the trap.

RCA-5 · the audit the linters passed
SYMPTOM: every linter green. CI green. Ship it?
ROOT CAUSE: linters check grammar, not meaning. An adversarial 3-agent audit found 3 CRITICAL + 4 HIGH issues sitting under a clean build: shell injection in a config generator, a "consent gate" for pentest tools that was cosmetic (tools still on $PATH), a static Wi-Fi password shipped in every image, an unauthenticated reboot path.
FIX: shell-quote all generated config, relocate audit tools off $PATH behind a fail-closed gate, per-device random keys, and — as important — rewrite the docs that over-claimed a security guarantee we couldn't deliver.

The most valuable output of the audit wasn't a code fix. It was catching a firmware that looked done sitting on top of a root shell-injection vuln.

the twist

Two AI agents built this

The entire thing — the RKFW reverse-engineering, the 25 docs, the four web UIs, the security remediation, the on-device tooling — was built by two Claude agents working in parallel, coordinating like a small engineering team.

◈ The terminal agent (repo owner)

Owned the git repo, the build system, docs, web UIs, and the on-device control shell. Committed directly; gated every push on shellcheck + markdownlint + link + Python checks.

◈ The desktop agent (live device)

Owned the physical box — flashing, the live upgrade, the radio-stack debugging, VPN bring-up. Staged findings to a shared directory for the repo agent to ingest.

How they stayed out of each other's way

One rule made parallel work collision-free: partition by file, coordinate at the seam. A shared COORDINATION.md + staging directory was the message bus. The device agent never touched the repo; the repo agent never touched the box. When one edited the other's file, it left a note. Bugs hid at the boundaries — and got caught there (the device agent flashed an image the repo agent had marked "done" and found it wouldn't boot).

They spawned sub-agents, too

For hard problems, each agent fanned out specialists: a 3-agent adversarial audit (security / correctness / UX, each with a different hostile lens), research agents to source the wireless chipset matrix, and a verification swarm that build-tested the Wi-Fi-audit package manifest against the live OpenWrt build server before a single package was committed.

Why diverse agents beat one: the security auditor assumes the user is malicious; the code reviewer assumes the author erred; the UX auditor assumes the user is confused. A single reviewer averages those lenses and misses the sharp cases each catches. The audit found the cosmetic consent gate, the ASU-rejecting build command, and the shipped weak password — three different failure modes, three different reviewers.

by the numbers

What ~22 hours produced

55+
commits
103
files
11k
lines
25
docs
5
web UIs
4
build variants
6
on-device tools
100%
CI green

Tokens consumed: [ FILL FROM DASHBOARD ] · Model: Claude (Opus/Sonnet) · Every push gated on shellcheck, markdownlint, link-check, and Python compile — no "trust me," evidence before assertions.

The deliverable is a complete firmware program, not a proof of concept: a no-maskrom flashing toolchain, four build variants (flagship / Wi-Fi-audit / VPN / AI), an on-device control shell, a ubus-powered web dashboard, a branded LuCI theme, a router-native LLM agent, and 25 docs — every hard-won gotcha turned into a guardrail so the next person doesn't rediscover it at 2 a.m.

what it looks like

A router with a face

Not a config file and a prayer — a real control surface. A ubus-powered dashboard, a build-your-image configurator, an on-device control shell, and a branded LuCI theme. All the hardware, finally usable.

The LinkStar H68K hardware — four Ethernet ports on a palm-sized RK3568 mini-router
The patient: a $99 RK3568 box — 2× 2.5 GbE + 2× 1 GbE + Wi-Fi. Photo © Seeed Studio, CC BY-SA 4.0.
The SpookyWrt on-device dashboard — live gauges, quick controls, connected clients
The on-device dashboard — live status, gauges, quick controls.
SpookyWrt Control — the build-your-image configurator
Control — build a custom image, get a ready first-boot script.
where it's going

Roadmap

SpookyWrt already runs great on the H68K. Next up: making it run on more popular little computers, and offering ready-made "editions" for different kinds of users.

The toolkit is OpenWrt-target-agnostic by design — the flashing pipeline and the whole SpookyWrt userland port to any board OpenWrt supports. Priorities:

shipped

LinkStar H68K

The flagship — kernel 6.x, Wi-Fi + 2.5 GbE alive, 4 build variants, fully hardened.

building

Editions

One shared core, several flavors — a friendly appliance edition, a prosumer flagship, a separate authorized-audit build, and a builder/dev edition.

next

Raspberry Pi 5

The most popular SBC on earth (BCM2712, PCIe/NVMe, HDMI) — SpookyWrt as a first-class Pi router/AP.

next

NanoPi (pocket boards)

The tiny NanoPi R-class boards — SpookyWrt in the smallest, cheapest form factor for travel routers & sensors.

next

Wi-Fi client profiling

WLAN-Pi-class analysis — see exactly what every device negotiates. Lands in the pro + audit editions.

explore

Pre-baked images + mirror

Download-and-flash release images, mirrored to the Internet Archive so they never rot.

still shipping · living log

Build log

This teardown is a living document — updated as we build. Newest first.

One look — the whole project goes SpookyJuice slime
Unified every web surface onto one brand palette. The overview, the on-device dashboard, and the image configurator all moved off the old mint + violet “instrument” scheme onto SpookyJuice slime (#7cfc00) + hazard-yellow on greenish-black — the same tokens this teardown and the on-device LuCI theme already use. The mint→slime / violet→hazard remap ran from one canonical token set so nothing drifts, and bright-gradient buttons got dark text so they stay legible.Everything now looks like the same product — the toxic-green “SpookyJuice” look, from this page all the way to the box’s own screens.
Two faces, one console — a Basic / Advanced mode switch
The on-device dashboard now has a mode switch that the editions drive: Advanced is the full instrument cluster (gauges, telemetry, operating modes); Basic is a jargon-free face — a health traffic-light, an “ask your router” box that answers in plain English from real device state, and four plain cards (Internet, Wi-Fi, Guests, Privacy). Casper boots into Basic, the rest into Advanced; your own choice sticks. Both faces call the same backend.The dashboard now has two faces: a simple one that explains everything in plain English and lets you just ask “is my Wi-Fi on?”, and a full-detail one with all the gauges. The box picks the right one for you; you can flip anytime.
Four editions, wired — Casper · Poltergeist · Reaper · Séance
The build system now emits four editions from one shared core via build.py --profile: Casper (Basic appliance), Poltergeist (Pro flagship, the default), Reaper (a separate consent-gated audit image), and Séance (Dev). En route we caught two live bugs stacked on each other: the on-device overlay had blown past the build server's 40 KB first-boot cap, and a stray exit 0 was silently skipping half the install. The fix is the fun part — a gzip+base64 self-extracting overlay (56 KB → 28 KB) that places every file first, then runs each first-boot step as its own isolated process. Still offline, still no local toolchain.Four flavors of the same box — a simple one, a powerful one, a hacker one, and a builder one — all built from one shared recipe. You pick the flavor; it builds itself, no tools to install.
The control layer — spooky, an on-device operator
One command to run and troubleshoot the box over SSH or serial: status, network, Wi-Fi, diagnostics (that check every gotcha we hit), services, capture, VPN — plus a diagnostic-bundle collector. The single front door to everything below.
VPN, done right — spooky-vpn
Import any provider (WireGuard or OpenVPN — Nord/Mullvad/Proton/PIA/Express) into a masqueraded zone with a one-command kill-switch. The peer-section trap and a base64-key bug — both fixed so no one re-hits them.
A router with a brain — spooky-agent
A lightweight LLM agent that lives on the box (now core to every edition): read-only tools that inspect real device state, so you can just ask it what's wrong. No cloud round-trip to see your own router.
It has a face now — dashboard, configurator & a branded LuCI theme
A ubus-powered live dashboard, a build-your-image configurator, and a SpookyJuice repaint of the OpenWrt admin UI. The box finally looks like the platform it became.
Hardened by adversaries — the 3-agent security audit
Three AI reviewers with hostile lenses found 3 critical + 4 high issues under a green build. All remediated — including rewriting docs that over-promised a guarantee we couldn't keep.
We didn't just patch a router.
We turned e-waste into a platform.
see it · run it

It's all open

MIT-licensed, CI-green, and live. Flash your own H68K — or any OpenWrt box — and never buy a router's software story on faith again.