leanos/REVIEW.md
Thomas Lutz Kolter ee203fd9fb leanOS: initial import (basiert auf NEON NOODLE OS)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-15 14:58:54 +02:00

12 KiB
Raw Permalink Blame History

Code Review — NEON NOODLE OS

Five-pass review. One pass per loop iteration. Findings: file, line, severity, fix. Critical/high issues are fixed directly; medium/low are logged for triage.

Pass Topic Status
1 Runtime / type / broken-import errors done
2 Logic bugs (races, edge cases, null checks) done
3 Security audit done
4 Error handling done
5 Consistency (naming, dead code, duplication) done

Pass 1 — Runtime / type / broken-import errors

Method: npx tsc --noEmit (strict), production vite build, import-resolution scan, manual read of the runtime-critical path (appRuntime.tsx Babel transform + new Function scope).

Result: no runtime, type, or import errors.

  • tsc --noEmit — clean, zero diagnostics.
  • vite build — succeeds (only the pre-existing >500 kB chunk-size advisory, not an error).
  • No project ESLint config present (ESLint not configured) — noted, not a defect.
  • All import specifiers resolve to existing modules; no dangling paths.
  • appRuntime.compileApp passes React + hooks + os into a new Function scope and memoises the built component per os instance — no obvious runtime fault in the happy path.

Findings

None at severity high or above. Observations deferred to later passes:

# File:line Sev Note Defer to
1.1 kernel/appRuntime.tsx:223 low Compile cache Map is never evicted; entries accumulate across self-heals (bounded by app count × versions). Pass 2
1.2 kernel/ai.ts:62 low currentProvider() exported but unused by callers (Settings reads aiProvider from the store). Possible dead export. Pass 5

No fixes applied in Pass 1 (nothing at critical/high).


Pass 2 — Logic bugs (races, edge cases, off-by-one, null checks)

Method: manual read of state/logic paths — store.ts (window mgr, openApp, installAndOpen), fs.ts, sdk.ts, bus.ts, appRuntime.tsx, AppHost.tsx, Window.tsx (drag/resize/snap), Terminal.tsx, Assistant.tsx.

Findings

# File:line Sev Issue Fix
2.1 apps/Terminal.tsx:84 (rm) high resolve("") returns cwd, so a bare rm (no operand) deletes the current directory — at / it wipes the entire virtual FS. Also affected cat/mkdir/touch (operated on cwd silently). Fixed — added missing-operand guards to rm, cat, mkdir, touch.
2.2 kernel/sdk.ts:32 medium ai.available was a one-time snapshot (aiAvailable()) captured when the window's os API is built (memoised per window in AppHost). Configuring the key in Settings afterwards left already-open apps (e.g. Terminal) reporting AI offline. Fixed — converted available to a getter returning the live value.
2.3 system-ui/Window.tsx:33 low Title-drag clamps Y to below the menu bar but never clamps X — a window can be dragged fully off-screen left/right with no way back. deferred (UX, low)
2.4 kernel/store.ts:274 low installAndOpen fuzzy match: a.name.toLowerCase().includes(query) can match the wrong app on very short queries (e.g. "a"). Intended fuzzy behaviour; acceptable. deferred (low)
2.5 kernel/fs.ts:65 (write) low write only auto-creates the parent if it's absent; if the parent path exists as a file, the child is written anyway (no "not a directory" error, unlike mkdir). deferred (low)
2.6 kernel/appRuntime.tsx:223 low (from Pass 1) compile cache Map never evicted. Bounded by app×version count; not a leak in practice. deferred (low)

Fixes applied: 2.1 (high), 2.2 (medium). Typecheck clean after changes.


Pass 3 — Security audit

Method: npm audit, dangerous-sink grep (innerHTML, dangerouslySetInnerHTML, eval, new Function, document.write), secret-handling trace (ai.ts/sdk.ts/db.ts), sandbox/blocklist review (appRuntime.tsx), dev-proxy review (vite.config.ts), .gitignore.

Good:

  • npm audit (prod deps) — 0 vulnerabilities.
  • No innerHTML / dangerouslySetInnerHTML / document.write anywhere — React auto-escaping intact; Terminal renders into <pre> (safe).
  • .env, .env.local are git-ignored — secrets not committed.
  • API keys are the user's own and sent only to the selected provider (correct auth headers per API shape).

Findings

# File:line Sev Issue Fix
3.1 kernel/appRuntime.tsx:25 medium Generated apps run via new Function in the page realm; the regex blocklist omitted indexedDB, so app code could open the fluid-os DB and read the settings → aiConfig store (all API keys) and exfiltrate them. Fixed — added indexedDB, globalThis, Function(, import.meta to the blocklist (defense-in-depth).
3.2 kernel/appRuntime.tsx:42 medium The blocklist is not a real boundary — generated code shares the realm and can still escape via deep prototype tricks ([].constructor.constructor, etc.). Documented inline. accepted by design (PROMPT.md: in-page execution). True isolation = iframe/worker — large refactor, deferred.
3.3 vite.config.ts:90 (/__ai/proxy) low (dev-only) Dev proxy forwards to any x-llm-url with the request's auth headers — an open proxy / SSRF + key-relay vector. Not present in production builds (no middleware ships). accepted (dev convenience; required for multi-provider CORS bypass).
3.4 vite.config.ts:9 (/__proxy) low (dev-only) In-OS browser proxy fetches arbitrary ?url= server-side and strips framing headers — SSRF in dev only. accepted (dev-only; intended for the browser app).
3.5 kernel/db.ts (settings store) low/info API keys persisted in IndexedDB in plaintext — inherent to a backend-less client app; keys never leave except to the chosen provider. Mitigated against in-page theft by 3.1. accepted by design.
3.6 kernel/ai.ts:100 info anthropic-dangerous-direct-browser-access: true enables direct browser calls to Anthropic — intentional for local dev. accepted.

Fixes applied: 3.1 (medium — blocklist hardening). Typecheck clean.


Pass 4 — Error handling (uncaught exceptions, missing try/catch, silent failures)

Method: traced every async path and entry point — store.init/installAndOpen/healApp, App.tsx boot, sdk.ts fs/ai, built-in apps (Terminal, Files, Assistant, AppBuilder), ai.ts chat/stream, the render-time AppErrorBoundary.

Already solid:

  • installAndOpen, healApp (store) — try/catch with user-facing notify. ✓
  • chat / chatStream (ai.ts) — throw with provider + status + body on non-2xx; callers catch. ✓
  • Terminal.run, Assistant.send — try/catch surfacing errors to the UI. ✓
  • AppErrorBoundary + self-heal catches render errors in generated apps. ✓
  • bus.emit — wraps each handler in try/catch. ✓

Findings

# File:line Sev Issue Fix
4.1 kernel/store.ts:100 + App.tsx:53 high init() had no try/catch and App calls void init(). Any failure in fs.seed/loadAiConfig/loadApps (e.g. IndexedDB blocked in private mode) left ready=falseOS stuck on the boot screen forever, silently (rejection swallowed by void). Fixedinit() now try/catches: on failure it boots with built-in apps + in-memory defaults, sets ready=true, and notifies "Storage unavailable". OS always reaches the desktop.
4.2 apps/Files.tsx:13,22,32,44,54 low refresh/open/save/create/del call os.fs.* without try/catch; an IndexedDB failure becomes an unhandled rejection (console-only, no UI feedback). Error boundary doesn't catch async. deferred (low — IndexedDB ops reliable; built-in app).
4.3 apps/Settings.tsx onSave low await saveAiConfig(cfg) not wrapped; a persistence failure would skip setSaved(true) and reject unhandled. deferred (low).
4.4 kernel/store.ts setTheme/setWallpaper low void setSetting(...) fire-and-forget swallows persistence errors silently (cosmetic settings only). accepted (low impact).
4.5 apps/Terminal.tsx (ai stream) low If os.ai.stream throws mid-stream, the catch reports it but the pre-pushed empty "out" line remains. Cosmetic. deferred (low).

Fixes applied: 4.1 (high — boot resilience). Typecheck clean.


Pass 5 — Consistency (naming, dead code, duplicated logic)

Method: export-usage grep (dead code), stale-string scan, magic-number/duplication scan.

Findings

# File:line Sev Issue Fix
5.1 kernel/ai.ts:62 low currentProvider() exported but never imported anywhere (Settings reads aiProvider from the store). Dead code. Fixed — removed.
5.2 Spotlight:148, Assistant:43, AppBuilder:33, Terminal:109, store:327 medium Five user-facing strings still said "set VITE_ANTHROPIC_API_KEY in .env" — stale since keys moved to Settings → Language Model (multi-provider). Misleading/inconsistent UX. Fixed — all reworded to "add an API key in Settings → Language Model".
5.3 store.ts:78 & Window.tsx:8 low MENUBAR_H = 36 duplicated as a magic number in two modules (plus DOCK_RESERVE, MIN_W/H). deferred — extract to a shared layout constants module (low risk, low value).
5.4 App.tsx:12 cssVars vs tokens.ts tokensAsCss low Two overlapping CSS-var builders. Intentional (apps get a subset; root gets the full set incl. shadows/radius-xl) but the key list is partly duplicated. accepted (different scopes by design).
5.5 registry.ts icons low App Builder uses "⚙️" and Settings uses "⚙" — near-identical gears, visually ambiguous in dock/desktop. deferred (cosmetic identity choice — recommend a distinct glyph for App Builder, e.g. 🛠️).

Fixes applied: 5.1 (dead code removed), 5.2 (medium — stale strings). Typecheck + full vite build clean.


Summary

All 5 passes complete. Verified with tsc --noEmit (clean) and production vite build (succeeds).

Fixes applied (7):

Sev Pass Fix
high 2.1 Terminal rm/cat/mkdir/touch now require an operand — a bare rm no longer deletes the cwd / whole virtual FS.
high 4.1 store.init() is now fault-tolerant — IndexedDB failure boots the OS with defaults + a notification instead of hanging on the boot screen forever.
medium 2.2 os.ai.available is a live getter, not a stale snapshot — apps see the key after it's configured in Settings.
medium 3.1 Sandbox blocklist hardened (indexedDB, globalThis, Function(, import.meta) — closes the trivial API-key exfiltration path.
medium 5.2 Five stale "VITE_ANTHROPIC_API_KEY" prompts reworded to point at Settings → Language Model.
low 5.1 Removed dead currentProvider() export.

Notable accepted/deferred items:

  • 3.2 (medium): generated-code sandbox is not a true security boundary (in-page new Function). Accepted by design (PROMPT.md); real isolation needs an iframe/worker — a substantial refactor, out of scope for this review.
  • 3.3/3.4 (low): dev-server proxies are open-proxy/SSRF vectors but ship only in dev, never in production builds.
  • Low-severity polish (window X-clamp, Files/Settings async try/catch, MENUBAR_H dedupe, duplicate gear icons) logged above for future triage.

Overall: healthy codebase. 0 dependency vulnerabilities, no type/import errors, no XSS sinks. The two highest-impact issues (FS-wiping rm, silent boot hang) are fixed; the chief residual risk (sandbox isolation) is inherent to the product's design and documented.

Review complete — loop stopped.