Code Showcase
No repo — 17 excerpts from the real source code, unaltered
The homepage says: "Every claim here can be checked against the code." That's either verifiable or it's just a sentence — this is the verification. 17 spots from the real HIY source code (Android, iOS, Relay), original and with the original comments, exactly as they stand in the repository. Not trimmed, not smoothed over.
What's shown is the cryptography: key generation, encryption, key derivation, signature format, reach logic. That's the part that has to be checkable — it's secure because it's known. What's deliberately not shown is at the end of this page.
End-to-end encryption — the wire format
Claim: "E2EE chats with X25519 + AES-256-GCM."
/**
* Ende-zu-Ende-Verschlüsselung für "Nur Freunde"-Posts, siehe
* `docs/E2E_ENCRYPTION.md`. Nutzt das separate X25519-Schlüsselpaar aus
* `EncryptionIdentity` (NICHT den Signier-Schlüssel — getrennte Schlüssel für
* getrennte Zwecke). Pendant zu `E2E.swift` — X25519 ist ein einziger,
* unzweideutiger Standard (RFC 7748), Tinks Primitiv hier liefert exakt
* dieselben Bytes wie CryptoKit dort; HKDF-SHA256 mit leerem Salt ist wegen
* HMACs eigenem Zero-Padding kurzer Schlüssel ebenfalls plattformunabhängig
* identisch (ein 0-Byte- und ein 32-Byte-Null-Schlüssel ergeben nach dem
* Padding auf die Blockgrösse dasselbe Ergebnis).
*
* Wire-Format pro versiegeltem Blob (`encText`, und jeder Wert in `encKeys`):
* base64(Nonce[12] || Ciphertext || Tag[16]) — identisch zu CryptoKit's
* `AES.GCM.SealedBox.combined`.
*/
object E2E {
private val HKDF_INFO = "hiy.e2e.v1".toByteArray(Charsets.UTF_8)
private const val NONCE_LEN = 12
data class Sealed(val encText: String, val encKeys: Map<String, String>, val encBild: String? = null)
The wire format is right there in the comment (in German — these are the developer's real, unedited notes, not a translation). Anyone who reads it can reconstruct an intercepted blob by hand — that's what makes a claim checkable. Notable in passing: separate keys for signing and encryption, with a reason given.
Backup PIN — the key derivation
Claim: "unlockable only with the PIN (600,000 rounds of PBKDF2)."
/**
* Verschlüsseltes Backup der Freundesliste — überlebt eine Neuinstallation/
* einen Gerätewechsel, anders als FriendsStore selbst (rein lokal,
* SharedPreferences). Der Relay sieht nie Klartext: der AES-Schlüssel kommt
* aus einer selbst gewählten PIN, nie vom Server — nur wer die PIN kennt,
* kann den Blob entschlüsseln, der Relay-Betreiber selbst nicht. Pendant zu
* FriendsBackup.swift.
*/
object FriendsBackup {
// OWASP-Empfehlung (2023+) für PBKDF2-HMAC-SHA256 ist 600k Runden — vorher
// 200k, zusammen mit der Mindestlänge von 10 Zeichen über die volle
// Tastatur (s. BackupPinDialog.kt) macht das Offline-Brute-Force gegen
// einen durchgesickerten Backup-Blob (liegt verschlüsselt auf dem Relay)
// praktisch aussichtslos.
private const val ITERATIONS = 600_000
private const val KEY_LENGTH_BITS = 256
600,000 rounds of PBKDF2-HMAC-SHA256, AES-256.
…and the minimum length, without which the round count says nothing
// MINDESTENS ZEHN ZEICHEN, UND BUCHSTABEN SIND ERLAUBT.
//
// Der verschluesselte Block liegt auf dem Relay, und darin steht die
// Freundesliste: Namen, Codes, Schluessel. Nicht der Inhalt von
// Nachrichten, aber das soziale Netz. Faellt er je jemandem in die Haende,
// zaehlt allein, wie teuer das Durchprobieren ist.
//
// Acht Ziffern waeren 100 Millionen Moeglichkeiten — trotz 600 000
// PBKDF2-Runden auf OWASP-Stand auf einer guten Grafikkarte in wenigen
// Stunden durch. Genau deshalb sind es zehn: zwei Stellen mehr
// verhundertfachen den Aufwand, Buchstaben vertausendfachen ihn.
// Erzwungen werden sie nicht — wer bei Ziffern bleibt, soll das koennen;
// die Tastatur bietet beides an.
//
// VERLAUF, damit die Zahlen unten nicht verwirren: 4 -> 6 -> 8 -> 10, die
// PBKDF2-Runden parallel von 200k auf 600k (s. FriendsBackup). Jede Stufe
// kam aus demselben Grund, nur mit besserer Rechnung.
//
// GILT NUR FUER NEUE PINs. Beim Wiederherstellen wird keine Laenge
// geprueft, sonst sperrten wir Bestandsnutzer:innen aus ihrem eigenen
// Backup aus. Die Kehrseite: Blobs, die noch mit einer kurzen PIN von
// frueher verschluesselt sind, bleiben schwach — dagegen hilft nur, nach
// erfolgreicher Wiederherstellung zur Neuvergabe aufzufordern.
val isValid = if (isRestore) pin.isNotEmpty() else pin.length >= BACKUP_PIN_MIN && pin == confirmPin
Even 600,000 rounds would be worthless against a four-digit numeric PIN. Only the two numbers together make a checkable claim. The progression 4 → 6 → 8 → 10 is deliberately noted alongside.
…and the actual derivation, still PBKDF2 today
/** Salt aus sub (nicht geheim — Salts müssen es nicht sein) statt fest
* verdrahtet, damit zwei verschiedene Konten mit zufällig gleicher PIN
* nicht denselben Schlüssel ableiten. */
fun deriveKey(pin: String, sub: String, verfahren: Verfahren = Verfahren.ARGON2ID): SecretKeySpec {
val salt = MessageDigest.getInstance("SHA-256").digest(sub.toByteArray(Charsets.UTF_8))
return when (verfahren) {
Verfahren.PBKDF2 -> {
val spec: KeySpec = PBEKeySpec(pin.toCharArray(), salt, ITERATIONS, KEY_LENGTH_BITS)
SecretKeySpec(
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).encoded,
"AES",
)
}
The function signature gives away the next step: a verfahren ("method") parameter, default value ARGON2ID. New backups today still run through the PBKDF2 branch — a switch elsewhere in the same file keeps them there until the iOS app can read Argon2id. Flip it too early, and backups meant to be opened on the other platform become unreadable. Even a plan that isn't finished yet belongs on this page, as long as it's named honestly as that.
Signed fields — what a signature covers
Claim: "nothing can be swapped without being noticed."
const SIGNING_FIELDS = {
post: ['id', 'author', 'handle', 'text', 'createdAt', 'originPeerId', 'audience', 'imageHasCameraExif', 'reachBudget', 'imageHash', 'avatarUrl', 'quotedPostId', 'encText', 'encKeys', 'encPubKey'],
// avatarUrl bei comment/friendreq nachgezogen (Sicherheits-Review vor
// Beta): vorher NICHT Teil der Signatur — ein Relay/MITM konnte das
// angezeigte Profilbild eines sonst gültig signierten, "✅ verifizierten"
// Kommentars/einer Freundschaftsanfrage unbemerkt austauschen. Gleicher
// Fix, der für WirePost schon früher gemacht wurde (s. dortiger Kommentar).
comment: ['id', 'postId', 'author', 'handle', 'text', 'createdAt', 'originPeerId', 'encPubKey', 'avatarUrl'],
report: ['id', 'targetId', 'targetType', 'reporterId', 'createdAt'],
friendreq: ['id', 'kind', 'fromId', 'fromName', 'toId', 'createdAt', 'encPubKey', 'avatarUrl'],
// Direktnachricht: anders als "post" gibt es hier NIE eine Klartext-Variante
// — kein `text`-Feld, nur der bestehende E2E-Mechanismus (encText/encKeys/
// encPubKey, exakt wie bei "Nur Freunde"-Posts). `toId` (Fingerprint/sub der
// Empfängerin) ist signiert mitgeführt, damit ein MITM nicht den Empfänger
// austauschen kann, ohne die Signatur ungültig zu machen — der Relay selbst
// braucht `toId` inhaltlich nicht (er matcht über encKeys), es ist reine
// Ziel-Absicherung.
dm: ['id', 'toId', 'author', 'createdAt', 'originPeerId', 'encPubKey', 'avatarUrl', 'encText', 'encKeys'],
// …
gm: ['id', 'gruppenId', 'author', 'createdAt', 'originPeerId', 'encPubKey', 'avatarUrl', 'encText', 'encKeys'],
};
The comment documents a FOUND AND CLOSED gap: avatarUrl wasn't signed, so a relay could swap the profile picture on an otherwise validly signed, "✅ verified" comment. Showing your own gap openly is more credible than any security promise. The newest line (gm, since 31.08.2026) shows the pattern growing with the product: group messages get the same protection as direct messages, not a weaker one of their own.
The second finding in the same function, months later
/** Felder, in denen ein senkrechter Strich erlaubt bleiben MUSS, je Typ.
* Genau eins pro Typ, und nur dort, wo Menschen frei schreiben. */
const TRENNZEICHEN_ERLAUBT = { post: 'text', comment: 'text' };
/**
* WARUM EIN SENKRECHTER STRICH IN FAST KEINEM FELD STEHEN DARF.
*
* signingString() haengt die signierten Felder mit '|' aneinander und
* escaped nichts. Enthaelt ein Feldwert selbst ein '|', sind die Feldgrenzen
* nicht mehr eindeutig: Aus author="Anna", handle="anna", text="hallo|welt"
* wird die Zeichenkette "…|Anna|anna|hallo|welt|…" — und dieselbe entsteht
* aus handle="anna|hallo", text="welt". Beide Belegungen ergeben BYTEWEISE
* dieselbe signierte Zeichenkette, also gilt dieselbe Signatur fuer beide.
*
* Wer eine Nachricht unterwegs veraendern kann — der Relay selbst, oder wer
* den Transportweg bricht —, koennte damit Inhalt ueber eine Feldgrenze
* schieben, ohne die Signatur zu brechen. Erfinden kann er nichts, nur
* vorhandene Bytes verruecken. Aber genau davor soll die Signatur schuetzen.
*
* DIESE PRUEFUNG MACHT DIE ZERLEGUNG WIEDER EINDEUTIG. Darf ausser dem
* Textfeld kein Feld ein '|' enthalten, dann muss jede abweichende Zerlegung
* ein '|' in ein anderes Feld schieben — und scheitert hier. Nachrechnen:
* Wer die Grenze VOR dem Text nach rechts schiebt, bekommt eins ins
* vorangehende Feld; wer sie danach nach links schiebt, eins ins folgende.
* Beides abgewiesen. Typen ohne Textfeld (dm, friendreq, report) sind damit
* vollstaendig eindeutig.
*
* DIE SAUBERE LOESUNG WAERE EIN LAENGENPRAEFIX statt eines Trennzeichens.
* Sie aendert aber JEDE signierte Zeichenkette und damit jede Signatur — jede
* installierte App wuerde ab dem Umstieg alles als "Signatur ungueltig"
* verwerfen. Das braucht eine versionierte Umstellung mit Uebergangszeit, wie
* bei der Attest-Pflicht. Diese Pruefung hier kostet nichts und schliesst die
* Luecke bis dahin.
*
* PREIS, bewusst bezahlt: Wer einen senkrechten Strich im Anzeigenamen fuehrt,
* wird abgewiesen. Der Client zeigt den Grund seit RelayAbgelehnt an.
*/
function trennzeichenProblem(type, p) {
Two documented findings in the same function — and the honest admission of why the clean fix (a length prefix) hasn't landed yet. Being open about an unfinished remainder reads as more credible than a page where everything looks done.
Reach budget — arithmetic, not an algorithm
Claim: "no algorithm, just a reach budget."
function zustellGrenze(p) {
return budgetVon(p) * BROADCAST_K;
}
function darfNochReisen(p) {
if (!REICHWEITE_AKTIV) return true;
if ((p.t || 'post') !== 'post') return true;
if ((p.audience || 'all') !== 'all') return true;
return (zustellungen.get(p.id) || 0) < zustellGrenze(p);
}
Four lines. No model, no weighting, no telemetry — a multiplication and a comparison. This is the path that carries practically all delivery: it counts how many devices pick up a post in the "Everyone" feed, not how many forward it. And the third line is the humanly most important one — audience !== 'all' lets everything else through untouched: only "Everyone" is limited at all. "Friends," "Local," and "Only me" are addressed, not broadcast, and go to everyone intended. Choosing a small reach doesn't silence your own people — it only limits how far a post spreads among strangers.
// Resonanzreichweite: 1 Hop verbraucht, bei lokaler Resonanz (z. B.
// Autor ist Freund) einen kleinen, rein lokalen Rabatt gewähren.
// Diese Entscheidung wird nirgends gemeldet oder gezählt.
val discount = if (isResonantAuthor(post)) RESONANCE_DISCOUNT else 0
val forwarded = post.copy(hopCount = (post.hopCount + 1 - discount).coerceAtLeast(0))
if (forwarded.hopCount < forwarded.reachBudget) {
broadcast(forwarded.toJson().toString(), exclude = key)
} // sonst: Reichweite erschöpft, verebbt hier — keine Weitergabe
Only on the local network does the math look like this: a hop counter that rises by 1 on every device-to-device forward (and drops instead of rising for friends of the author). That's the path on the same Wi-Fi — the exception, not the rule. Both paths are arithmetic, not an algorithm, just different ones: one counts forwards, the other counts pickups.
Identity in the chip — Android
Claim: "the private key never leaves the chip."
private fun ensureKeyPair() {
if (keyStore.containsAlias(ALIAS)) return
// StrongBox bevorzugen (API 28+), bei Fehler auf normalen Keystore zurückfallen.
val strongBoxSupported = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P
if (strongBoxSupported) {
try { generate(strongBox = true); Log.i(TAG, "Identität in StrongBox erzeugt"); return }
catch (e: Exception) { Log.w(TAG, "StrongBox nicht verfügbar: ${e.message}") }
}
generate(strongBox = false)
Log.i(TAG, "Identität im Keystore erzeugt")
}
private fun generate(strongBox: Boolean) {
val builder = KeyGenParameterSpec.Builder(
ALIAS,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY,
)
.setAlgorithmParameterSpec(java.security.spec.ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
if (strongBox && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
builder.setIsStrongBoxBacked(true)
}
StrongBox preferred, with a fallback to the regular Keystore. That fallback belongs there — not every device has StrongBox, and hiding that would be exactly the kind of half-truth this page accuses others of.
Identity in the chip — iPhone
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag,
kSecAttrAccessControl as String: access,
],
]
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
// Secure Enclave nicht verfügbar (z. B. manche Simulatoren) — Fallback
// auf einen software-gestützten Schlüssel im Keychain, weiterhin
// secp256r1 und damit drahtkompatibel, nur ohne Hardware-Schutz.
return generateSoftwareKey(tag: tag)
kSecAttrTokenIDSecureEnclave is the one line that matters. That two independent implementations produce the same format is itself an argument — and here too, the fallback is stated openly right alongside.
What's NOT in a notification
// KEIN NAME UND KEIN KLARTEXT IN DER NUTZLAST — auch nicht dort, wo der
// Relay beides kennt.
//
// Bis hierher stand der Absendername im Titel und der Kommentartext im Body.
// Beides ist inhaltlich verzichtbar und reist an Apple und Google vorbei: aus
// "diese Person hat jener geschrieben, um 21:04" laesst sich der halbe
// Freundes-Graph rekonstruieren, ohne eine einzige Nachricht zu lesen. Genau
// dieselbe Korrektur wurde beim Freundschaftsanfrage-Push schon gemacht (s.
// dortiger Kommentar) — hier fehlten nur die drei anderen Faelle.
//
// Was der Relay stattdessen schickt, ist eine Kennung: `t` und `fromId`. Den
// Namen loest der Client aus der EIGENEN Freundesliste auf — dieselbe Quelle,
// aus der die iOS-Extension ihn schon fuer Direktnachrichten holt, und der
// einzigen, der man glauben darf.
//
// Die Texte hier bleiben trotzdem stehen und bleiben deutsch. Sie sind der
// Rueckfallschirm: installierte Apps aelterer Fassungen lesen `title`/`body`
// unbesehen, und auf iOS erscheinen sie, wenn die Extension nicht laeuft.
// Weglassen hiesse "HIY" mit leerer Zeile auf jedem Geraet, das noch nicht
// aktualisiert hat. Neue Clients ueberschreiben sie ohnehin — samt der
// richtigen Sprache, was die alte Loesung nie konnte.
About a limit, not a capability: a push has to go through Apple or Google. The only question is what's inside it. Google and Apple see THAT something arrived and WHEN — not what, and not from whom.
Group modules — one answer for all of them
/**
* DIE EINE ANTWORT FUER ALLE MODULE.
*
* Vier Module fragen etwas, und der naheliegende Weg waere, jedem seine
* eigene Antwortart zu geben: `stimme`, `zusage`, `verfuegbarkeit`,
* `haken`. Vier Formate auf der Leitung, vier Auswertungen, vier Stellen,
* an denen sich ein Fehler einnisten kann.
*
* Stattdessen EINE: `{bezug, art, wahl:[Zahlen]}`. Bei der Umfrage sind
* die Zahlen die angekreuzten Optionen, beim Termin genau eine (ja /
* vielleicht / nein), beim Doodle die passenden Vorschlaege, bei der Liste
* die erledigten Punkte. Die Auswertung steht damit an EINER Stelle
* (`GruppenModule.werte`), und ein fuenftes fragendes Modul braucht sie
* gar nicht mehr.
*
* WARUM DAS OHNE ABSPRACHE FUNKTIONIERT: Jede Antwort ist eine eigene
* Nachricht mit eigenem Zeitstempel. Wer zweimal antwortet, hat zweimal
* geantwortet — es gilt die spaetere. Niemand muss etwas zurueckziehen,
* niemand muss eine Nachricht aendern (was verschluesselt ohnehin nicht
* ginge), und ein Geraet, das eine Woche aus war, holt die Antworten in
* beliebiger Reihenfolge nach und kommt aufs selbe Ergebnis.
*/
const val MODUL_ANTWORT = "antwort"
Four question-asking modules — poll, event, doodle, list — and a single answer shape for all of them. That's a design decision, not a line of code: the other three paths (a separate format per module) were considered and rejected. The "WARUM DAS OHNE ABSPRACHE FUNKTIONIERT" ("why this works without coordination") paragraph solves a real puzzle in passing — how a poll answer can "change" when nobody can alter an already-sent encrypted message: you don't change anything, you say it again, and the latest one counts.
Whoever scans, knocks
/**
* "Ich habe euren QR-Code gescannt und moechte dazu."
*
* DER EINZIGE WEG, WIE JEMAND VON AUSSEN AN EINE GRUPPE HERANKOMMT — und
* er endet nicht im Beitritt, sondern in einer Frage. Wer scannt, ist
* damit noch kein Mitglied; die Nachricht geht ausschliesslich an die
* Person, deren Code im QR steht, und die entscheidet.
*
* WARUM DAS OHNE FREUNDSCHAFT FUNKTIONIERT: Der QR traegt den
* Verschluesselungsschluessel der Einladenden. Damit laesst sich eine
* ganz gewoehnliche Gruppennachricht bauen, deren `encKeys` genau EINEN
* Eintrag hat — den fuer sie. Der Relay stellt sie zu, ohne etwas ueber
* Gruppen zu wissen; alle anderen koennen sie nicht einmal oeffnen.
*
* WARUM DAS NICHT ZUM EINFALLSTOR WIRD: Es traegt sich niemand selbst
* ein. Die Nachricht landet als Frage im Verlauf, und erst ein Tippen auf
* "Aufnehmen" schreibt die Mitgliederliste fort — von einem Geraet, das
* ohnehin schon Mitglied ist.
*/
const val MODUL_BEITRITT = "beitritt"
A QR code looks like a key. Here, it's a doorbell. The difference isn't a small one: forwarding an invite link doesn't tear a group open — the message goes to exactly one person, who decides, and nobody adds themselves.
What a server can't tell apart
} else if (type === 'gm' && stored.gruppenId && stored.encKeys && !stored.silent) {
// `silent` wie bei friendreq (s. oben): Eine Gruppennachricht ist nicht
// immer eine Nachricht. Nach jedem Geraetewechsel geht die aktualisierte
// Mitgliederliste an alle — Verwaltung, kein Gespraech. Ohne diese
// Unterdrueckung klingelte bei jeder Wiederherstellung "Neue
// Gruppennachricht" bei allen Mitgliedern, ohne dass jemand etwas
// geschrieben hatte (gemeldet von Benjamin am 31.08.2026).
//
// WARUM DER RELAY DAS NICHT SELBST SIEHT: Das Modul steckt im
// verschluesselten `encText`. Er sieht "eine gm" und sonst nichts — das
// ist der Sinn der Sache. Also entscheidet es die Absenderin.
// An jedes Mitglied ausser die Absenderin selbst.
The most honest excerpt in the whole collection. It shows an inconvenience of encryption instead of an advantage: because the server doesn't know the content, it also can't decide whether something is worth a notification. The fix isn't an exception to the principle, it's shifting the decision to where the plaintext lives — onto the device. Anyone who wants to know whether an encryption claim is real should watch for exactly these spots: where the server obviously knows less than it would need to know to be convenient.
The relay side, too
Almost every excerpt so far has come from the apps. But the relay matters more for credibility, because that's where the suspicion lives — "what does the server collect?" Four spots that all give away nothing critical.
What the relay knows about a group — and that it's written down
/**
* Gruppennachrichten abholen. Zugestellt wird, wofuer ein Eintrag in `encKeys`
* vorliegt — es gibt kein Empfaengerfeld wie `toId` bei der Direktnachricht.
*
* DAMIT SIEHT DER RELAY, WER MIT WEM IN EINER GRUPPE IST. Das laesst sich nicht
* vermeiden: Irgendwoher muss er wissen, an wen er ausliefern soll. Neu ist es
* nicht — bei einem "Nur Freunde"-Beitrag erfaehrt er aus denselben `encKeys`
* schon heute, wer mit wem befreundet ist. Den INHALT sieht er in keinem der
* beiden Faelle. Das gehoert auf die Technologie-Seite, nicht unter den Tisch.
*/
app.post('/gm/fetch', (req, res) => {
The last sentence in the comment is the point. A weakness named in the source code itself is a different kind of statement than one someone found from the outside. And it's not new: on a "Friends only" post, the same information already sits in encKeys.
Retention: seven days, written into the code
const RETENTION_DAYS = parseInt(process.env.RETENTION_DAYS || '7', 10);
const MAX_POSTS = parseInt(process.env.MAX_POSTS || '50000', 10);
// …
function prune() {
const cutoff = nowMs() - RETENTION_DAYS * 86400000;
let changed = false;
for (const [id, p] of posts) if ((p.serverTs || 0) < cutoff) { posts.delete(id); changed = true; }
// Obergrenze: älteste zuerst entfernen
if (posts.size > MAX_POSTS) {
const sorted = [...posts.values()].sort((a, b) => a.serverTs - b.serverTs);
// Die Zahl VOR der Schleife festhalten: `posts.size` schrumpft mit jedem
// Loeschen, die Bedingung wanderte also mit und es wurde nur etwa die
// Haelfte des Ueberhangs entfernt. MAX_POSTS wurde dadurch nie erreicht.
const zuViel = posts.size - MAX_POSTS;
for (let i = 0; i < zuViel; i++) { posts.delete(sorted[i].id); changed = true; }
}
if (changed) {
rewriteDisk();
// Die Reichweiten-Buchhaltung mit aufraeumen — sonst waechst sie ewig
// weiter, obwohl die Beitraege laengst weg sind.
for (const id of [...zustellungen.keys()]) if (!posts.has(id)) { zustellungen.delete(id); zustellGeaendert = true; }
for (const id of [...zustellHerkunft.keys()]) if (!posts.has(id)) zustellHerkunft.delete(id);
sichereZustellungen();
}
}
Retention periods usually live in a privacy policy. Here they live in the file that carries them out, and the two can be laid side by side. The comment in the middle shows a real, found and fixed bug: the cap was never reached, because the count shifted mid-deletion.
A direct message only comes with proof
const data = `${praefix}|${since}|${createdAt}|${typeof id === 'string' ? id : ''}`;
if (!verifyRawSignature(data, pubKey, signature)) {
// Fingerprint UND IP mitloggen: ohne die war eine abgelehnte Anfrage nicht
// zuzuordnen. Eine leere Signatur bekommt einen eigenen Hinweis, das ist
// der haeufigste Fall (gesperrtes iPhone, s. SecureEnclaveIdentity.sign).
const who = fingerprintOf(pubKey) || 'unbekannt';
const why = !signature ? 'Signatur LEER (Geraet gesperrt?)' : 'Signatur ungueltig';
console.warn(`[relay] /${praefix} abgelehnt: ${why} (fp=${who}, ip=${req.ip})`);
res.status(401).json({ error: 'Signatur ungueltig' }); return null;
}
The app's access token is baked in and therefore effectively public. It isn't enough for direct messages: to fetch them, you have to sign with the private key that never leaves the hardware chip. The difference between "logged in" and "in possession of the device" — at a spot where it actually matters.
The bugs are in here too
// Direktnachrichten gehen NIE an alle. `withinBudget` wird nur im
// post-Zweig oben angefasst und blieb fuer "dm" auf true — jede
// Direktnachricht ging damit live an jeden offenen Client, mit Empfaenger,
// Absender-Schluessel und Zeitpunkt. Der HTTP-Weg schliesst das laengst aus
// (GET /posts filtert dm heraus, s. dort), der WebSocket-Weg nicht. Wer
// wann wem schreibt, lag damit fuer jeden offen, der eine Verbindung
// offenhaelt — genau das Metadaten-Leck, das die DM-Auslieferung ueber
// POST /dm/fetch verhindern sollte.
if (stored.t === 'dm') withinBudget = false;
The strongest excerpt in the whole collection. This is a real bug — found, fixed, and described without any polish: every direct message went out live over the WebSocket path to every open client, with recipient, sender key, and timestamp. Leaving something like that standing in public means you either have nothing to hide or you're braver than you need to be. Both are more convincing than any security promise.
What's deliberately not shown: What's shown is the cryptography — key generation, encryption, key derivation, signature format, reach logic. That's the part that has to be checkable; it's secure because it's known.
Not shown is abuse defense: bot detection patterns, report thresholds, rate limiting. Openness there only helps an attacker, who just has to stay under the threshold. This distinction isn't an excuse — it's the difference between a lock and an alarm system.
Nothing from the moderation panel: it shows blocks, reports, and dossiers, and even a harmless excerpt would give away the structure of moderation.
And no key-derivation parameters used as a "this is how secure it is" claim: the numbers themselves (rounds, memory) can stand, but without a comparison like "X years to crack." Numbers like that age badly and invite contradiction.
These excerpts are a curated look, not a complete repository and not an invitation to build your own client. Questions about a specific spot: info@hiy.ch.