<!-- The markdown twin of https://kleidos.dev/roadmap/, converted from that page at build time. The page is the original; this is the same words with the markup taken off. The site was last changed on 2026-10-25. -->

# Roadmap

Three things, in this order: where each part of the project stands and what shows it, what is left and why it has to happen in the order shown, and what I decided against.

There are no dates on this page. I announce none, because a date is the first thing on a roadmap to stop being true, and because I work on this alone, which would make any date I gave you a guess in a nice font.

On this page

1. [Status](https://kleidos.dev/#status)

2. [What comes next](https://kleidos.dev/#next)

3. [Dropped](https://kleidos.dev/#dropped)

## Where the project is

The whole of it, honestly: what works, what is being built, and what has not been started. Each row names what makes it true.

Working

### One firmware, nine boards

Every board registers its own hardware (its screen, its power controller, its buttons or keyboard) into the same firmware, and the drawings in “The devices” are generated from the same measured boards (src/boards/).

Working

### The encrypted vault on the device

Entries are stored as encrypted, authenticated records, with separate keys for encryption, authentication and the unlock check, and the host test suite covers the round trip and the refusals (src/vault/).

Working

### Unlocking, on every kind of input

The two-button boards take a PIN dialed on the timing arc, the boards with a keyboard take a passphrase, and the touch boards offer either (src/ui/entry/).

Working

### Typing over Bluetooth

The device becomes a Bluetooth keyboard for the session it is told to start, types over a bonded and authenticated link, and shuts the radio down again when the session ends (src/ble/).

Working

### Firmware updates from the browser

On the boards with room for two app partitions, firmware is uploaded from the admin panel; an image built for another board, or an older version, is refused, and an update that boots but never confirms itself rolls back on its own (src/platform/ota.cpp).

Being built

### The admin panel

In development. It is already where entries are added, imported from another manager and exported as an encrypted backup, and it is still changing (src/web/).

Being built

### The hardened build

In development. Secure Boot, flash encryption and encrypted settings are configured for the 8 MB boards; on the ESP32-S3 ones the per-chip key is burned by a step that exists now and that I have not run on a board yet (docs/security/secure-provisioning-checklist.md).

Not started

### The power figures, measured at the cell

The awake figures are read off each board's own power controller and published on the measurements page; every sleep figure is still an estimate. I have not made the bench measurements that would replace those estimates with readings from a meter (docs/testing/production-sleep-checklist.md).

Not started

### The long end-to-end run

The release criteria ask for 50 end-to-end cycles (boot, unlock, browse, type, sleep) with no crash. I have not run them, and I have ticked no other box on that list either (docs/QA_CHECKLIST.md).

Not started

### The first release

There is nothing to install yet: the firmware is at version 0.1.0, no release is published, and the Overview page says “Coming soon” because that is what it is (version.txt).

Not started

### Flashing a board from the browser

The page that would install the firmware over USB from a browser is written and parked: it has no release to offer, so this site publishes nothing at the page's address, and its own manifest lists no build (web/).

Not started

### A review by somebody else

I have not had the firmware or the cryptography reviewed by anybody outside the project. The dossier an auditor would start from is written and waiting (docs/security/audit-dossier.md).

I put no dates on this list. It names only work that exists in the repository, and a step that nothing there can show is listed as not started.

## What comes next, and why in that order

There are eight steps left. The order is the part worth disagreeing with: each one names what cannot start until it is done.

### Step 1. Finish the admin panel

The browser panel is how a vault gets filled. Of the nine boards, only three have a keyboard, so on the other six, typing a password into the device by hand is not a realistic way to add one. The panel works; it is not finished.

#### Waiting on it

Anything anybody would call a first release. There is no point shipping a vault that is hard to put anything into.

src/web/

### Step 2. Make the hardened build able to create a vault

The hardened build is the one with verified boot, encrypted flash, and the key derived from the PIN mixed with a secret only that device holds, which is what makes guessing a PIN offline pointless. On the classic ESP32 boards that secret is a random value in the encrypted settings; on the ESP32-S3 boards it is a key in the chip's fuses, burned from the computer in a separate step before the first hardened boot, and the upload refuses to go ahead without it. The step exists, but no board has been through it yet.

#### Waiting on it

Everything the security page says the hardened build does. Right now the strongest build is the one I cannot hand to anybody.

docs/security/secure-provisioning-checklist.md

### Step 3. Open the repository

It is private while I prepare the first release. A pass over the history has to come first, and so does deciding how an incoming contribution is licensed: that has to be settled before the first one is merged, not after.

#### Waiting on it

The security page, which asks you to believe twenty threats and forty-five controls you cannot read. And the review at the end of this list, because nobody audits what they cannot open.

CONTRIBUTING.md

### Step 4. Measure the current at the cell

Every sleep figure the project publishes is an estimate. The awake ones are real: they come off the board's own power controller, on its own battery. But the device cannot measure its own sleep in any useful time, so this needs a meter, a bench and an afternoon.

#### Waiting on it

A battery-life figure of any kind. Today the measurements page gives none and says why on every row.

docs/testing/production-sleep-checklist.md

### Step 5. Run the fifty cycles

Fifty unlock-and-type cycles end to end, on each board, without a crash. Parts of it are automated; I have never run the whole of it.

#### Waiting on it

The release. Firmware that has not been left running for a day is firmware whose first long day happens on somebody else's desk.

docs/QA_CHECKLIST.md

### Step 6. Cut the first release

A version, a tag, and one image per board. Nothing to download exists today, which is the first line of the limits list on the overview.

#### Waiting on it

The installer below, which has nothing to serve until this exists.

version.txt

### Step 7. Publish the browser installer

A page that flashes a board over USB from a browser, with no toolchain to install. It is written, and parked in the repository, pointed at a release that is not there.

#### Waiting on it

Somebody using this who is not me. That is the whole purpose of it.

web/

### Step 8. Have somebody outside the project read it

The threat model, the control matrix and the dependency register were written here, by the person who wrote the code they describe. That is worth something, and it is not a review.

#### Waiting on it

Nothing, and it is last on purpose: an audit of a moving target is an audit of nothing, so it waits until the steps above have stopped moving the target.

docs/security/audit-dossier.md

None of this is a commitment about when. The list above is where a step changes state, and the feed publishes the change on the day I type it.

## What I decided against

A list of everything somebody would like is not a plan: a plan is partly the shape of what it leaves out. These are nine things this project will not do, each with the reason written down where you could check it, and four of them settled by a measurement rather than by taste.

### A memory-hard key derivation

Argon2id or scrypt in place of PBKDF2. Measured against published figures for a current graphics card: scrypt, in the memory these chips have, makes the attacker faster rather than slower. The best Argon2id configuration an ESP32-S3 could hold buys about five times the cost, and asks in exchange for a hand-written memory-hard primitive outside the platform's crypto API, a different derivation on the boards with no external RAM, and key-derived state sitting in a memory you can put a probe on. Two more PIN digits buy a hundred times.

docs/research/kdf-performance.md

### The 600,000 iterations OWASP recommends

For the unlock, that is about 5.3 seconds on the ESP32-S3 boards and 24 on the classic ESP32 ones. The unlock is sized to roughly two seconds instead: 175,000 iterations and 40,000. The backup file does use 600,000, because nobody stands in front of the device waiting for a backup.

docs/research/kdf-performance.md

### Passkeys over Bluetooth or over the network

Somebody asked for it, and I researched it before answering. The Bluetooth transport for security keys is Windows-only, needs an operating-system pairing of its own, and has unresolved reports of not working; Chromium removed its support for it in 2020. No browser and no operating system reaches an authenticator over a local network at all. The one path that works on every host is USB, which only the four ESP32-S3 boards could offer, so it would be a headline feature that five of the nine boards do not have.

docs/research/passkeys-feasibility.md

### Correcting the clock

Three boards have no clock chip and keep time through a sleep on an internal oscillator. Across 66 measured sleeps the error ran from 6,033 parts per million behind to 2,300 ahead; each chip carries an offset of its own, and the die temperature moves the error more than anything else does. No correction that fits that spread would hold a one-time code inside its one-step tolerance, so none is applied: a code on those boards asks for a fresh sync instead.

docs/research/slow-clock-sleep-matrix.md

### A hardware counter for the failed attempts

It would make the wrong-PIN counter impossible to roll back by restoring an older flash image. The chip's one-time-programmable counters allow a handful of writes in the whole life of the part, and this value is rewritten on every wrong PIN. It is filed as an accepted risk, not as a postponed feature.

docs/security/audit-dossier.md

### Verified boot on the 4 MB boards

Two update slots and a signed bootloader do not fit in 4 MB of flash. Three boards (the Plus 1.1, the Plus SE and the CoreInk) are re-flashed whole instead, and the device grid on the overview says which boards have a hardened build and which have none.

docs/variants.md

### A crash dump in a shipping build

It is the obvious way to find out why a device rebooted. A crash dump is a copy of the task stacks, and at the moment of a crash those can hold the PIN and the vault key. Development builds have it; no production or hardened build enables it, and a build check fails if one ever starts to.

docs/security/serial-silence-and-crash-records.md

### The ready-made libraries for the hardware

Nine boards and two chip families build from one source tree with no Arduino core in it. Every panel, power controller, clock chip, motion sensor, touch panel and keyboard is driven by a driver written in this repository, and the graphics library the whole hobby ecosystem uses was removed from the entire fleet, including from the one board it was still drawing for.

src/drivers/

### A content security policy on this site

This page has no sign-in, no input and no session, so a policy would have nothing to protect, and the element that carries one is what stops the host's own bot protection from working. What survived its removal is the rule it kept being confused with: no inline script, no inline style, no event handler in the markup, and nothing on this page loaded from another site. The guard refuses a deploy that breaks any of those four.

scripts/site/check_publish.py

Any of these could be reopened by a better argument than the one that closed it. That is what the reasons are for.
