28 July 2026Claims
Our own store listing promised a feature the app deliberately hides
Both of our app-store descriptions said: “You add a contact with a QR code or a one-time link.” There is no QR pairing in Avano. The application requests no camera permission, there is no code decoder anywhere in it, and the pattern that looks like a code on one screen is decorative noise that encodes nothing — the app's own source says a camera pointed at it "would decode garbage". The interface hides the scan button in any build with a real engine.
So the product was right and the description was wrong. That is exactly the same defect as a switch in an interface that toggles nothing, except that it reaches everybody who looks at the store page rather than only the people who press the switch. A screenshot of that screen had already been withheld for this reason, which means the text was claiming something our own screenshot policy had refused to show.
Four more claims did not survive the same check. That we "open-source the code" — the repository is private. That Avano is "pre-release software in active development" — a phrase our own shipped self-test forbids, because store rules exclude betas. That Apple's push service can see the timing of incoming messages — there is no push integration at all, so that sentence disclosed a limitation of a feature that does not exist and implied background delivery the app cannot do. And that the app tells you per conversation whether the relay split is in force — true on one platform, not the other.
The uncomfortable part is that every one of these was written in good faith by somebody describing the product from an earlier, plausible mental model. None was a lie. They were all claims nobody re-checked against the thing itself, which is the specific failure this whole project is organised against.
28 July 2026Product
The microphone drew a delivered tick over a recording that never existed
Avano's interface had a microphone button. Tapping it on a real conversation drew a voice message with a waveform and a delivered tick.
It had never recorded anything. The application declares no microphone permission, so the request for audio was rejected; the rejection was swallowed by an empty error handler; the recorder stayed empty; and the code fell through to a path that draws a bubble with no audio in it. Playing it back ran a synthetic waveform animation on a timer. Nothing was ever sent, and no engine call was ever made.
The guard that should have caught it existed two functions away — the photo path opens with a comment saying a live conversation must not draw a "sent" bubble for an image that never left the device. The interface hides unavailable controls by identifier, and the microphone element had no identifier, so it could not be hidden and was not.
This is the repository's signature defect inverted. Usually it is a defence nobody calls. Here it was an interface claiming a send that never happened, to a user who might have believed a message had gone. The fix was a deletion, not a feature.
Voice messages will come back properly, and there is a nice result waiting for them: capped at thirty seconds, a voice message fits inside a single padded file chunk, which means every one is exactly one upload and leaks no duration at all. The expensive alternative — allowing long recordings without leaking length — costs ten times the traffic for every "ok".
28 July 2026Design
We made cover traffic free, and reversed our own decision to sell it
Cover traffic — sending at a constant rate whether or not you have anything to say, so nobody can read your volume — was going to be a paid feature. It is now free, on by default, at the same rate for everyone.
The argument that changed it is not generosity, and it is not about cost. A privacy lane you have to buy marks the people who bought it. If only subscribers carry constant traffic, a relay does not have to break anything to identify them: it reads the constant rate straight off the queue. And people who pay for anonymity are, on average, people with more to hide.
So a premium-only cover lane is not a privacy feature at all. It is a high-value-target flag, maintained by us, at the subscriber's expense. Anonymity is a property of a crowd, and a crowd you have to buy your way into is not one.
The user-facing switch stays, for an honest reason: measured at the socket, the lane costs about 748 MiB per device per month. Somebody on a metered mobile plan has a real, non-privacy reason to turn it off. What changed is the default and the price, not the existence of the choice — and note the failure direction is now inverted from every other setting in the product. If the stored preference cannot be read, cover stays on, because being wrong that way costs bandwidth and being wrong the other way costs the property.
What we have not done, and are saying so before somebody finds it: we have not priced this. Constant-rate cover costs money per user per unit time rather than per message, so a free user's cost is a floor that never falls. Every sustainable free tier in this market is either near-zero marginal cost or explicitly boxed in. That number is computable and it should exist before the promise is repeated at scale.
27 July 2026Security
Why our security.txt points at hello@ instead of security@
Until recently, requesting /.well-known/security.txt from our site returned the homepage with a success code — because the host falls back to the index page for any missing path. A researcher's tooling saw a valid response containing markup, which reads as a malformed file rather than an absent one. That is worse than a clean not-found, because it looks like we tried.
Writing the file raised a smaller question with a more interesting answer. The conventional address is security@. We have published security@avano.app elsewhere, and we have not confirmed that mailbox exists.
An unconfirmed disclosure address is a trap with our name on it. A researcher writes to it, receives no bounce, and reasonably concludes they have reported the issue and started a disclosure clock. Then nothing happens — not because we ignored them, but because the mail went nowhere. They believe they have disclosed responsibly and we never knew. That is strictly worse than having no security address at all.
So the file lists the address we know a human reads. It also says, in the file itself, that we have no bug bounty and no published key, rather than implying otherwise by omission. The general rule: a channel that silently drops mail is worse than no channel, and the same reasoning applies to any control that fails quietly — which is most of them.
23 July 2026Build integrity
A build succeeded for two days without compiling anything we changed
An application package built on one day was shipping compiled engine binaries dated two days earlier. Two days of work absent from a "fresh" build, with nothing anywhere reporting a problem. The packaging step repackages whatever compiled library already sits in the output folder and has no way of knowing the source changed.
It is not the only tool here that succeeds loudly while doing nothing. The cross-compiler has printed a hard compile error and exited zero. A code generator reads interface information out of a library's symbol table, and our release profile strips symbols — so pointed at a stripped library it finds nothing, writes nothing, prints nothing, and exits zero. This machine's clock has jumped backwards, after which the build system skips work it believes is already done, and a green test run becomes a report about code that was never compiled.
What all of these share is that the failure is indistinguishable from success at the place you would normally look. So the rule is: do not look there. Check the artefact — its size, its timestamp, its hash, and whether it contains the thing it was supposed to produce. "Build successful" is not evidence that a build happened; it is evidence that nothing errored, and those are different claims.
The general form is worth keeping because it reaches well past builds: a verification step that can fail open is not a verification step. Before trusting one, ask what it would print if it did nothing at all — and if the answer is "the same thing", fix the check before using it.
21 July 2026Claims
Nine security claims were false, and every one had a passing test
A single day's review found nine places in this codebase where a comment, a document or a test asserted a security property the code did not have. Not one was carelessness. Every one had a test that "confirmed" it.
They share one shape, and it is the shape that survives ordinary review: a test asserted a surface property that the security property implies, instead of the security property itself. A return code instead of an invariant. Byte inequality instead of unlinkability. A field's presence instead of its verification. Every assertion was true. Every one was about the wrong thing.
Some worked examples, with what the attacker actually did:
- "Re-registering a queue is idempotent" — the test checked the call returned success. The attacker took over the queue, wiped the mail, and killed the relay.
- "Group blocks are uncorrelated" — the test checked two ciphertexts differed. The attacker correlated them by arrival time without reading a byte.
- "Messages disappear" — the test checked the row count dropped. The attacker read the plaintext out of the write-ahead log.
Three rules came out of it and they now govern this website as well as the code. Name the adversary next to the claim — never "the relay cannot learn X", but who cannot learn it, from what they observe. Assert the property, not a proxy for it — a test about bytes cannot speak to a property about timing. Watch the test fail before you trust it, and confirm the break you introduced was actually applied, because a no-op edit and a correct test produce identical output.
There is a mirror failure worth naming too, because it is easy to write while fixing the first kind: a test that asserts more than the mathematics gives. One of ours did, and the code was right and the author was wrong. The tell was that the adversary had not been named before the assertion was written.