Commit Briefs

7f51b2b2cd isaac

ui: unveil ~/.fugu/skills before the startup skills read (tags/v0.1)

skills_load() ran in main() before any unveil, with the whole filesystem visible and symlinks followed -- so a symlink planted at ~/.fugu/skills/<name>/SKILL.md was an arbitrary-file read primitive: its first bytes were slurped into a model-visible skill body. Call the (previously dead) skills_unveil() first so the load is pinned to ~/.fugu/skills; unveil checks the resolved target, so an out-of-tree symlink is refused (EACCES) and the skill is silently skipped. In-tree skills are unaffected. The skills dir then joins the front-end's locked unveil set, read-only -- the user's own dir, no runtime code reads it. Behaviour change: skills symlinked OUT of ~/.fugu/skills no longer load (deliberate; that is the read primitive). regress/skills gains test_skills_unveil_confinement: a forked child (the unveil is process-wide and irreversible) plants /real plus /evil whose SKILL.md symlinks to a file outside the skills dir, then asserts skills_unveil + skills_load loads /real but never follows /evil. Verified it fails without the unveil (revert experiment). Found by an adversarial confinement audit of the curses UI worker after its runtime pledge was widened for the conversation drawer.


565182c8b8 isaac

ui: conversation drawer to browse and switch sessions

Add a side drawer to the curses UI (o in vi normal mode) listing past conversations newest-first with a preview and relative time. Tab moves focus between the drawer and the conversation; j/k + arrows select; Enter switches. A "[+ New conversation]" row starts a fresh session. Switching is a pure UI + journal operation -- net is stateless per turn (the UI resends the full projection), so the next turn carries the new history with no net/parent changes. Switching mid-turn soft-abandons the in-flight turn: the old journal gets a turn_failed marker, the UI drops the abandoned turn's late output (handle_msg gates on an `abandoning` flag, still answering M_TOOL_REQUEST so net can drain), and busy stays set until it finishes so a send in the new conversation waits. The drawer needs runtime file access (list/read/create journals), so the curses worker keeps rpath/wpath/cpath after startup instead of narrowing to stdio+tty; unveil still confines it to ~/.fugu/sessions. - journal_list(): scandir the sessions dir, newest-first, skipping empty files. - ui_curses.c: drawer state/render/input, the soft-abandon switch, the o/Tab rebinding (o no longer enters insert in normal mode). - regress/tui: seed two sessions, open the drawer, switch, assert the transcript becomes the other session's content. - CONTEXT.md: session / conversation / drawer / turn / projection glossary.


d37c628913 isaac

add clean-check pipeline: make check = clean-room build + full regress

Wipes all build artifacts, does a fresh DEFAULT build, runs every regress suite from scratch, exits non-zero on any failure. This is the canonical verification step. Incremental builds bit us twice: a stale in-tree binary, and an FUGU_DEBUG build left in src/ that dies under pledge and hangs chat/compact/tui. Wired as the make check target, shipped in the distfile, and documented in AGENTS.md (Testing).


36ff1dbc2e isaac

regress: cut test timeouts 60/90s -> 15s, document binary-flavor gotcha

The whole suite completes in ~1s, so a 90s alarm backstop is absurdly long -- a genuine hang now fails in 15s (10s for the chat/compact stub self-alarm), still a 15x margin over normal runtime. Applied across all eight suites that arm an alarm (chat, compact, exec, fetch, imsg, net, sandbox, tui). Also adds .scratch/regress-debug-binary-gotcha documenting why chat/compact/tui hang/fail when src/ was last built FUGU_DEBUG=1 (they exec the in-tree binaries, which die under pledge), plus the stale top-level vs obj/ binary trap.


ae9e128080 isaac

fix build warnings: drop dead resolve_secret, declare forkpty in tui test

parent.c: resolve_secret was kept as a back-compat wrapper during the api-hardening sweep but every caller moved to secret_load -- remove it (-Wunused-function). tui_test.c: it included <util.h> for forkpty(3), but the build is -I.../src/common where fugu has its own util.h, which shadowed the system header; declare forkpty directly instead.


a74240b311 isaac

regress: tear down child processes on timeout instead of orphaning

chat_test and compact_test fork a stub TLS server + the full fugu, then set alarm(90). on_alarm only _exit(97)d, orphaning both children -- the stub has no timeout of its own and blocks forever in accept()/tls_read(), so every stalled run leaked processes that ran until killed by hand. Track the child pids and SIGKILL them from on_alarm before exiting; also give the stub child its own alarm(60) + default SIGALRM so it self-terminates if orphaned by any other path. Verified: a forced stall (FUGU_BIN=/bin/cat) now leaves no live orphans.


c359dadcc3 isaac

ui: drop speaker labels, left-align message bodies

prefix_of returns empty for BK_USER and BK_ASSIST, so user and assistant messages render with no "you"/"fugu" prefix and their bodies start at column 0. They stay distinguishable by weight (user bold, assistant normal) and the existing blank line between blocks; tool:/error: labels are kept. regress/tui now asserts the labels are absent instead of present.


7e0a0d6d31 isaac

fetch: http_allow wildcard for allow-all-minus-block posture

acl_host_match treats a bare "*" token as matching any host, so http_allow "*" plus an http_block list gives an allow-all-minus-blocklist policy for the http_request tool without new config keys, parser, or imsg changes. The SSRF guard is unchanged (private/loopback/link-local still refused), so even wide open this brokered path stays far safer than allow_subprocess_net -- removing the incentive to reach for unguarded subprocess networking. Documents "*" in fugu.conf.5 with the exfiltration caveat (default stays default-deny; "*" is an explicit operator choice). Adds three regress/fetch cases: wildcard allows an un-listed host, block wins over the wildcard, and an unrelated block does not over-match. Full regress 710 -> 713, 0 failures. Issue: .scratch/http-allow-wildcard/


6b55003318 Isaac

Import fugu: OpenBSD-native privsep AI coding agent

Five pledge/unveil-sandboxed binaries (fugu parent + fugu-{ui,net,exec,fetch}), base system + vendored jsmn.h only. Multi-provider net worker (Anthropic + OpenAI-compatible wire protocols), vi-modal curses TUI with markdown rendering, runtime /model picker, SSRF-guarded fetch worker, sandboxed exec tools, and a user/model-invocable skills subsystem. Includes the regress suites, man pages, ADRs, and the .scratch issue tracker.