The short version
A transfer has no network attack surface, because it uses no network. The only connection in Air Kalon is the Mac app's update check, which installs nothing that isn't signed by Kalon's developer and notarized by Apple. Air Kalon does not encrypt the stream. Anyone who can see the sender's screen clearly enough can capture the whole file. Treat a transfer the way you would treat holding a printed page up to someone across a room.
What never happens
- Transfers involve no network. Nothing is uploaded, synced or reported, and there is no server, account or analytics. The Mac app's one connection is its update check, described below.
- No radio is used for the transfer. Wi-Fi, Bluetooth and cellular can all be off.
- The only permission the apps ask for is the camera: on the receiver while receiving, and on the sender only while a stream is on air with auto-stop switched on.
Updates on the Mac
Air Kalon for Mac keeps itself up to date from its GitHub releases. It reads a small file describing the newest release twice a day and when you choose Check for Updates…, sending nothing but the version you have, over HTTPS only. With Update automatically turned off, in the Software Update window, Air Kalon makes no connection at all until you choose Check for Updates….
An update is installed only if every check passes:
- The download matches the size and SHA-256 published with the release.
- Every name in the archive sits inside Air Kalon's own app, with no absolute paths, no
..and no links, checked before anything is unpacked. - The app inside is Air Kalon, newer than the copy installed, signed with Kalon's Developer ID and notarized by Apple, under a strict check of all of its code.
- The same checks run again just before the swap, which happens as Air Kalon quits, in one step, so there is never a moment without a working app.
A tampered download site can at worst hold an update back; it cannot get anything else installed. The iPhone and iPad app does not check for updates.
What an onlooker can capture
The frames are plaintext. A camera that can read the sender's screen, from a phone, a security camera or a reflection, can run the same decoder and recover:
| Item | Where it travels |
|---|---|
| File contents | Every frame |
| File name, media type and kind (file, snippet or bundle) | Inside the KCF1 container, in the clear |
| Original and transmitted size | Container header and every frame header |
| SHA-256 of the file | Container header |
| Session, progress and completion | Frame headers, and the receiver's acknowledgement code |
Every frame header reserves a cipher suite byte for future encryption, but no encryption is implemented. A frame with a nonzero suite is recognised and reported as unsupported. The specification already fixes that any future suite must authenticate the whole 24-byte header. The plan document (docs/PLAN.md) describes a planned ChaCha20-Poly1305 suite; it is not available today.
Integrity
Every transfer, of any size, passes two checks before a file is offered:
| Check | Algorithm | Covers | Purpose |
|---|---|---|---|
| Stream hash | FNV-1a, 32-bit | The whole KCF1 container | Detects accidental corruption in decoding |
| File digest | SHA-256 | The original file bytes, after decompression | Must match or the transfer fails and nothing is offered |
The SHA-256 covers the file bytes only. The name, media type and flags are covered by the stream hash, which is a transport check rather than an integrity guarantee, and the receiver re-sanitises the name whatever it says.
Neither check authenticates the sender. Anyone who controls what the sender's screen shows, or who puts their own screen in front of your camera, can send any file with a matching digest. Air Kalon proves that the bytes arrived as they were sent, not who sent them.
Defences in the receiver
The receiver treats every decoded code as untrusted input.
- Strict parsing. Every frame format has a fixed validation order, and anything that fails is ignored with no change of state.
- Bounded allocation. A decoder never locks on to a first frame claiming more than 256 MiB, or one whose block count disagrees with its lengths. The container never declares more than 256 MiB for the file or the body.
- Quorum before a switch. A frame from a different stream is buffered until three distinct frames of that stream arrive within two seconds. One hostile code cannot reset a nearly complete transfer. Pending candidates are capped at eight streams.
- Bounded restarts. More than three same-session restarts in sixty seconds stops the receiver switching and reports a conflict.
- Bounded decompression. Inflation never produces more than the declared original length plus one byte, output grows in 1 MiB steps, and trailing bytes after the DEFLATE stream are rejected.
- Refuse, do not guess. A payload that is not a verifiable KCF1 container is refused with its reason. There is no raw fallback, so nothing unverified is ever offered.
- Safe file names. The name is reduced to its last path component, control characters and the characters
/,\and:are removed, leading dots and spaces are trimmed, and an empty name becomesfile. The receiver applies this rule again when it reads the container, and the apps take only the last path component once more when writing. - No automatic actions. A received link opens only when you tap Open. Only
httpandhttpslinks qualify, links with a user name or password are refused, and the button names the host first.
The acknowledgement channel
The receiver's acknowledgement code is also plaintext. It shows the session, the progress and the payload hash to anyone who can see the receiver's screen.
A sender honours an acknowledgement only when its mode, session and payload hash all match the live stream, and stops at most once per stream. Another device's acknowledgement in view cannot stop your transfer. Someone who can read your stream could, in principle, show a forged done acknowledgement to your sender's camera and stop it early; the effect is that the sender stops streaming, and the receiver simply does not finish.
What is stored
| Where | What |
|---|---|
| Mac | Received files are written only where you choose in the save panel |
| iPhone and iPad | A received file is written to the app's temporary directory and handed to the share sheet |
| Both | Stream settings and receiver experiment switches, in the app's preferences |
There is no transfer history. Sent files are read, not copied, except that the iPhone and iPad app keeps a temporary copy of an item dragged onto the Send screen until the next drag.
Physical considerations
- Shield the sender's screen from other cameras when the content is sensitive.
- Close the transfer by pressing Stop, or with auto-stop, so the stream is not left playing.
- Check that the name and size on the completion screen are what you expected before you open a file.