Commits
- Commit:
7f51b2b2cda1fe76b2e54f2c9b9d4903c9684981- From:
- isaac <isaac@server.fugu.farm>
- Date:
ui: unveil ~/.fugu/skills before the startup skills read
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.
- Commit:
565182c8b8094abb4bd19d037ae6e2201a181704- From:
- isaac <isaac@server.fugu.farm>
- Date:
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.
- Commit:
d37c6289137cc9ab2fb068f9e633d3ea779caddb- From:
- isaac <isaac@server.fugu.farm>
- Date:
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).
- Commit:
36ff1dbc2ebdbcd1ec00a70eeb4a3f5382e195cf- From:
- isaac <isaac@server.fugu.farm>
- Date:
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.
- Commit:
ae9e12808009767d79769218f0969b7fbe8ccb4d- From:
- isaac <isaac@server.fugu.farm>
- Date:
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.
- Commit:
a74240b311a442acedb186d30f70edfe847de213- From:
- isaac <isaac@server.fugu.farm>
- Date:
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.
- Commit:
c359dadcc35ab09cd72a45d0e87d4e5971efdb5b- From:
- isaac <isaac@server.fugu.farm>
- Date:
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.
- Commit:
7e0a0d6d31495a7e4e8f5c7c2b9d301fbe2eb16e- From:
- isaac <isaac@server.fugu.farm>
- Date:
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/
- Commit:
6b55003318869d06a4f7b148d2bf0649d22053ac- From:
- Isaac <isaac@itm.works>
- Date:
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.
