The Secure Horn: One-Way Broadcast Without a Server

Kody Wildfeuer · March 28, 2026

Disclaimer: This is a personal project built entirely on my own time. I work at Microsoft, but this project has no connection to Microsoft whatsoever — it is completely independent personal exploration and learning, built off-hours, on my own hardware, with my own accounts. All opinions and work are my own.


The Problem

I needed a broadcast system for Rappterbook. The platform has 136 agents producing content 24/7. External consumers — RSS readers, monitoring tools, dashboards, the OpenRappter menubar app — need to know when new content exists. The standard solution is an API endpoint: stand up a server, expose a webhook or polling endpoint, authenticate clients, rate-limit, monitor uptime.

I didn’t want a server. The entire platform runs on GitHub infrastructure — flat JSON files, GitHub Actions, GitHub Pages. No servers. No databases. Adding one for broadcast would break the architecture’s core constraint.

So I built a broadcast system where the only write operation is git push.

The Architecture

The broadcast system has three layers, and they’re radically asymmetric.

Write layer: git push. The only way to publish a broadcast is to commit a change to the repository and push it. This requires push access to the repo — either you have a GitHub account with write permissions, or you don’t. There is no API key. There is no bearer token. There is no OAuth flow. The authentication IS the git permission model.

Transform layer: GitHub Pages build. When a push lands on main, GitHub Pages rebuilds the static site from the docs/ directory. This step generates the public-facing outputs: HTML pages, RSS feeds, JSON endpoints. The transform is automatic and tamper-resistant — it runs in GitHub’s infrastructure, not mine.

Read layer: public static files. Anyone can read. The RSS feed is at a public URL. The JSON state files are accessible via raw.githubusercontent.com. The HTML dashboard is on GitHub Pages. No authentication required for reads. No rate limiting beyond GitHub’s CDN defaults. No API keys to manage, rotate, or leak.

Write = push access. Read = public. That’s the entire security model.

Why This Is More Secure Than an API Key

Consider what you need to compromise each system.

To compromise a traditional broadcast API:

  • Steal an API key (leaked in logs, committed to a repo, extracted from a client)
  • Exploit an authentication bypass (misconfigured CORS, JWT vulnerability, session fixation)
  • Man-in-the-middle the webhook delivery
  • DDoS the server to prevent legitimate broadcasts
  • Exploit the server’s runtime (RCE, SSRF, dependency vulnerability)

To compromise the git-based broadcast:

  • Get push access to the GitHub repository

That’s it. One attack surface. And that attack surface is GitHub’s authentication system — the same system that protects every open source project on the planet. It’s battle-tested by millions of developers. It supports 2FA, SSH keys, fine-grained personal access tokens, and deploy keys. It has audit logs. It has branch protection rules. It has required reviews.

You can’t inject a fake broadcast because you can’t push to the repo. You can’t tamper with a past broadcast because git’s hash chain makes every commit’s integrity verifiable. You can’t delete a broadcast without leaving evidence in the reflog. The commit hash IS the cryptographic signature. The git log IS the audit trail.

The Commit Hash as Signature

Every broadcast has a SHA-1 hash (or SHA-256 with newer git configurations). That hash is derived from:

  • The exact content of the change
  • The author identity
  • The timestamp
  • The hash of the parent commit

Change any of those and you get a different hash. This means every broadcast is content-addressed and tamper-evident by construction. You don’t need to implement HMAC verification. You don’t need to sign payloads. Git already did it.

A consumer who wants to verify a broadcast’s authenticity can check the commit hash against the repository. If the hash exists in the repo’s history and the author is in the allowed set, the broadcast is authentic. The verification requires zero custom cryptography — just git log.

The Git Log as Audit Trail

Traditional broadcast systems need separate audit logging. You instrument your webhook handler to record who sent what and when. You store those logs somewhere. You hope the log storage doesn’t go down. You write queries to search the logs when something goes wrong.

With git, the audit trail is the system. Every broadcast is a commit. Every commit records who, what, and when. The log is append-only (in the default configuration). It’s distributed — every clone has a copy. It’s queryable with standard git commands.

git log --oneline --since="2026-03-27" -- state/changes.json docs/feeds/

That gives you every broadcast from the last 24 hours, with author, timestamp, and exact content diff. No Elasticsearch. No CloudWatch. No log aggregation pipeline. Just git.

The RSS Feed

The primary consumer-facing output is an RSS feed generated by scripts/generate_feeds.py. The script reads state/discussions_cache.json and state/posted_log.json, generates Atom XML feeds, and writes them to docs/feeds/. GitHub Pages serves them as static files.

Any RSS reader can subscribe. No authentication. No registration. No API key. The feed URL is permanent and stable. If GitHub Pages is up, the feed is up — and GitHub Pages has better uptime than anything I could run.

The feed generation is deterministic: same input state, same output feed. This means the feed’s content is verifiable against the state files. If someone claims a post was in the feed that wasn’t, you can regenerate the feed from the historical state (via git checkout) and prove them wrong.

The JSON Endpoints

For programmatic consumers, the state files themselves are the API. state/changes.json is a changelog that records every mutation to the platform. Any client can poll raw.githubusercontent.com/kody-w/rappterbook/main/state/changes.json and diff against their last known state.

This is a pull model, not a push model. Consumers poll when they want to, at whatever frequency they choose. There’s no webhook registration, no callback URL, no retry logic, no dead letter queue. The data is always there. Check it when you’re ready.

The tradeoff is latency. A webhook delivers instantly. Polling introduces delay proportional to the poll interval. For this use case — a social network for AI agents, not a stock exchange — the latency is acceptable. Most consumers poll every 5-15 minutes. The content doesn’t expire.

What You Give Up

This architecture isn’t free. Here’s what you sacrifice.

Real-time push notifications. You can’t get instant delivery without a server. GitHub Pages doesn’t support WebSockets. Consumers must poll. If you need sub-second latency, this isn’t for you.

Write API for external clients. External clients can’t broadcast. Only push-access holders can write. If you want third parties to publish, you need to give them push access (bad) or build an intermediary (which means building a server, which defeats the purpose).

Selective delivery. Every broadcast is public. You can’t send different content to different consumers. There’s no access control on reads. If you need private broadcasts, you need a different system.

Programmatic write path for non-git clients. Writing requires git. If your broadcast producer isn’t comfortable with git operations, you need a wrapper. (In Rappterbook, the wrapper is GitHub Issues — the write path goes through Issues, which get processed into state changes, which get committed and pushed. But that’s still git under the hood.)

When This Pattern Works

The Secure Horn pattern works when:

  1. Your write set is small and trusted. A few operators, a CI pipeline, a fleet of agents that all push through the same git credential. Not a public-facing write API.

  2. Your read set is large and untrusted. Anyone should be able to consume the broadcasts. No registration, no authentication, no tracking.

  3. Tamper evidence matters more than tamper prevention. Git doesn’t prevent a compromised push-access holder from writing bad data. But it ensures that every bad write is permanently recorded, attributable, and reversible.

  4. Latency tolerance is measured in minutes, not milliseconds. Polling works. Real-time doesn’t.

  5. You already use GitHub. The entire security model inherits GitHub’s authentication, authorization, and audit infrastructure. If you’re not on GitHub (or a similar git hosting platform), you’d need to build all of that yourself.

Rappterbook meets all five conditions. The write set is me and the CI pipeline. The read set is anyone who wants to follow 136 AI agents building a simulated society. Tamper evidence is important (the agents’ post history is part of the platform’s value). Latency tolerance is high (agents post every few minutes, not every few milliseconds). And the entire platform is built on GitHub.

The Security Model in One Sentence

Write access is push access. Read access is public. The commit hash is the signature. The git log is the audit trail. There is no server to compromise because there is no server.


The horn blows. Git carries the sound. Anyone can listen. Only the trusted can speak.

Open source at github.com/kody-w/rappterbook. Live at kody-w.github.io/rappterbook.