diff options
| author | Adam Soltys <[email protected]> | 2026-09-04 19:04:41 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-04 20:12:58 -0700 |
| commit | e63f86a603ebae0e7c66f94bfdea69905f36f80c (patch) | |
| tree | fa0d940c8aa615119bf60eed85d827ba1c0b9266 /src/routes | |
| parent | a9ae409528219d56a9fbab5dca8a1ad3af68a106 (diff) | |
| download | coinos-ui-e63f86a603ebae0e7c66f94bfdea69905f36f80c.tar.gz coinos-ui-e63f86a603ebae0e7c66f94bfdea69905f36f80c.zip | |
pos: refuse to flash anything that is not a valid LittleFS image
The flasher hex-encoded whatever the /flash endpoint returned, so a
server error body was written to devices as the config partition.
Check the response status, require a 128KB image with the LittleFS
magic, and surface the error above the form instead.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_014gVYGi29FjTeLdNTiutkZx
Diffstat (limited to 'src/routes')
| -rw-r--r-- | src/routes/(app)/pos/+page.server.ts | 28 | ||||
| -rw-r--r-- | src/routes/(app)/pos/+page.svelte | 13 |
2 files changed, 40 insertions, 1 deletions
diff --git a/src/routes/(app)/pos/+page.server.ts b/src/routes/(app)/pos/+page.server.ts index 1df1bb33..1e3e1e99 100644 --- a/src/routes/(app)/pos/+page.server.ts +++ b/src/routes/(app)/pos/+page.server.ts @@ -1,6 +1,18 @@ import { PUBLIC_COINOS_URL } from "$env/static/public"; import { auth, fd, get } from "$lib/utils"; import { hex } from "@scure/base"; +import { fail } from "@sveltejs/kit"; + +const LITTLEFS_SIZE = 0x20000; +const LITTLEFS_MAGIC = "littlefs"; + +// A valid image is exactly one partition long and carries the LittleFS +// superblock magic at byte 8. Anything else (e.g. a JSON error body from +// the server) must never reach the flasher. +const isLittleFS = (buf: Uint8Array) => + buf.length === LITTLEFS_SIZE && + new TextDecoder().decode(buf.subarray(8, 8 + LITTLEFS_MAGIC.length)) === + LITTLEFS_MAGIC; export async function load({ cookies }) { let token; @@ -25,6 +37,22 @@ export const actions = { }); const buf = new Uint8Array(await res.arrayBuffer()); + + if (!res.ok) { + let message = `Config server returned ${res.status}`; + try { + message = JSON.parse(new TextDecoder().decode(buf)).message || message; + } catch (e) {} + return fail(res.status >= 400 && res.status < 600 ? res.status : 500, { + error: message, + }); + } + + if (!isLittleFS(buf)) + return fail(500, { + error: `Config server returned an invalid image (${buf.length} bytes)`, + }); + return { bytes: hex.encode(buf) }; }, }; diff --git a/src/routes/(app)/pos/+page.svelte b/src/routes/(app)/pos/+page.svelte index 871c355d..36869fba 100644 --- a/src/routes/(app)/pos/+page.svelte +++ b/src/routes/(app)/pos/+page.svelte @@ -30,7 +30,11 @@ let portInfo = $state(""); // --- Existing config path (generated server-side to hex in form.bytes) --- - let bytes = $derived(form ? hex.decode(form.bytes) : undefined); + let bytes = $derived(form?.bytes ? hex.decode(form.bytes) : undefined); + let configError = $state(""); + $effect(() => { + configError = form?.error || ""; + }); let littlefsAddress = $state(LITTLEFS_ADDRESS_DEFAULT); let configProgress = $state(0); let configDone = $state(false); @@ -68,6 +72,10 @@ let flashConfig = async () => { if (!esploader || !bytes) return; + if (bytes.length !== 0x20000) { + configError = `Refusing to flash: image is ${bytes.length} bytes, expected ${0x20000}`; + return; + } configDone = false; configProgress = 0; @@ -175,6 +183,9 @@ {#if configProgress > 0}<div class="mt-2">Progress: {configProgress}%</div>{/if} {/if} {:else} + {#if configError} + <div class="text-red-600 mb-2">{configError}</div> + {/if} <form method="POST" use:enhance class="space-y-2"> <input name="ssid" placeholder="Wifi SSID" class="input" /> <input name="key" placeholder="Wifi password" class="input" /> |
