Skip to content

os/v3/puppy: Lemonade boots with no API key on a dd-written stick (05onboard-data pre-creates lemond.conf, so 10lemonade never mints) #867

Closed

Description

@bong-water-water-bong

A stick written with dd alone boots Lemonade on 0.0.0.0:13305 with no API key at all, and it keeps
booting that way: 10lemonade's "mint one on first boot" never fires because 05onboard-data has already
created the file empty.

05onboard-data runs first (name order 05 < 10) and does:

[ -f "$DATA/lemonade/lemond.conf" ] || : > "$DATA/lemonade/lemond.conf"
chmod 600 "$DATA/lemonade/lemond.conf"
ln -sfn "$DATA/lemonade/lemond.conf" /etc/1bit/lemond.conf

so by the time 10lemonade runs, its guard

if [ ! -f "$CONF" ]; then   # mint a key

is false for an empty file, . "$CONF" exports nothing, and lemond starts unauthenticated.

Measured on the cycle-13 image in QEMU (~/wt/puppy-out-c13/1bit-os-puppy.img, sha256 dcc619ff…),
booted from a plain dd copy with no prepare-stick:

# /data/lemonade/lemond.conf: 0 bytes, /etc/1bit/lemond.conf -> it
# sed -n 's/^LEMONADE_API_KEY=//p' /etc/1bit/lemond.conf | wc -c   -> 0

/var/log/lemond-onebit.log:
[Warn] (Server) Serving on non-loopback host '0.0.0.0' without an API key. All endpoints, including the
       /internal/* control endpoints, are reachable from other machines unauthenticated. ...

# GET /api/v1/models with no Authorization header -> 200   (same on the LAN: the firewall opens 13305)

Second boot, same stick: still 0 bytes, still 200 without a key. So it is not a first-boot race.

Writing a key by hand and restarting works, which is what the docs will tell users to do:

# printf 'LEMONADE_API_KEY=%s\n' "$key" > /data/lemonade/lemond.conf; /etc/init.d/10lemonade restart
# no key -> 401, wrong key -> 401, right key -> 200, /v1 no key -> 401

Fix

Either mint on emptiness (test [ ! -s "$CONF" ], or test for a LEMONADE_API_KEY= line) in
10lemonade, or let 05onboard-data mint it when it creates the file. The comment in 10lemonade says
"so the appliance is never open without it" — with the current ordering it always is, on any stick that
did not go through prepare-stick --mint.

Two related notes for the same lane:

  • prepare-stick --mint is what saves the shipped protocol, so docs/install.md (this lane) now says
    plainly that a dd-only stick has no key and that Lemonade logs the warning.
  • Worth deciding whether the appliance should refuse to bind 0.0.0.0 with no key rather than warn: the
    firewall opens 13305 to the whole LAN by design (zz-onboard-ports), so the warning is the only thing
    between a dd-written stick and an open LLM on the network.

Found while verifying docs/clients.md and docs/install.md (lane pi-rlk); transcript in
~/wt/rlk-vm/results.txt (phases 1 and 2b).

Activity

  1. added
    bugSomething isn't working
    os1bit OS image, installer and appliance console (os/mini, os/v2, os/v3)
    on Nov 3, 2026
  2. bong-water-water-bong commented on Nov 3, 2026

    @bong-water-water-bong
    CollaboratorAuthor

    Triaged at origin/main 692cfa25c, verified on PR #828's branch head 5a310e89 (the init scripts are on that branch only). Confirmed exactly as reported, and the blast radius is larger than the warn line suggests.

    05onboard-data:66   [ -f "$DATA/lemonade/lemond.conf" ] || : > "$DATA/lemonade/lemond.conf"     <- creates it EMPTY
    05onboard-data:68   ln -sfn "$DATA/lemonade/lemond.conf" /etc/1bit/lemond.conf
    10lemonade:43       if [ ! -f "$CONF" ]; then   # mint a key  -> false for a 0-byte file
    10lemonade:52-53    . "$CONF" ; export LEMONADE_API_KEY        -> exports nothing
    

    05 < 10 is name order, so the file always exists before the guard runs. The guard tests for the file, the failure is an empty file — [ ! -s "$CONF" ] (or a ^LEMONADE_API_KEY= grep, which is the idiom os/mini/prepare-stick.sh:254 already uses) is the whole fix.

    Two facts the report does not state, both from the branch

    1. The only mint in the tree is prepare-stick.sh's, and it lives in a different module: git grep -n 'LEMONADE_API_KEY' pr828 -- os/v3/puppy finds 10lemonade:27,47,51,53 and README/vm-check rows and no build-time mint — os/v3/puppy/mkimage.sh never writes a key. The mint is os/mini/prepare-stick.sh:74,248-278. So "a dd-written stick has no key" is not an edge case: it is every stick that did not go through prepare-stick --mint, and 10lemonade:27's own header ("minted at build time, or here on first boot") describes a build step that does not exist in the os/v3 image. Either mint in 05onboard-data (the writer owns the file) or add the build-time mint to mkimage.sh — but the first-boot mint must be repaired either way, because the file being empty is the state a fresh image ships in.
    2. The reachability is real, not theoretical: zz-onboard-ports:26-36 inserts iptables -I INPUT 1 -p tcp --dport 13305 -j ACCEPT after Puppy's -P INPUT DROP, precisely so the appliance answers on the LAN (U3). With no key, lemond serves /api/v1/* and the /internal/* control endpoints to every machine on that LAN unauthenticated. That is not "a warning at boot"; it is an open model + admin API on the network, on the default path.

    So my reading of the last question in the report: fail closed. Mint on emptiness (cheap, keeps U3 working and keeps the key out of the image), and if a key still cannot be minted, bind 127.0.0.1 rather than warn and bind 0.0.0.0 — an appliance whose U3 row fails is a bug report; an unauthenticated control plane on a LAN is a security report. If you would rather decide that policy than take it from me, the mint fix alone closes this issue and the binding question becomes its own row.

    Next action: [ ! -s "$CONF" ] in 10lemonade (or mint in 05onboard-data:66, which is where the file is created), plus one vm-check.sh row that a fresh dd image answers 401 without a key — the existing U3 key row at vm-check.sh:371 greps /etc/1bit/lemond.conf for a key line, which a 0-byte file... let me not guess that one: it is in the branch and the c13 boot's own vm-check transcript in ~/wt/rlk-vm/results.txt is where to read whether it passed vacuously. That row is the check this bug needs and it is worth reading before fixing. Owner pi-os3; bucket A (closes on #828 or a stack of it).

  3. bong-water-water-bong commented on Nov 3, 2026

    @bong-water-water-bong
    CollaboratorAuthor

    Correction to the last paragraph above, now read from the branch instead of hedged — and the answer is the interesting one:

    vm-check.sh:371-373 is the right row already:

    ("U3 key", "grep -q '^LEMONADE_API_KEY=..' \"$(readlink -f /etc/1bit/lemond.conf)\" && echo key-on-data", "key-on-data"),
    ("U3 catalogue", "k=$(sed -n \"s/^LEMONADE_API_KEY=//p\" /etc/1bit/lemond.conf); curl -s -H \"Authorization: Bearer $k\" … | grep -o onebit | wc -l", None),
    ("U3 api local", '… curl -s -o /dev/null -w "http=%{http_code}" -H "Authorization: Bearer $k" …', "http=200"),

    On a keyless boot U3 key fails (no key-on-data), and U3 api local would still print http=200 — because an unauthenticated lemond answers 200 to a request that carries a bogus Bearer header. So the row that would have caught this is not vacuous; it simply never saw the case, because the gate's own path (mkimage.sh → prepare-stick.sh --mint) always mints before it boots. That is the third instance this week of the same shape (with #709's secureboot SKIP and #866): the gate does not exercise the artifact the user has.

    So the verification for the fix is two rows, both already expressible in vm-check.sh's table: boot a plain dd copy (no prepare-stick), and assert U3 key → key-on-data and an unauthenticated GET /api/v1/models → http=401. The second one is the row the table is missing today.

  4. bong-water-water-bong commented on Nov 4, 2026

    @bong-water-water-bong
    CollaboratorAuthor

    Re-adjudicated after #934 (4b5ef507, 03:03Z) and #936 (e8bd2b30, 03:43Z) merged — the fix this issue asks for has
    been reversed by an owner decision, so the row's action is now a one-word close, not a mint.

    The owner's cycle-20 decision (~20:1x, quoted in #934's body): the appliance ships like stock Lemonade, no
    LEMONADE_API_KEY by default
    , and a key is something a person turns on. Verified at main:

    os/v3/puppy/embed/etc/init.d/10lemonade:46-60   no mint at all; keyed mode when a key LINE exists, blank mode otherwise
                                                    an EMPTY assignment still counts as none  <- this issue's third state, still refused
                                                    the mode is logged
    os/v3/puppy/embed/usr/local/bin/1bit-webapp-proxy:73-77   Origin that is not the loopback page -> 403, before the key is attached
    os/mini/release-check.sh   the lemonade row asserts the MODE's contract (blank: 200 + model list with no auth header;
                               keyed: 401 without the key, 200 with it) + 5 self-test cases
    os/v3/puppy/vm-check.sh    U3 key / U3 auth / U3 webapp key / data key follow the mode; new U3 proxy row (cross-origin=403)
    

    The premise I recorded here ("05onboard-data pre-creates an empty conf so 10lemonade never mints") is still true of
    the c19 tree and is now moot by design: nothing mints, deliberately, and the empty-file trap this issue identified is
    what 10lemonade:52-58 explicitly refuses, so the class is guarded even though the mint is gone. In blank mode 13305
    stays open to the LAN — that is the decision, not an oversight (LAN clients are the point), which also answers this
    issue's closing question ("refuse to bind 0.0.0.0 with no key, or warn-and-bind"): warn-and-bind, with console item 1
    as the toggle and the proxy's Origin guard as the borrowed-key defence.

    So the two rows this issue said a fix needs now exist: release-check.sh asserts the shipped mode's contract, and
    vm-check.sh's U3 auth/U3 proxy rows assert it on a plain dd copy (prepare-stick.sh without --mint: 7/7
    PASS; vm-check.sh blank copy: 47 PASS / 0 FAIL / 3 SKIP).

    Owner: one word and this closes. Either close as superseded by the cycle-20 blank-key decision (my read — the
    issue's filed fix is the thing that decision removed), or say you still want a mint-on-first-boot default and I will
    re-scope the row to #936's image instead. I am not closing it myself: it is your behaviour decision, and the row is now
    bucket B (owner word), not A.

  5. bong-water-water-bong commented on Nov 6, 2026

    @bong-water-water-bong
    CollaboratorAuthor

    Resolved on main by the cycle-20 owner decision, verified at a083e31 — the fix this issue asked for is not a mint; the third state it named is refused and the shipped mode is the owner's.

    • os/v3/puppy/embed/etc/init.d/05onboard-data:67-73 no longer pre-creates an empty lemond.conf; the comment cites this issue and says why the rule stays: "a file that exists and is empty reads as 'keyed' to every script that greps for the line and as 'no key' to lemond (measured on a dd-written stick: 200 without an Authorization header, on the second boot too)". It creates the directory and the symlink only.
    • os/v3/puppy/embed/etc/init.d/10lemonade:43-62 has no mint at all (owner decision 2026-11-03): blank mode is the default and is logged, an empty LEMONADE_API_KEY= is treated as none, and keyed mode requires a real key line.
    • os/v3/puppy/embed/usr/local/bin/1bit-webapp-proxy:74-78 refuses a non-loopback Origin with 403 before any key is attached, so the open endpoint cannot be driven from a foreign page.
    • vm-check.sh's U3 rows are the gate: key-on-data fails on a keyless boot rather than the run passing, which is what this issue's comment established.

    Closing per the issue's own last comment ("the row's action is now a one-word close, not a mint"). The residual — an application-layer default of blank mode — is the owner's decision, not a defect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingos1bit OS image, installer and appliance console (os/mini, os/v2, os/v3)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions