A point release with a stack of real fixes, the start of a GUI-subsystem refactor, and one apology.
Default root password (the apology)
I forgot to put the default root password anywhere visible in the 1.0.0 release. People who organically tried to boot Aegis over the past few days couldn’t find it — sorry about that. It’s:
Username: root
Password: forevervigilant
The README has it now, the homepage has it now. Single-line oversight, “how do I even use this thing” friction. Genuinely my bad.
The bug that almost shipped
On bare metal, Lumen would draw the desktop exactly once and then freeze. Mouse cursor stuck, keyboard ignored, no progress. QEMU never triggered it. The kind of thing where you spend ten minutes thinking “weird hardware quirk” before catching yourself.
It was two AF_UNIX bugs interacting:
fcntl(F_SETFL, O_NONBLOCK)didn’t propagate to AF_UNIX sockets. The kernel’ssys_fcntlupdatedsock_t.nonblockingfor AF_INET but never touchedunix_sock_t.nonblocking. Lumen’s listen socket — which it explicitly set non-blocking — was actually still blocking at the kernel level.- AF_UNIX sockets had no
.pollcallback.sys_pollfell through to the permissive default (always returns POLLIN), so Lumen’s event loop calledaccept()every tick. Combined with bug #1, the very firstaccept()blocked forever and took the whole compositor with it.
Fixed in kernel/syscall/sys_file.c and kernel/net/unix_socket.c. New unix_vfs_poll() reports readiness based on accept queue (LISTENING), peer ring buffer (CONNECTED), and peer state (POLLHUP).
stsh was secretly /bin/sh
/etc/passwd says the root shell is /bin/stsh but logging in dropped you into /bin/sh. Not a fallback path, not a config bug — the kernel was loading the wrong binary.
kernel/fs/initrd.c had /bin/stsh aliased to the /bin/sh blob. A leftover from before stsh existed as a real binary, when we wanted the path to resolve to something. VFS open order is initrd-first, ext2-fallback, so execve("/bin/stsh") always loaded the sh blob from initrd and never the real stsh on disk. Removed the alias. Now you actually get stsh — capability-aware prompt (# if you hold CAP_DELEGATE, $ otherwise), tab completion, the caps/sandbox/grant builtins. There’s a regression test (tests/tests/stsh_default_shell_test.rs) that runs the stsh-only caps builtin after login and panics loud if /bin/sh is what answers.
Three more bugs the GUI installer caught
The 1.0.0 GUI installer mapped the framebuffer directly via sys_fb_map and rendered on top of the desktop, which meant it wasn’t really a window — no chrome, no z-order, no input through the compositor. I wanted to fix that for 1.0.1: make it a proper Lumen window via a new external-window protocol over AF_UNIX. That’s the stack the original Lumen-freeze bugs broke into.
Building that out smoked out three more deep bugs, all of which had been silently lurking:
1. stb_truetype rasterizer assertion (Bastion crash). Assertion failed: z->ey >= scan_y_top (stb_truetype.h:3350). Subpixel positioning + fp rounding produced glyph edges with ey < scan_y_top on the very first scanline. Upstream stb clamps this only when j == 0 && off_y != 0, but the same hazard fires at off_y == 0 too — Bastion’s small-glyph BakeFontBitmap path always triggers the unclamped case. Made the clamp unconditional. Fixed in user/lib/glyph/stb_truetype.h.
2. AF_UNIX ring buffer math (lumen_connect EIO). The probe binary I wrote to test the new protocol kept getting lumen_connect failed (-5) on the very first byte. Turned out UNIX_BUF_SIZE was 4056 — matching the pipe ring “for symmetry” — but the ring math (head - tail) & (UNIX_BUF_SIZE - 1) only computes a real modulo when the size is a power of two. 4055 = 0b1111_1101_0111 — bits 3 and 5 are clear. So 8 & 4055 == 0. The 8-byte hello write put 8 bytes in the client’s ring (ring_head=8, ring_tail=0), but the server’s ring_used() computed (8 - 0) & 4055 = 0 and returned EAGAIN. The poll callback used a different check (raw head != tail) and correctly reported POLLIN — producing a perfect “poll said ready, read returned EAGAIN” race on every connect. Bumped to 4096.
3. mmap memfd size check (window_create NULL). With (2) fixed, lumen_connect worked but lumen_window_create returned NULL. The server was creating a memfd for the shared pixel buffer, calling ftruncate(memfd, 80000) (200×100×4), then mmap(NULL, 80000, ..., MAP_SHARED, memfd, 0) — and getting EINVAL. sys_mmap rounds len up to a page boundary at the top (80000 → 81920), then later compared len > mf->size for MAP_SHARED. 81920 > 80000 → EINVAL. The page allocation was fine; only the bookkeeping was wrong. Compare against page_count * 4096 instead.
After all three: gui-installer connects to Lumen via AF_UNIX, gets a memfd-backed pixel buffer, renders into it, sends LUMEN_OP_DAMAGE, and Lumen blits it into a normal composited window with chrome and a close button — same as terminals. Capability gate dropped: it no longer needs CAP_KIND_FB.
Mouse + keyboard for proxy windows
When the gui-installer hit the desktop, two more wires were missing:
- Lumen wasn’t dispatching keystrokes to proxy windows. The main loop wrote bytes directly to
focused->tag(= PTY master fd). Proxy windows have no PTY (tag = -1), so keys went nowhere. Fixed: invokefocused->on_key()if it exists; terminals’on_keywrites to PTY internally, proxy windows’on_keysendsLUMEN_EV_KEY. Both paths converge. - gui-installer wasn’t handling mouse clicks. The original installer was keyboard-only, so the “Next” button was just a visual hint. After the Lumen port mouse events arrived but were dropped. Added a hit-test that synthesizes Enter on a click in the advance-button area — same action keyboard Enter triggers.
Citadel dock split into a separate process
This is the start of a longer arc: cracking Lumen open into smaller subsystems that can each grow on their own. The dock is the first thing out the door.
In 1.0.0 the dock was statically linked into Lumen and rendered as an overlay callback after windows composited. In 1.0.1 it’s its own binary at /bin/citadel-dock, talks to Lumen over the same AF_UNIX protocol the gui-installer uses, and asks Lumen to spawn built-ins via a new LUMEN_OP_INVOKE message. Vigil starts it as a graphical-mode service. Lumen no longer knows what a dock is.
The protocol grew two opcodes: LUMEN_OP_CREATE_PANEL (a chromeless, non-focusable, bottom-anchored window) and LUMEN_OP_INVOKE (ask Lumen to run a named built-in like "terminal" or "widgets" — eventually those will be separate binaries too). Cost: lost the frosted-glass blur effect, since the dock can’t see the pixels behind itself anymore. Acceptable downgrade for now.
The longer plan is to keep peeling: terminal, taskbar, file manager, settings — each its own process, each potentially its own project/repo. cairo-dock-style. This release is the first cut.
Use-after-free in proxy window cleanup
While testing the new protocol I left a debug probe service auto-launching in graphical mode. The probe creates a window, presents a frame, exits — and on bare metal the very next mouse interaction page-faulted Lumen. Page fault, RIP in user code, CR2 in the mmap region — classic UAF.
Two underlying bugs:
lumen_server_hangupandhandle_destroy_windowwere callingglyph_window_destroy(pw->win)aftercomp_remove_window(comp, pw->win)— butcomp_remove_windowalready destroys the window. Double free. Removed the second destroy.comp_remove_windowclearedc->focusedif the removed window was focused, but didn’t clearc->drag_winorc->content_drag_win. Any subsequent mouse event would dereference the stale pointer. Cleared both.
Also: the close button now invokes on_close if set (proxy windows use this to send LUMEN_EV_CLOSE_REQUEST to their owner instead of being destroyed unilaterally), so external clients can show “are you sure?” dialogs.
Small things
lumenrunning inside a Lumen terminal would map the framebuffer twice and produce two cursors. Lumen now setsLUMEN_RUNNING=1in its environment; nested launches refuse to start with:lumen: you're already using lumen, pal.glyph_window_t.taginitialized to -1 instead of defaulting to 0 — fd 0 is a valid file descriptor and was indistinguishable from “uninitialized” in the dispatch checks.
Get it
Download v1.0.1 ISO — same shape as 1.0.0 (live boot, graphical default, text-mode in GRUB), with all of the above baked in.
If you find more bugs — and you will — file an issue at exec/aegis. Security findings can also go privately to execxd@icloud.com.
Forever vigilant.