DEV Community

Ronny Cruz
Ronny Cruz

Posted on

The Compatibility Claim I Hadn't Earned

The Compatibility Claim I Hadn't Earned

By Ronny Cruz Alvarez, founder of Open Feed Network (Candor Network). Solo founder, one production platform, and — as of this week — one very small Fediverse instance.

The line in my own README

Sentinel Signup is a registration-screening service for Fediverse instances. It reads your pending signup queue, scores each applicant, and approves or rejects through the admin API. I launched it during the July spam wave, when instance admins were drowning in fake approval requests.

Here's the setup instruction I shipped with it:

Mastodon: Preferences → Development → New application, scopes admin:read admin:write.

Read that again. The product says Fediverse. The documentation says Mastodon. Those are not the same word, and the gap between them was a claim I had no evidence for.

The Fediverse is not one piece of software. It's Mastodon and GoToSocial and Akkoma and Pleroma and Misskey and Sharkey and Iceshrimp and a dozen others, most of which implement "the Mastodon client API" to varying and undocumented degrees. My sidecar spoke four endpoints of that API. I had verified them against exactly zero servers, because I had never run an instance in my life.

I was selling anti-abuse tooling to a community I wasn't a member of. That's not a moral failing, but it is an epistemic one: everything I knew about how those servers behave, I knew from reading specifications. Specifications describe intent. Deployments have opinions.

So I built the testbed I should have had first

The constraint was that it had to cost nothing. Mastodon itself is a heavy tenant — Ruby, Sidekiq, Redis, its own Postgres — and the box I'd be putting it on already runs five production services. GoToSocial is a single Go binary with SQLite that idles in a couple hundred megabytes, and it implements the Mastodon client API. That's the whole reason it was the right choice: it's not Mastodon, which makes it a real test rather than a rehearsal.

Two things went wrong before it ever started, and both are worth writing down.

The CPU refused. GoToSocial's standard build runs its bundled ffmpeg and SQLite through wazero, a WebAssembly runtime, and wazero's compiler requires x86-64-v2 instructions. My VPS reports itself as a generic QEMU virtual CPU — no SSE4.2, no SSSE3, no POPCNT — because that's what many budget and older virtualized hosts advertise. GoToSocial doesn't degrade to interpreter mode; it panics on purpose, which I respect. The fix is an official nowasm build that uses the system's native ffmpeg instead. If you're self-hosting anything on inexpensive virtualization, check /proc/cpuinfo before you assume modern instruction sets. Mine had been quietly ancient for months.

The proxy lied about who was knocking. The instance sits behind Cloudflare, and my nginx config forwarded the client address the conventional way. Behind a CDN, that address is the CDN's — every signup on Earth would have arrived from the same handful of edge IPs.

That one is worth dwelling on, because of how it fails. Nothing errors. The screening service still returns verdicts. The logs still look healthy. It just silently becomes blind in the exact dimension it's most valuable in: IP reputation, subnet velocity, burst detection — all the signals that catch a bot farm — collapse into "one address, always." A safety system that reports success while measuring nothing is worse than one that's obviously broken, which is a lesson I keep relearning in new costumes. The fix was one nginx directive reading the CDN's real-client header. The habit is: after any infrastructure change, verify that your signals still carry information, not just that your requests still return 200.

What verification actually looked like

With the node running and reachable, I checked the four calls my sidecar makes, one at a time, against a server I'd never tested:

  • GET /api/v2/admin/accounts — 200
  • GET /api/v1/admin/accounts (the fallback path) — 200
  • GET /api/v2/instance — 200
  • POST /api/v1/admin/accounts/{id}/reject — 200

Then the question that actually determined whether the product is useful on GoToSocial at all: does the admin API expose the signup IP? Mastodon does. If GoToSocial didn't, then on GTS instances Sentinel would be running on username and application text alone — still something, but a fraction of the defense, and I'd have to say so out loud in the documentation.

It does. The ip field came back populated. Velocity, subnet, and reputation all function. The compatibility claim survives without a caveat, and — this is the part that matters — it survives because I ran it, not because I assumed it.

I also found two differences worth documenting. GoToSocial places a signup into the pending queue immediately, without waiting for email confirmation, which Mastodon doesn't. And its admin CLI has no delete for accounts at all — only disable, plus rejection through the API. Neither breaks anything. Both would have been surprises in front of a customer.

The first real signup

Then I registered a throwaway account on my own instance, in an incognito window, like a stranger would, and wrote a genuine sentence in the reason field.

Thirty seconds later the sidecar picked it out of the queue, sent it to the live screening service, and logged:

WOULD APPROVE: @[redacted] verdict=pass confidence=1 — Clean IP history and natural application language.

Dry run, so it did nothing, which is exactly what dry run is for. But the whole chain had run for the first time in its life: pending signup → admin API → screening engine → verdict → action decision. Every piece I'd built separately, working together, on a server type I'd never touched.

Rejecting the test account afterward returned 200 and cleared the queue to zero, which incidentally verified the last endpoint.

I left it in dry run, on purpose

The obvious next move is flipping it live so my own instance is auto-screened. I didn't.

My instance isn't a community — it's one account and a testbed. Turning on auto-approval means strangers get accounts, which means I'm hosting federated content, which means I've added a moderation surface to a machine already running five production services. That's a real decision with real obligations, not a config flag, and I'm not making it as a side effect of testing something.

So the sidecar runs continuously in dry run: every future signup gets screened and logged, giving me the observations, while approval stays a human decision. The testbed value is fully there. The obligation isn't taken on accidentally. I can flip it whenever I mean to.

Honest limitations

  • One instance, one signup, my own IP. This proves the plumbing connects. It is not a load test, not an adversarial test, and not evidence about accuracy. Nobody has attacked this instance, because nobody knows it exists yet.
  • One version. GoToSocial 0.22.1. API coverage moves between releases; a claim verified today is a claim about today.
  • "Fediverse-compatible" is still an overclaim, and I'm retiring it. What I can now say is: Mastodon and GoToSocial, verified. Akkoma, Pleroma, Misskey, Sharkey, Iceshrimp — untested. If you run one of those and want to find out with me, I'll do the work with you.
  • The nowasm build is officially unsupported for media processing. Fine for a testbed. Worth knowing before you put it under a real community.
  • I use AI tooling heavily — Claude, mostly — to build and to write, including this post. The screening engine itself is deliberately heuristic and rule-based with no model in the loop, because it has to run at effectively zero marginal cost for instances that can't pay for spam protection. Where the machines helped, they helped; where the evidence comes from, it's my own server's logs.

The point of being customer zero

"Built for ourselves, available to you" has been the pitch since the safety modules were extracted from my own platform. This week it got more literal: my platform's registration is screened by this product, and now a second server type is too, on infrastructure I run.

The reason that matters isn't marketing. It's that every gap I just described — the CPU, the blinded IPs, the queue behavior, the missing CLI verb — would otherwise have been found by an admin under attack, at the worst possible moment, in a system they'd trusted on my word. Finding them cost me an evening and zero dollars. Finding them the other way costs someone their instance.

If you run a Fediverse instance of any flavor and your signup queue is full of garbage, Sentinel Signup is free during beta: signup.candortheopenfeednetwork.com. If you run something I haven't verified, tell me and let's test it properly — tips@candortheopenfeednetwork.com. I answer everything myself.

Top comments (0)