lwis docs

Security / Verifying releases

Verifying releases

Every lwis release is signed with an offline minisign key, and the key is published in three independent places. This page shows how to check a download yourself.

What is signed

FileURL (latest)Signature
lwis binary, Linux x86_64/download/lwisd/x86_64-linux/download/lwisd/x86_64-linux.minisig
Android app/download/apk/download/apk.minisig
Firefox extension/download/xpi/download/xpi.minisig (and Mozilla's own signature)
Release manifest/download/manifest.json/download/manifest.json.minisig
Agent SKILL/SKILL.md/SKILL.md.minisig
Web app manifest/capture/release.json/capture/release.json.minisig

Every release also keeps a permanent URL: /download/v/<version>/apk, /download/v/<version>/lwisd/x86_64-linux and so on. The release manifest lists the version, the commit, and the SHA-256 of the APK and the binary.

Versions look like 0.11.0+ab12cd3: the release number plus the commit it was built from. Release tags are SSH-signed.

The signature's trusted comment names the file and the version, for example lwis dev 0.11.0+ab12cd3 apk. It is covered by the signature, so a valid signature over an older file can't pass for a newer one.

The release key

The minisign public key, key id 4CFFBB488D579337:

RWQ3k1eNSLv/THHxZ9vd5wWyQEfYXQObusc6FSR7DLDV8tWIbAIhJlAv

You can find it in:

install.sh embeds the key too, but it's served by the website, so it doesn't trust itself: it fetches the DNS record over DNS-over-HTTPS from both Cloudflare and Google, requires both to answer the same, and refuses to install on any mismatch. An attacker would need to control both the website and the DNS zone.

The DNS zone is not DNSSEC-signed. DNS-over-HTTPS protects the path to the resolver, not the resolver's view of the zone. Comparing with the key above, or with the one in the repository, is the strongest check.

DNS records

All four are TXT records under lookwhatisee.xyz:

NameContent
_lwis-releasev=lwis1; minisign=<public key>; keyid=<key id>
_android._lwis-releasev=lwis1; apk-cert-sha256=<hex>: the APK signing certificate
_latest._lwis-releasev=lwis1; version=<version>; sha256-x86_64-linux=<hex>; sha256-apk=<hex>: moved by every release
_snippet._lwis-releasev=lwis1; share-open-sh-sha256=<hex>: the recipe printed by share links

Read them with dig, or over DNS-over-HTTPS:

dig +short TXT _lwis-release.lookwhatisee.xyz
curl -s -H 'accept: application/dns-json' \
  'https://dns.google/resolve?name=_latest._lwis-release.lookwhatisee.xyz&type=TXT'

Check the lwis binary

install.sh and lwis self-update do all of this for you. By hand:

curl -fsSLO https://lookwhatisee.xyz/download/lwisd/x86_64-linux
curl -fsSLO https://lookwhatisee.xyz/download/lwisd/x86_64-linux.minisig
minisign -Vm x86_64-linux -P RWQ3k1eNSLv/THHxZ9vd5wWyQEfYXQObusc6FSR7DLDV8tWIbAIhJlAv
sha256sum x86_64-linux   # compare with sha256-x86_64-linux in _latest._lwis-release

install.sh also verifies with OpenSSL 3 when minisign isn't installed. LWIS_INSTALL_SKIP_DNS=1 skips the DNS check for offline installs, with a warning; compare the printed key yourself then.

lwis self-update never takes a key from the server. It verifies with the key compiled into the running binary, requires the DNS key to be the same (--skip-dns to bypass), requires the signed comment to name the right file and a newer version, checks the SHA-256 against the DNS record when it names that version, runs the new binary's --version, and only then replaces itself (the previous binary stays as lwis.old). A change of release key therefore means reinstalling with install.sh.

Check the Android app

For a first install, check the APK before you open it:

curl -fsSL -o lwis.apk https://lookwhatisee.xyz/download/apk
curl -fsSL -o lwis.apk.minisig https://lookwhatisee.xyz/download/apk.minisig
minisign -Vm lwis.apk -P RWQ3k1eNSLv/THHxZ9vd5wWyQEfYXQObusc6FSR7DLDV8tWIbAIhJlAv
apksigner verify --print-certs lwis.apk

The signer's certificate SHA-256 must be:

0e88d987a52a7a8a6f4a2926a3767633cdd8b68adf023148544e03d6331a9bc6

It's also in the _android._lwis-release record and in the release manifest (apk.cert_sha256).

After the first install, Android itself checks every update against the installed app's signing key. The release key is an offline RSA 4096 key that never touches the build server.

Upgrading from 0.8.2 or older. Those releases were signed with a public debug key. Newer releases carry an APK Signature Scheme v3 rotation from that key to the release key, so your app updates in place and keeps its pairings. Once your phone runs a rotation-signed build, Android refuses any update signed with the debug key alone. Until then it would accept one, so only update from lookwhatisee.xyz.

Reproducible builds

The published lwis binary is built with Nix: static musl, pinned toolchain, crates pinned by hash, fixed timestamps and build paths. Anyone with the source repository and Nix can rebuild a release from the commit named in its version and compare:

scripts/rebuild-check 0.11.0+ab12cd3

It builds the commit in a clean worktree, builds it a second time to check that the two are identical, then compares the SHA-256 with the published binary and, when it names that version, the DNS record (if dig is installed). --no-check skips the second build. Exit code 0 means identical.

The APK build is deterministic too, but it bundles the web app, whose npm build isn't pinned bit for bit yet, so the APK has no end-to-end rebuild check.

The SKILL and the web app