Changelog
All notable changes to secure-agent are documented here. The format follows Keep a Changelog and the project adheres to Semantic Versioning.
[1.3.0] - 2026-10-12
Added
- Advisor: read-only evidence tools and optional local classification for context routing.
- Chat: read tools for built-in skills, findings and session activity, with tool progress; New chat resets the context.
- CLI:
secure-agent telemetry repair. - Console: a System tab for CPU, memory, pressure history, worktrees and clutter, filtered by the header search, replaces Sessions → Cleanup; unreadable cleanup history, reclaimed space and agent asks show as unavailable.
- Console: a notice on any report that failed to refresh, and a banner that claims all-clear only after telemetry loads, with Retry.
- Console: the same reads across agents are one decision with Treat as routine and Dismiss all (
flag_idsonPOST /expected). - Cursor: Secure Agent's tool hooks run beside the user's hooks.
- Documentation: guides for setup, first session, coverage and troubleshooting, generated CLI, configuration and API references, and a docs archive on each stable release.
- Findings: apply built-in prevention instructions to the recorded project's AGENTS.md or CLAUDE.md in one click, with a file and text preview.
- Findings: evidence, control, residual risk and review state shown apart; reviews are saved per evidence revision, reopen on stronger evidence and keep the risk (
/reviews). - Findings: send selected findings to the local agent's review queue.
- Findings: agent-scoped
.envexceptions for test and non-secret files. - Findings:
test-valueevidence for secrets that look like test values. - Guard and findings: Allow once by default; session, 24-hour and 7-day permissions cover one file, workspace or endpoint and are revoked under Protection → Rules & permissions (
/decision-scopes). - Incidents: Dismiss in Needs you; Mark as routine and the advisor above the report's evidence.
- Incidents: reported remediation in the console, menu bar and exports, flagged when newer evidence arrives.
- Pricing: Claude Sonnet 5.5.
- Proxy: the proxy URL's user part selects
inspectortunnel. - Resources: intervention results in the console, menu bar and session export; an approval acts only on the process it was captured for.
- Session reports: saved review decisions with their evidence revision and residual risk; copying a partial report warns.
- Sessions: Home opens a session's requests, findings, coverage and machine impact (
GET /sessions/{id}/overview). - Sessions: a Results view of saved decisions, revocable permission scopes, process-control outcomes and reported remediation.
- Sessions: Memory pages through retained activity, ended sessions included, with event and time filters and Earlier, Newer and Latest, and opens finding evidence and incident reports.
- Settings → File Guard and Egress Firewall: add, edit and remove paths and patterns, validated and saved (
/guard/config,/firewall/patterns). - Settings → Secure Agent → Analysis: request timeout, classifier connection check, metadata-only debug logs, and queue, retry and timing diagnostics.
- Settings → Secure Agent → Chat: Review new findings automatically (
system_agent.auto_review), with editable minimum severity and finding types. - Settings → Secure Agent → Traffic: route Claude Code through Secure Agent's proxy (
GET /routing/claude). - Setup: first use walks through Choose, Enable and Result for one harness, with an inert installed-hook check that shows the hook's error (
POST /coverage/probe). - Status: per-harness trace and guard support, and guard, trace and payload observations per live session.
- Status:
proxy_tunneled,proxy_decrypted,es_service.newest_event_atandlag_seconds. - Status, posture and Doctor: dropped telemetry, failed evidence reads and writes, lost OTLP spans, egress summary loss and incomplete proxy inspection; spool loss and bus drops fail only while they grow.
- Telemetry Doctor: recovery steps and Open hook setup.
- Worktrees: Discuss asks the agent about a worktree in a panel docked beside the page; Ask advisor about all and Discuss all cover a repository (
POST /agent/worktree,repoonPOST /worktrees/advise). - Worktrees: Remove for confirmed rows and amber Remove… to the Trash for keep and review rows, greyed while work would be lost, a rebase, merge or cherry-pick is in progress, or an agent was seen there in the last 15 minutes (named on the row), with a legend.
- Worktrees: merges are detected by content, also after a history rewrite.
Changed
- App: bundle identifier
com.cavi-ai.secure-agent. - Chat: sending and waiting states.
- Chat: command status, exit code and output stay beside the proposal.
- Console: a decision shows at most three buttons and a More menu.
- Console: one Allow per destination organization replaces one per address.
- Console: findings show their file and destinations as chips.
- Console: the page fills wide windows, with decision groups two-up.
- Console: disabled buttons are dimmed.
- Console: revoking a permission or removing an expected read or file exception asks for confirmation.
- Console: a Protection tab with Traffic, Rules & permissions, Secret sources and Audit replaces Egress and Policy; recurring activity opens its saved expectation.
- Console: the session view, filters, scroll and focus survive Back and Forward, drawers and expired credentials, which pause controls until the console reconnects.
- Console and menu bar: Home and the hero list only decisions that need you, each with its evidence, uncertainty, control outcome and action consequences; warnings and recurring connections stay in the findings history, now a compact log.
- Dependencies:
modernc.org/sqlite1.60.1. - Egress: infra apps' endpoints count as
uninspected_infra. - Findings: critical findings sort first.
- Findings: temporal and parent-process correlations labeled apart from direct activity.
- Findings: weak historical findings stay at review severity.
- Findings: repeats fold across restarts.
- Findings: expected reads are scoped to exact hosts.
- Findings: destination mutes match structured evidence.
- Findings:
.envevidence shows variable names only. - Findings and incidents: a payload finding shows whether its request was blocked, only observed, or unknown.
- Install: one copy,
/Applications/Secure Agent.app, signed with Developer ID and notarized. - Menu bar: sessions, findings and incidents open in the console at that record.
- Performance: snapshots, posture and store lookups do about half the work.
- Posture: uninspected egress alone is all-clear.
- Protection → Traffic: the Recurring activity badge counts pending decisions.
- Sessions: a compact navigator and activity ledger; narrow windows switch between list and detail.
- Sessions: a tool call with no result is marked
incompleteafter 24 hours. - Settings: a sidebar of pages under Protection, Alerts, Monitoring, Secure Agent and General.
- Settings → Egress Firewall: rules grouped by secret type, with Block all and Monitor all.
- Settings → Exceptions: muted flags grouped by rule, beside allowed files.
- Settings → File Guard and Notifications: mode menus replace segmented controls.
- Settings → Providers: every daemon harness by name with its mark, Codex, Cursor, OpenClaw and Hermes first, toggles applied without a restart.
- Spend: saved spend shows at once while it refreshes; one quota per shared Codex account; a daily usage bar, chosen by click or arrow keys, opens that day's breakdown.
- Worktrees: the live-session button reads Ask <harness>, runs Claude Code without a budget cap, and a failed ask shows the agent's own error.
- Worktrees: rows lead with their state, and reasons are tags colored by what they mean for removal (
reason_kindsonGET /worktrees). - Worktrees: the advisor sees the merge verdict and the default branch's side.
Fixed
- Advisor: re-triage stays pending until its verdict lands, with elapsed time and task limits.
- API: failed or partial evidence reads and unsaved operator decisions return 503 and show in storage health; Open and Reveal answer 410 only for a deleted file.
- App: daemon restart retries are bounded.
- Build:
makestops when a binary's commit stamp is not HEAD. - Chat: Enter cannot send twice.
- Chat: drafts survive failed sends.
- Console: a live-update stream that falls behind reconnects and reloads.
- Console: the connecting process is identified once per connection, on macOS 14 too.
- Console: an unidentified connecting process gets 503 instead of ending the session.
- Console: an older refresh or headline never replaces newer data.
- Console: malformed reports keep the last good data.
- Console: filtered results survive a failed refresh.
- Console and menu bar: a decision updates the page and the headline right away.
- Costs: one model call per Claude API call, with stored duplicates removed.
- Daemon: startup fails when the control API cannot be served.
- Daemon: one daemon per store.
- Doctor:
configreports start-only changes, unreadable overlays and skipped reloads. - Doctor: trace coverage counts only sessions seen since boot.
- Doctor: a running ES service with an earlier exit passes.
- Egress episodes: no lost connections during concurrent writes.
- File telemetry: no events lost to full queues or spool rotation.
- File telemetry: a lagging eslogger is restarted.
- File telemetry: a helper launchd will not start is re-registered, also from Doctor.
- File telemetry: events from exited processes join the parent's session.
- Findings: approval, review and file inspection buttons on grouped findings.
- Findings: no sensitive-read finding for a credential read by its owning program (
credential_ownersprograms), a directory open, or a file the agent wrote. - Findings: a finding whose write fails is raised on its next occurrence.
- Firewall: fingerprint reloads and ingestion leave the registry unchanged when a source, the salt or the engine fails.
- Fleet:
/statusand/fleetshare one livefleet_configured. - Fleet: security alerts keep delivery capacity when traces fill the queue.
- Fleet: delivery logs are capped at 5 MiB with one backup, and the collector keeps critical counts through replay.
- Forensic archive: kept when rotation fails.
- Guard: an Always answer that cannot be saved applies to that request only and shows in Doctor.
- Incidents: a report opens from any aggregated flag, workflow actions change only that report, and it keeps its newest evidence time, original flag and saved remediation; a duplicate never replaces it.
- Menu bar: a hidden status item is recreated once per loss.
- Menu bar: an action's result stays visible after its finding leaves the queue.
- OpenCode: history read in bounded pages.
- Policy files: an edit fails and leaves the file intact while a notification, mute, allowlist, expected-pattern, firewall or resource policy file is unreadable.
- Pricing: stored calls priced when a price is added.
- Resource episodes: include the session's tool calls, model calls and late file events.
- Routing: the proxy URL carries the token.
- Sessions: activity timestamps only move forward.
- Sessions: an ended session that records new work while its process lives returns to active, and every session on an exited or reused root ends in the same sweep.
- Sessions: failed Trace and status reads keep the last loaded data with a stale warning and Retry.
- Sessions: rekeying is atomic.
- Sessions: a silent session rooted in another family's infra ends after an hour.
- Sessions: Antigravity sessions are recorded as
antigravity. - Sessions: ended sessions release their write throttle once stored.
- Store: lists, retention,
sinceanduntilfilters and first-seen times order by exact instant across time zones. - Store: records are published only after they are stored.
- Store: failed session writes are retried.
- Store: schema upgrades run in one transaction.
- Store: a stored
nowtimestamp no longer stops the store from opening. - Store: flag and incident retention deletes only the overflow.
- Transcripts: events kept until delivered.
- Transcripts: checkpoint failures retried and reported.
- Transcripts: deleted transcripts are retired and unreadable ones keep their checkpoint.
- Updates: the downloaded app is verified before it replaces the installed one, and the previous app is restored if relaunch preparation fails.
- Worktrees: an action cancels the background rescan.
- Worktrees: a merged branch keeps its merge reason beside keep and review reasons.
- Worktrees: git reads never lazy-fetch objects in a partial clone.
- Worktrees: repository text cannot close the advisor's evidence block.
Security
- Build: Go 1.26.9 standard-library security fixes.
- Firewall: masking hides every detected fingerprint and pattern span in full, overlaps included.
- Guard hooks:
guard-rules.jsonbundled and installed besidesecret_guard.py. - Guard: a prompt is withdrawn when its hook stops waiting.
- Proxy: request bodies up to 64 MiB are inspected before forwarding, including gzip, URL, JSON and base64 forms.
- Redaction: one rule set for the daemon, activity log and guard audit.
[1.2.0] - 2026-09-30
System Agent
- Agent workspace centers the conversation, with editable quick-command drafts and an on-demand sidebar for the review queue, history, and tools. Narrow layouts preserve the draft and keep the composer visible.
- Analyze activity turns findings and recent behavior into recommendations that can be reviewed, explicitly approved for local execution, or saved for later delegation.
- Chat stays on local Ollama. It proposes exact local shell commands for explicit review and confirmation, with Terminal mode for passphrases; commands run with the operator account's file and network access.
- Harness selection and plan creation moved to a separate handoff section, so sending chat cannot silently route to another agent. The API rejects legacy chat requests with a harness field.
- Pi runner joins optional harness handoff in terminal mode only. Its model is pinned to the local Ollama in an isolated configuration; extensions, shell tool, context files, and online startup checks are disabled. Pi has no built-in sandbox, so its file tools still have the operator account's file access.
- Local commands and harness handoffs use private task workspaces and task-scoped
TMPDIR. Temporary scripts, configurations, answers, and files created inside the workspace are deleted on exit; startup reclaims crash leftovers while preserving live Terminal tasks. Requested outputs and redacted stored history remain intact.
Added
- Expected secret reads:
Expected: gh → GitHubon a read-then-connect finding stores that pattern (agent, reader, file, destination org) and reviews its open flags; later occurrences are counted instatus.expected_flags, not flagged, and a new reader, file or destination still flags.GET/POST/DELETE /expected(NoAgent); the Policy tab lists them with Forget. - Console Resources: machine headroom
?explains the score. It is the lowest of memory still available, CPU still idle, swap still free, and thermal headroom. Under 15 is critical.headroom_limiternames which input set the score. - System agent (opt-in,
system_agentin config.yaml, applied live): chat directly with a model on local Ollama. It can propose an exact shell command and folder, then run the command only after confirmation. Optional harness handoff is a separate action that saves a plan and requires another confirmation to dispatch. Seedocs/SYSTEM_AGENT.md. - Seven built-in skills for the system agent:
ssh,git,signing,claude,codex,openclaw,hermes. /agent/status,/agent/skills,/agent/chat,/agent/plans,/agent/dispatch,/agent/runs(console-admitted, NoAgent). Text is masked by the firewall before it is stored or sent to the model; a secret that cannot be masked is refused.- Console: an Agent tab with direct Ollama conversation, confirmed local commands, and a separate Harness handoff section for saved plans and explicit dispatches.
- Console Cleanup view: a progress toast for every removal started in this tab, another tab or the CLI, with phase, size and files being deleted, time in the phase and one bar per group.
- Console Cleanup view: a finished removal's toast names the bytes reclaimed and links the history.
- Console Cleanup view: a failed removal's toast stays until closed.
- Console Cleanup view: tiles for space freed in 30 days and all time, removable now and in the Trash.
- Console Cleanup view: Remove all across repositories on the Removable now tile, one dialog counting worktrees per repository.
- Console Cleanup view: a 30-day chart of bytes freed and moved to the Trash per day, with a tooltip per day.
- Console Cleanup view: History drawer listing the cleanup ledger by day, with filters by kind.
- Console Cleanup view: a chart column opens the history at that day.
- Console Cleanup view: search by branch, folder or repository.
- Console Cleanup view: click a row's path to copy it.
- Console Cleanup view: Show in Finder on every worktree still on disk.
GET /cleanup/ledger?days=N: daily bytes and cleanups freed and moved to the Trash for the N days ending today.- Worktree removals report
phase,step_atand the measuredbytesandfileswhile they run. - File telemetry registers itself at launch, opens its two System Settings panes once per build, and has a Doctor with per-check fixes (menu bar: Run Doctor…).
- Console Cleanup view: Remove all on a repository with two or more removable worktrees (count and size on the button); one dialog, each removal checked again by the daemon, each row shows its steps, one line when the batch ends.
- Worktree folders whose repository moved or was deleted:
POST /worktrees/reveal(Finder),POST /worktrees/reconnect(git worktree repairin the repository that still records the folder, named in the row'sreconnect),POST /worktrees/trash(Trash on its volume, booked astrash:orphan-worktree). - Console Cleanup view: a missing repository's group comes first with "the folders below still point to it"; its folders offer Open folder, Reconnect (when a repository still records them) and Move to Trash.
- The
errorsline of a missing repository counts the folders that still point to it. - Sessions carry
origin, the openclaw agent behind a Codex session, named in console titles and "×N" folded rows. GET /costs/plansserves each Codex home's plan headroom, shown as a line and bar per plan on the console Spend card.- Console Clutter panel: Ask advisor on every project header; the plan shows under the header.
secure-agent worktrees ask <path>; console Ask the agent is highlighted only when an active resumable agent is working in the worktree, with its answer under the row. Ask advisor remains a separate advisory action.- Console Cleanup view: Review opens the local-only reasons for a linked worktree and offers an explicit Move to Trash action. The daemon rechecks the verdict, HEAD and reasons, preserves the folder in Trash, and leaves the branch, commits and stashes in Git (
POST /worktrees/review-trash). - Console Sessions › Cleanup sub-view (was Worktrees): a Clutter panel grouped by project (8 rows until Show more) with kind filters, Move to Trash and Run <clean command>.
- Console Muted list shows the agent of a scoped mute.
- Console Policy: muted flag classes show the mute's agent, or "all agents".
- Console: four tabs (Home, Sessions, Egress, Policy) replace nine.
- Console: old tab links, menu bar deep links and saved views open the matching tab or sub-view.
- Console Home: Needs your attention first, then Spend, then collapsed Findings history and Trends.
- Console Sessions: Sessions, Processes, Resources, Worktrees and Events sub-views.
- Console Policy: notification rules, guard decisions, file exceptions, muted flag classes and the policy audit.
- Console: a load without a console token, or a revoked token, shows only "Console session ended."
- Console: a new
#ct=in the address bar reloads the page with that token. - Console Worktrees tab: disk card (volume bar, worktree, removable and reclaimed totals), sizes per project and worktree, biggest projects first; a removal updates the totals in place; while sizes are measured the tab re-reads the cached report every 5 s (up to 5 minutes).
- Console Worktrees tab: Ask advisor on review and keep rows; the note shows under the row.
- Console: Mark as routine / not ok on finding cards and in the What to do drawer, with your history and the suggestion.
- Console: a What to do drawer on every finding, and the playbook and plan in the incident and file drawers with Ask the advisor and the recommended actions as buttons.
- Console Spend card: dimension (repo, provider, model, day) and window (24h, 7d, 30d) controls, kept per tab.
- Console Spend card: day view as bars.
- NoAgent route class: refused for agent processes on the socket (live family check) and on the console listener (the TCP client's process).
- Console: evidence paths (incident Accessed Files, a finding's File) open a file drawer with Reveal in Finder and Open in editor;
#file=<path>deep link. - Console Worktrees tab: rows grouped by repository with state, stale, idle days, branch, path and reasons; state and stale filters; Remove, Prune, Hide repo, Add repository and Rescan.
- Console pattern card on Attention and Flags: count, window, summary, 24-bar cadence strip, open count, served actions, individual flags.
- Console tab bar stays at the top while the page scrolls.
- Console tab bar shows a posture pill once stuck.
- Console posture pill scrolls the page back to the top.
- Console scope bar under the tabs names the session or process family Events, Flags and Incidents are narrowed to, with a Clear button.
- Console drawers opened from inside another drawer show a Back button that reopens the previous drawer.
- Session report:
GET /sessions/{id}/report?format=json|mdaggregates one
session's tools, models, cost, files, hosts, guard decisions, findings, secret-rule hits and opening timeline (names, paths, hosts, model and rule ids, counts — never content); secure-agent session <id-or-prefix> prints it as markdown, secure-agent sessions lists sessions, GET /sessions filters by harness, repo, branch and since, and the console's session head gains an Export button that copies the markdown.
- Model-call spend.
GET /costsandsecure-agent costsum model calls
by repo, branch, harness, session or model over a window (since, until), with tokens, distinct sessions and the dominant harness per group. Calls from models outside the pricing table are counted as unpriced_calls and never assigned a cost. The console Overview shows a 24h spend tile and a top-5 spend-by-repo card.
- Opt-in session resource budgets and containment. A hot-reloadable
resource_control policy supports observe-only reporting, approval-required containment, or explicit automatic whole-family termination after a sustained RSS/CPU breach. Grace periods, cooldowns, PID start-time checks, retryable failed approvals, console controls, and durable audit entries keep the destructive path bounded and explainable. The console now includes a visual policy editor with complete per-workspace overrides, longest-path selection, atomic YAML persistence, and an explicit confirmation before automatic termination can be saved.
- Firewall rules can be demoted back to monitor from the console (block was a one-way ratchet in the UI).
GET /allowlistlists user-approved endpoints;DELETE /allowlistremoves one. The console renders them with working Remove buttons.- Fleet config is hot-reloadable. The daemon's config watcher (previously
advisor-only) now swaps fleet sinks, heartbeat cadence, labels, and hostname live within one poll cycle — enroll takes effect in seconds, and enrolling a node that had no webhooks at boot activates heartbeats without a restart. fleet_configured follows the webhook set so the console's fleet panel appears/disappears live.
- Daemon-restart signal in the console. An uptime that moves backwards
is reported as "Daemon restarted — reconnected" instead of silently pretending continuous state.
- Console: sessions and processes lead with the harness identity and a brand mark.
- Console: bar charts keep their geometry under the content security policy.
- Console: saved views and a global search.
- Console: light and dark themes follow the system setting.
- Console: a session's trace shows as a waterfall.
- Console: a Sessions tab renders durable sessions.
- Console Cleanup view: Remove shows "Removing…" with the current step, then drops the row and moves the totals, or keeps the row with the error or the refusal and a Try again button; a toast reports each outcome.
- Menu bar: the top unacted finding shows under the hero with its served title, explanation and disposition.
- Menu bar: the finding's recommended allow host, allow path, mute or dismiss action runs from the popover.
- Menu bar: an allow host or allow path from the popover also dismisses the finding.
- Menu bar: a popover action runs only when its method, path and body keys match its action id.
- Menubar: an incident's files reveal in Finder or open their console file drawer.
- Menubar: Settings → Advisor lists the models recommended for this Mac with a Use button; onboarding step 9 offers the recommendation with Use recommended.
secure-agent worktreesexplains a 403: while the menu bar app runs, changes go through its console.- Owner-only
GET /debug/pprof/on the control socket: Go runtime profiles
for the owner uid and the pinned menubar app; refused for agent and foreign peers, never admitted by the console token, not served on the proxy listener.
- Unified Attention Center. The console now groups resource approvals,
blocked guard requests, critical findings and incidents, and uninspected egress by complete agent session. Each group shows its workspace, process count, memory, CPU, and the reason it needs review, with existing scoped actions available directly from the queue. Signals that cannot be safely attributed to one live session remain in an explicit agent-level group. The grouping is computed once, daemon-side (/posture → groups): the console renders it as served and the menubar's attention predicate reads the posture state instead of re-deriving it from flags and incidents, so the icon, hero, badge, and console queue can never disagree.
- Resource Mission Control for local agent fleets. The daemon now samples
live RSS and CPU for attributed processes, groups them into stable session families, retains one hour of five-second history, and diagnoses heavy memory, full-core CPU, rapid growth, idle retention, runaway children, and orphan drift. GET /resources exposes process topology, trends, evidence, thresholds, confidence, and reclaim estimates. The web console ranks and charts whole sessions with click-through family detail; the native menu bar shows family CPU/memory and supports Impact sorting. A local Resource Flight Recorder preserves bounded pressure episodes when diagnoses appear, change, or memory escalates another 25%, retaining whole-session totals, the root, the 64 highest-impact processes, and a ten-minute prelude after exit. Each episode adds up to 80 redacted activity references from the captured process family, marks them on the trend chart, and identifies the activity observed during the steepest sample-to-sample memory rise without claiming that temporal proximity proves causation. Exact nanosecond and per-process lifetime checks prevent recycled PIDs from importing unrelated activity; a bounded settling window re-enriches episodes from events persisted just after the initial capture.
- Fleet posture heartbeats (`status` envelope kind). Every fleeted node
now pushes a status envelope — at boot, every fleet.heartbeat_interval_sec (default 60s), and immediately on posture-state transitions — carrying the node's own /posture headline (posture_state, posture_summary, needs_you), hostname, agent count, and operator-defined fleet.labels (env/role/team grouping). Heartbeats bypass per-sink events: filters on purpose: liveness that can be unsubscribed is indistinguishable from a dead node. Posture computation was extracted (computePosture) so the collector renders the exact headline the console and menubar show.
- Per-rule notification overrides (`/notify/rules`). Default policy is
now severity ≥ 3 notifies (was ≥ 2); per-rule Default / Always / Never overrides persist at ~/.config/secure-agent/notify-rules.json and apply to the menu bar and the web console alike (console bell menu, Settings → Notifications section). Sets/clears are audited.
- Advisor actions have a feedback loop.
/statusnow carries
advisor_health (circuit-breaker state, last error, queue depth); re-run requests show a pending spinner, the fresh verdict lands with a notice, timeouts say so honestly after 90s, and a paused advisor renders "Advisor offline — verdicts paused" instead of a clickable dead button (in the web console AND the menu bar sheet).
- Human-readable process labels. The console event timeline and incident
cards show agent names (not bare PIDs); menu bar session rows show the project folder next to the PID; keychain flags carry a "this is usually routine" context note.
- Regression coverage for every "nothing happens" bug class. The DOM
harness is now stateful: Allow removes the suggestion, Dismiss removes the card, re-triage shows pending then the landed verdict, advisor-offline renders the disabled state, tabs hide/show panels. Connection states are pinned too: 403 → "Session expired — reopen from the menu bar" (never "daemon down"), network failure → retry banner, token persists across reloads via sessionStorage with no #ct fragment. Go + Swift suites cover the server/client halves (posture unacted filter, retriage lifecycle, dismiss echo, peer-gate disposition policy for /mute, /flags/acknowledge, /allowlist, /notify/rules, DaemonClientError human descriptions, advisor_health decoding, NotifyRulesResponse decoding).
- Codex calls on a ChatGPT plan (provider
chatgpt) and Hermesopenai-codexcalls count as plan calls, not unpriced. - Events rows name every trace kind (TOOL, TURN, MODEL), key tool calls by session and call id and turns/model calls by session, timestamp, model and tokens so same-timestamp rows never collide, sort newest first with the date shown when not from today, and carry
price_class; a Hermes or openclaw database resumes from its saved watermark, zero included, instead of restarting at the last 24 h. POST /cleanup/advise: queues a project (a repository, ormachinefor caches outside any repository) for a local-advisor cleanup plan built from its worktrees and clutter: a summary and at most 5 steps; paths, branch names and reasons go to the model inside<evidence>.GET /cleanupadvice: the stored plan per project; plans never change a verdict or what an action accepts.POST /worktrees/ask: resumes the Claude Code or Codex session that worked in a keep or review worktree with a fixed request — open a pull request for work worth keeping, or say the worktree can go;--fork-session, $1.00 cap (Claude Code), 15-minute bound, own process group, one at a time.- Agent answers (
pr,removable,keep) are recorded inagent_asks, the audit trail and the cleanup ledger;GET /worktrees/asks;/worktreescarries each worktree's newest ask. - Tool clean commands and agent asks run in their own process group: a timeout stops everything they started.
GET /cleanup:.tmpand.quarantinedirectories in and around repositories, build output inside repositories, developer tool caches and per-app caches, each with size, last touched, project and how to clear it.POST /cleanup/trashmoves one inventory item to the Trash on its volume;POST /cleanup/cleanruns a tool cache's own clean command; both re-check the inventory and skip places with a live agent session.- Items inside a repository are offered for the Trash only when git ignores them and they hold no tracked files.
- Cleanup ledger books
trash:<kind>andclean:<tool>rows; totals count Trash moves apart astrashed_bytes. POST /mutetakes an optionalagentthat scopes the mute to that agent only.GET /mute,/snapshotmutesandDELETE /mutecarry the mute'sagent.- Flag explanations and patterns serve
mute-class/mute-rule-hostwith the agent inbodyand a label naming it ("Mute keychain access for codex"). GET /muterows carrytitle, the rule's human title.GET /worktreessizes:size_bytesper worktree and per repository (allocated bytes, measured in the background and cached for an hour;sizingwhile pending),summary.size_bytes,summary.removable_bytes,volumes(mount, total, free) andreclaimedtotals.POST /worktrees/removeanswers the bytes reclaimed.POST /worktrees/advise: queues a worktree for a local-advisor note (remove,revieworkeepwith a rationale); branch names, paths and commit subjects go to the model inside<evidence>.GET /worktreesadvice: the stored note per worktree path at its current HEAD; notes never change the state or whatPOST /worktrees/removeaccepts.- Operator labels: allow, mute, path allow and guard answers record ok or not ok for the case (rule, agent, path or host);
POST /labelstakes Mark as routine / Mark as not ok and kills from a finding. - Flag explanations count the operator's labels on the same case;
/advisor/planshows the 5 most similar and, after 3 consistent labels, suggests the offered allow (or kill); triage and plan prompts carry the similar labels. - Playbooks: every rule has a fixed response — why it fires, what to do now, how to prevent it (guard rules, settings, secret handling, agent instructions, workflow), which actions apply.
GET/POST /advisor/plan: the playbook always; on request the local advisor writes a plan from this machine's context (explanation, session, masked file excerpt, history, local policy) — why, prevention, behavior changes, remediation, recommended actions; stale when new evidence arrives.GET /costs?by=provider: recorded provider, else the vendor whose price table resolves the model, else(unknown).GET /costs?by=day&tz=<minutes>: spend per local calendar day, oldest first.GET /costs:tzoutside-840..840returns400.- Claude Code model calls record provider
anthropic. GET /files/detail: facts, findings, agent-session accesses, transcript hits and a masked excerpt for a file stored evidence names.POST /files/revealandPOST /files/open: Finder selects the file or the default text editor opens it; audited.- Firewall
Engine.Mask: fingerprint and pattern hits replaced by[REDACTED:<rule>], with a rescan that reports anything left. - Transcript secret hits and their flag evidence carry the byte offset of the line.
/advisor/discoverreturnsmachine(chip, RAM, free disk) andrecommendations: installed chat models and a verified catalog (Qwen3.5 4B/9B, Qwen3.6 35B-A3B, Qwen3.8 27B 4-/8-bit) ranked by fit for this machine; Ollama discovery reports model sizes.GET /worktrees: every git worktree found from session workspaces, agent worktree directories,worktrees.rootsand a saved repo list, each with stateremove,review,keep,pruneormain, reasons,staleand last activity.- Worktree merge detection by ancestry or a zero-context patch-id match for squash merges; local git only.
- Worktree rows list precious ignored files (
.env*,*.pem,*.key,.tmp/,.claude/,.remember/) with file count and size. - Worktree directories git no longer lists are reported as orphans.
POST /worktrees/reposadds or hides a repository on the saved list.worktrees.rootsandworktrees.stale_dayssettings, applied live.POST /worktrees/remove: removes a worktree only when a fresh inspection saysremove(git worktree remove, never--force; the branch stays), or prunes missing ones; 409 carries the fresh verdict.worktree-removeandworktree-pruneaudit rows.- A missing worktree that is locked reads
keep(git does not prune it). - A worktree active in the last 24 hours is never
remove. GET /patterns: repeating flags grouped by agent, rule and subject, with cadence, pids, sessions, disposition, summary and actions./snapshotpatterns./posturepatternitems in place of the flag items a pattern covers.POST /flags/acknowledgeacceptsflag_ids(up to 500) in one transaction./posturemachinegroup for collector, hook and machine-wide egress items./egress/uninspectedrows carryidentity,first_seenandsession_id.identity.class(vendor,telemetry,cloud) on/egress/uninspected,/egress/endpointand/snapshotsuggestions.- Egress drill-down groups vendor-class endpoints (no
infra) into one row per agent and vendor with Allow all and Evidence. - Egress drill-down keeps cloud and telemetry hosts in the unknown list with the org shown after the host.
/snapshotsuggestions carryidentity./snapshotsuggestions skip vendor-class hosts.- Advisor host prompt carries the host's identity, reverse name and the agents it is already allowed for.
- Hermes Agent process matcher (
hermes-agent,/.hermes/). - Hermes Agent sessions from
state.dband each profile'sstate.db: turns, tool calls and model calls;hermes_homesetting. - Hermes sessions carry the repo, branch and parent session Hermes records.
/doctorhermescheck: databases read, watermarks and last poll, or not installed.- openclaw conversations from
lcm.db: sessions, turns, tool calls and model calls;openclaw_homesetting. /doctortrace coverage names the traced harnesses./doctorcollectors check prints the openclaw, opencode and hermes database, watermark and last poll./costsrows and total splitunpriced_callsintounknown_model_callsandunpriced_model_calls;plan_callsandlocal_callsare separate counters, excluded fromunpriced_calls./costs?by=modelrows carryproviderandclass.GET /costs/unpriced: zero-cost calls by harness, provider and model with their class.- opencode and codex model calls record the provider.
GET /flags/{id}/explain: one plain sentence per rule, no pids.- Explain carries the file's category and owner, each destination's org, allowlist state and read→connect gap, the session with the nearest tool call and model.
- Explain carries one disposition (
acknowledged→benign-likelyat advisor confidence ≥ 0.85 →warning→critical). - Explain lists the applicable actions with the exact request each performs; the advisor's suggestion is marked recommended.
/flagsand/snapshotstampexplainon the first 25 unacknowledged flags without network lookups.secret-in-transcriptflag: tailed harness transcript lines are scanned with
the firewall's known-secret fingerprints and typed patterns; a hit carries the rule id, transcript path, and session id (never the matched text), is severity 3 for a registered secret and 2 for a typed pattern, and repeats per (path, rule) are collapsed.
- Codex model attribution. Codex model calls carry the model id from the
rollout's thread settings and are priced from the price tables; a model the tables do not know costs 0 and counts as unpriced — never a fabricated price.
- User price table.
pricinginconfig.yamlsets USD per 1M input and
output tokens by exact model id or prefix, wins over the built-in table, and applies live on change. Built-in prices now cover OpenAI and Google model families alongside Anthropic.
- Sequence numbers + gap detection (`boot`/`seq` on every envelope). The
Publisher stamps each envelope with a per-boot monotonic sequence; the collector tracks holes with a 90s grace for retries/reordering and surfaces confirmed loss per node (gaps in /fleet, "N deliveries lost" warnings in the overview). Delivery stays best-effort — but backlog-cap drops and collector downtime are now *visible* instead of silent. A new boot (daemon restart) resets the expectation; legacy unsequenced envelopes skip tracking.
- Cross-node rule aggregation (`GET /fleet/rules`). Rolling-24h per-rule
fleet footprint: which rules are firing, on how many of the fleet's nodes, with how much critical mass — "one node is an incident; five is a bad release." Rendered as a "Rules across the fleet" table in the collector overview, sorted by node spread.
- Posture-aware collector rollup. The reference collector now harvests
what it used to discard: flag severity and incident risk feed rolling 24h counts (flags_24h, critical_flags_24h, incidents_24h, recomputed at snapshot time so they decay on quiet nodes); guard decisions break down into allow/deny; last_event (security activity) is tracked separately from last_seen (liveness). The overview page leads with a fleet headline ("2 critical · 1 stale · 12 all-clear") over cards titled by hostname with posture chips and label chips, sorted critical-first. Heartbeat nodes are stale after 3 min and "gone quiet" after 10 (was: indistinguishable from idle after 10 min); legacy event-only nodes keep the lenient 10/20-min thresholds.
- Node identity config.
fleet.hostname(display-name override) and
fleet.labels in config.yaml, carried in every status envelope.
- Rolling 24h blind-spot counter.
status.uninspected_egressnow counts
distinct endpoints seen in the last 24 hours instead of growing monotonically for the daemon's lifetime (it had inflated to ~1000); pairs silent for 7+ days are swept from the tracker.
- **Rule-level mutes (
host: "*").**POST /mutewithhost: "*"
suppresses an entire flag class (counted in status.muted_flags, all open flags of the rule acknowledged). Exposed as "Dismiss this flag class" in both UIs — including, finally, keychain flags.
- Per-flag dismiss in both UIs. Every flag card/sheet now has "Dismiss"
(reviewed-and-done) alongside "Dismiss this flag class" and "Kill" — the missing middle recourse. Posture excludes acknowledged flags, so a reviewed flag stops demanding attention everywhere.
- Notification scopes can be set per workspace.
- Sessions and traces export as OTLP/HTTP JSON spans.
- Codex sessions are traced from their rollout files.
- Stream deltas are typed per kind.
GET /sessions: durable sessions with lifecycle state, attributed from a session-start hook handshake as work begins.secure-agent cleanup advise <repo|machine>;secure-agent cleanupprints each project's plan under it.secure-agent cleanup [--kind] [--project] [--refresh] [--json],cleanup trash <path>,cleanup clean <tool>.- Cleanup ledger: every worktree removal books the bytes it gave back, every prune a row;
GET /cleanup/ledgerandsecure-agent cleanup log. secure-agent worktreeslists the biggest projects first with sizes, disk free per volume, worktree and removable totals and reclaimed so far.secure-agent worktrees advise <path>; the list view prints the note under its row.secure-agent cost --by provider|dayand--tz <minutes>(default: this machine's offset).secure-agent worktrees [--state] [--repo] [--stale] [--refresh] [--json]andsecure-agent worktrees add|hide <path>.secure-agent worktrees remove <path>andsecure-agent worktrees prune <repo>.secure-agent costprints the class breakdown and one pricing hint per unpriced model id.- Self-check.
GET /doctorandsecure-agent doctorreport pass, fail
or skip for guard-hook registration and activity, file telemetry, collectors, per-harness trace coverage, session identity, repo attribution and creation rate, tool-call pairing, Claude model-call pricing, per-kind retention, egress routing and bus drops, each failure with a one-line fix; the CLI exits 1 on any failure.
- One-command fleet enrollment (`secure-agent fleet enroll <collector-url>`).
Reads the node id from the running daemon, generates the webhook secret, merges fleet.webhooks into config.yaml (comment-preserving, backup written first; re-enrolling the same URL rotates the secret in place), and prints the single line the collector's secrets file needs. The old flow — hunt the node id, invent a secret, edit two files, restart the daemon — is gone.
secure-agent: a headless service install command registers the daemon with launchd.secure-agent worktrees removewaits up to 14 minutes.- CI: cancel stale runs, job timeouts, credential-free checkout, cgo-free Linux gate, go mod tidy, govulncheck, Dependabot, Go test shuffle. Proxy token and CA permission contracts now have unit tests (the old 0600 check was asserting a different temp path). Go toolchain 1.26.6.
POST /worktrees/remove"async": trueremoves in the background;GET /worktreesremovalsreports each removal's step and outcome for 30 minutes.- Session memory:
GET /sessions/{id}/memoryand the Sessions tab list one session's redacted activity, findings, incidents, guard decisions and resource episodes in time order, paged withbefore. - Egress episodes:
GET /egress/episodesgroups each agent's outbound connections by destination; one seen 5+ times at steady intervals is recurring and becomes a Home decision. - Expected egress:
POST /expected-egressmarks an observed destination, or a complete activity scope (executable, harness, workspace), expected;DELETErevokes it; both are audited. - Expected egress only clears the Home decision; the proxy, guard, correlator, incidents and flags never consult it.
POST /egress/episodes/{id}/assessasks the local advisor for a recurring episode's possible purpose; the console labels it as an inference.
Changed
- macOS builds use SDK 27 with an explicit macOS deployment target.
- Event store: a flag's own event and file events that count as a secret read stay past their kind's row cap for the full retention, up to 20,000 rows per kind;
file-open,file-write,file-deleteandexeckeep only their newest rows. - Row budgets: file writes 150,000 (was 10,000), connection opens and closes 30,000 each (was 5,000).
/doctorretention fails when record rows, or a kind other thanfile-open,file-write,file-deleteandexec, are evicted inside a day; it passes naming how far back those four kinds' newest rows reach.- File telemetry: a write burst the reader skips reads as "File monitoring is falling behind" after 60 s, not as a flooding writer;
es_service.unparsed_sharecounts only lines the reader parsed;es_service.flooding_sinceadded. - Console header event rate keeps a fixed width, so the status chip does not shift as events per second change.
- Console switches set
-webkit-user-selectso Safari does not select the switch label. - Findings skip shell rc and harness-settings reads as secret reads and name the raising process and its launcher.
- File telemetry's root helper drops open events on system paths (OS libraries and frameworks, app-bundle contents, Homebrew Cellar,
/devnodes, user caches) that no sensitive-file rule matches before they reach the spool. - File events from a process that already exited cost one process lookup per tagger refresh instead of one per event.
- Cleanup totals no longer count an agent's answer (
ask:*ledger rows) as a cleanup. - Menu bar icon and agent count are drawn in the console's brand purple, a lighter shade on a dark menu bar.
- Console Spend opens on the last usage reports:
GET /costs?cached=1answers at once from the daemon's usage cache (saved in the store, so it outlives a restart) while a fresh report is computed; the card reads "Updating usage cache… (cached 3h ago)" and the tile's line "updating…" until it lands. Spend no longer holds the console's first render, and/costs/planskeeps plan headroom across restarts (snapshots under a week old). .gitleaksignorefingerprints name the rewritten commits of the six known test fixtures.- Idle daemon: opencode, openclaw and Hermes polls skip an unchanged database,
/costsand/costs/unpricedreuse a report for 30 s, and transcript discovery re-lists only directories whose mtime moved. make installwaits up to 90 s for the restarted daemon to answer/statusbefore reporting file telemetry, and says "unknown" instead of asking for approvals when it gets no answer.make install/make app/make dmgsign with the first Apple Development identity when one exists (else Developer ID Application, else ad-hoc) and give every bundled binary a stable--identifier, so file telemetry's Full Disk Access grant survives rebuilds instead of resetting on every install.- Console Cleanup view: each worktree row has a state stripe (green remove, amber review, violet keep, grey prune); the keep chip is violet instead of red.
- CI fails when a tracked file holds a
/Users/<name>or/Volumes/<name>path outside the placeholder names fixtures use. live_acceptance.shreads the workspace root fromSECURE_AGENT_WORKSPACE_ROOT, defaulting to two levels above the main checkout.- Console: shows the posture banner capped at 3 rows off Home and empty on Home, agent ids in their own case, Egress led by the uninspected endpoints with zero-hit rules folded into one row, and Processes at full width.
GET /worktreesandGET /cleanupanswer from the last scan at once; one older than 10 minutes answers while a background rescan replaces it (refreshing: true).- Worktree and clutter sizes older than an hour keep answering while measured again, instead of dropping to "measuring…".
- The last worktree scan, cleanup inventory and their sizes are saved in the store (
scan_cache) and answer after a daemon restart. - Console Cleanup view: re-reads while a report is refreshing; the scan line names the cached scan's age.
- A stored advisor verdict publishes its flag as a stream delta.
- The served allow-host label for an IPv6 address names the address owner instead of the literal.
- Menu bar hero: state, color and subtitle come from
/posture. - Menu bar: flags refetch only on
flag,posture,guard-promptandguard-resolvedstream frames. - Menu bar: an identical flags refetch leaves the popover unchanged.
- Menu bar: notification and mute-list titles come from the daemon.
- The console token admits a method other than GET or HEAD only when the route lists it in
MutatingMethodsorConsoleMethods. /guard/path-allowis console-admitted on the proxy listener.POST /guard/path-allowis a pinned-UI mutation.- Console finding cards offer the served allow-path action.
- Menu bar Open console loads the fresh console link into the existing console tab before focusing it.
- Console notification preferences moved from the header to the Policy tab.
/worktrees*routes are NoAgent: agent processes cannot read or change the machine's worktrees.- Console Overview: Memory by session is Memory by family — one bar per live process family, its RSS counted once,
N sessionsin the label. - Console Overview: the Memory by family badge counts agent families, not infra.
- Events carry a
(session_id, kind, id)index; the event store writes planner statistics (PRAGMA optimize,analysis_limit1000) at open and after each prune. - Untagged keychain flags name the agent the match strings give the exe.
- Untagged keychain flags with a version-number exe name carry the directory that names it (
untagged:claude 2.1.280). - Console Flags list hides flags a pattern covers.
/postureitemsandgroupscome from one pass./postureputs every item in exactly one group./posturegroup items sum toneeds_you./posturegroupsinclude unacknowledged severity-2 flags with priority 1, rule title and disposition./posturegroupsinclude pending resource decisions asresourceitems./postureitemsinclude pending resource decisions asresource_pressureitems./postureitemscarry oneuninspected_egressitem per group with uninspected egress to unknown endpoints./postureegress items and their counts exclude known CDN/cloud carriers./postureitemscarry open critical incidents at severity 3./posturestateiscriticalwhile a critical incident is open./postureitemscarry open high incidents at severity 2./postureitemscarry other incidents open more than 72h at severity 1.- Console Attention badge shows
posture.needs_you. - File opens, writes and deletes from processes outside every agent family are not stored unless they raise a flag.
- Event pruning seeks a
(kind, id)index: kinds by index skip-scan, each kind's budget cut at its budget-th newest id. - Insert-driven pruning runs at most once per 30 s.
- The event store opens WAL with
synchronous=NORMAL. - Posture and attention: a flag the advisor judged benign at confidence ≥ 0.85 is severity 1 (
attention, "Finding, likely benign"), nevercritical. - Console: an explained flag's card and Attention item show who, what and the one verdict with the served actions as buttons; raw evidence, pid and timestamps sit behind Details.
- Console Resources: a one-row machine strip (headroom, memory, CPU, swap; pressure and thermal chips) replaces the host block.
- Console Resources: families with a diagnosis lead as at most five needs-attention cards.
- Console Resources: families group by harness, sorted by memory, with orchestrated children nested under their parent and infrastructure in one trailing, uncounted group.
- Console Resources: families are named harness · repo@branch or harness · folder, never by pid; the Overview memory chart uses the same names.
- Console Resources: View family opens a drawer with usage, processes, recent activity, findings and terminate actions instead of switching tabs.
- Console Resources: the board fits a 375px phone at any font metrics; family rows and long labels wrap instead of overflowing.
- Console: the Events tab lists the newest 50 rows, the family process table 12 and an Agents group 8 instances, each with Show more.
- Console: pid-scoped event filtering opens the Events tab.
- Console: "View session in timeline" opens the session in the Sessions tab with its trace, keeping Events scoped to it.
- Console: the drawer's Copy button stays hidden outside incident reports.
- Advisor host pre-assessment skips vendor-class hosts as well as CDN carriers.
- Advisor host pre-assessment still covers cloud, telemetry and unknown hosts.
- Transcript discovery by harness shape: Claude, Cursor, Codex and
Antigravity transcripts are found by per-harness globs re-resolved every 15 s instead of recursive walks of their trees; files modified in the last two minutes are tailed every second; tail offsets are saved at most every 30 s and on shutdown; the ES spool tail skips a spool whose size and mtime are unchanged.
- The Endpoint Security collector ships inside the app bundle
(Contents/MacOS/secure-agent-esd plus Contents/Library/LaunchDaemons/com.cavi-ai.secure-agent-esd.plist) and is registered with SMAppService.daemon: the user approves Secure Agent in Login Items and Full Disk Access, with no admin password. The collector verifies its code signature instead of an install-time hash, the setup card names Secure Agent instead of the launchd label, and a collector installed by an earlier version under /Library is removed from the card (one admin prompt) or by packaging/uninstall.sh.
- API HTTP handlers for resources, kill, firewall, and guard live in their own files (
api.go1662→940). Same package, no behavior change. - The daemon composition root is split into named stages (
buildResourceStack,
buildFleetAndOTLP, runResourceLoop, wireEgressOverrides, buildAdvisorHooks, buildResourcePolicyUpdater, makeResourceExecutor, startCollectors) — Build 477→197 lines of sequencing. Same wiring, no behavior change.
- Console: shows the posture banner capped at 3 rows off Home and empty on Home, agent ids in their own case, Egress led by the uninspected endpoints with zero-hit rules folded into one row, and Processes at full width.
- Console Cleanup view: re-reads while a report is refreshing; the scan line names the cached scan's age.
- The console token admits a method other than GET or HEAD only when the route lists it in
MutatingMethodsorConsoleMethods. /guard/path-allowis console-admitted on the proxy listener.- Console finding cards offer the served allow-path action.
- Console notification preferences moved from the header to the Policy tab.
- Console Overview: Memory by session is Memory by family — one bar per live process family, its RSS counted once,
N sessionsin the label. - Console Overview: the Memory by family badge counts agent families, not infra.
- Console Flags list hides flags a pattern covers.
- Console Attention badge shows
posture.needs_you. - Console: an explained flag's card and Attention item show who, what and the one verdict with the served actions as buttons; raw evidence, pid and timestamps sit behind Details.
- Console Resources: a one-row machine strip (headroom, memory, CPU, swap; pressure and thermal chips) replaces the host block.
- Console Resources: families with a diagnosis lead as at most five needs-attention cards.
- Console Resources: families group by harness, sorted by memory, with orchestrated children nested under their parent and infrastructure in one trailing, uncounted group.
- Console Resources: families are named harness · repo@branch or harness · folder, never by pid; the Overview memory chart uses the same names.
- Console Resources: View family opens a drawer with usage, processes, recent activity, findings and terminate actions instead of switching tabs.
- Console Resources: the board fits a 375px phone at any font metrics; family rows and long labels wrap instead of overflowing.
- Console: the Events tab lists the newest 50 rows, the family process table 12 and an Agents group 8 instances, each with Show more.
- Console: pid-scoped event filtering opens the Events tab.
- Console: "View session in timeline" opens the session in the Sessions tab with its trace, keeping Events scoped to it.
- Console: the drawer's Copy button stays hidden outside incident reports.
- Every hero count is clickable: "N flags to review" opens the top unacted flag's action sheet; uninspected/would-block opens the console egress drill-down (tab deep-links now survive the token handoff).
- The session board is its own console tab (Overview / Sessions / Agents / Egress / Findings) instead of filling the main dashboard. Overview keeps the activity trend and event timeline.
- Console SSE only refetches
/snapshoton exec, guard, and proxy-hit; file/conn events update the sparkline only. - Console layout is now tabbed (Overview / Agents / Egress / Findings)
instead of one endless page — organized by the question each view answers, with per-tab badges ("2" on Egress = uninspected endpoints waiting), deep-linkable tabs (#findings), and posture items that jump to the right tab. Posture banner + KPIs stay always-visible on top.
- Menu bar hero: state, color and subtitle come from
/posture. - Menu bar: flags refetch only on
flag,posture,guard-promptandguard-resolvedstream frames. - Menu bar: an identical flags refetch leaves the popover unchanged.
- Menu bar: notification and mute-list titles come from the daemon.
- Menu bar Open console loads the fresh console link into the existing console tab before focusing it.
- Menubar sessions are grouped by harness family again (collapsed headers with session/memory/activity aggregates, small families expanded), capped at 6 groups with a "+N more — open the console" link. The flat 50-row list is gone.
- Menu bar popover: a glance-and-act view.
GET /worktreesandGET /cleanupanswer from the last scan at once; one older than 10 minutes answers while a background rescan replaces it (refreshing: true).- Worktree and clutter sizes older than an hour keep answering while measured again, instead of dropping to "measuring…".
- The last worktree scan, cleanup inventory and their sizes are saved in the store (
scan_cache) and answer after a daemon restart. - A stored advisor verdict publishes its flag as a stream delta.
- The served allow-host label for an IPv6 address names the address owner instead of the literal.
POST /guard/path-allowis a pinned-UI mutation.- Events carry a
(session_id, kind, id)index; the event store writes planner statistics (PRAGMA optimize,analysis_limit1000) at open and after each prune. - Untagged keychain flags name the agent the match strings give the exe.
- Untagged keychain flags with a version-number exe name carry the directory that names it (
untagged:claude 2.1.280). /postureitemsandgroupscome from one pass./postureputs every item in exactly one group./posturegroup items sum toneeds_you./posturegroupsinclude unacknowledged severity-2 flags with priority 1, rule title and disposition./posturegroupsinclude pending resource decisions asresourceitems./postureitemsinclude pending resource decisions asresource_pressureitems./postureitemscarry oneuninspected_egressitem per group with uninspected egress to unknown endpoints./postureegress items and their counts exclude known CDN/cloud carriers./postureitemscarry open critical incidents at severity 3./posturestateiscriticalwhile a critical incident is open./postureitemscarry open high incidents at severity 2./postureitemscarry other incidents open more than 72h at severity 1.- File opens, writes and deletes from processes outside every agent family are not stored unless they raise a flag.
- Event pruning seeks a
(kind, id)index: kinds by index skip-scan, each kind's budget cut at its budget-th newest id. - Insert-driven pruning runs at most once per 30 s.
- The event store opens WAL with
synchronous=NORMAL. - Posture and attention: a flag the advisor judged benign at confidence ≥ 0.85 is severity 1 (
attention, "Finding, likely benign"), nevercritical. - Advisor host pre-assessment skips vendor-class hosts as well as CDN carriers.
- Advisor host pre-assessment still covers cloud, telemetry and unknown hosts.
- Transcript discovery by harness shape: Claude, Cursor, Codex and
Antigravity transcripts are found by per-harness globs re-resolved every 15 s instead of recursive walks of their trees; files modified in the last two minutes are tailed every second; tail offsets are saved at most every 30 s and on shutdown; the ES spool tail skips a spool whose size and mtime are unchanged.
- API HTTP handlers for resources, kill, firewall, and guard live in their own files (
api.go1662→940). Same package, no behavior change. - The daemon composition root is split into named stages (
buildResourceStack,
buildFleetAndOTLP, runResourceLoop, wireEgressOverrides, buildAdvisorHooks, buildResourcePolicyUpdater, makeResourceExecutor, startCollectors) — Build 477→197 lines of sequencing. Same wiring, no behavior change.
- Overview shows a 3-row session strip (project folder, RSS, last seen, needs-you) that opens the Sessions tab. The full board stays on Sessions.
- Uninspected-egress headline now counts only unknown endpoints. Known CDN/cloud infrastructure (Cloudflare, Google, AWS, GitHub, Akamai, Fastly, Azure — by suffix, CIDR, and cached PTR for bare IPs) is collapsed into a separate
uninspected_infrafigure and one collapsible group in the drill-down, excluded from the hero, posture, allowlist suggestions, and advisor pre-assessment. - Process tagger walks every 3s while agents are tagged (idle stays 5s).
PostToolUseis onesecret_guard.pyspawn (injection scan + activity log);injection_scan.pyis no longer a second process.- Collector storage sits behind a seam. Persistence is now a small
envelopeLog interface (append/replay/query) with the JSONL backend as the reference implementation — the swap point for a production-grade SQLite store (retention, TLS, alerting on the roadmap).
- `keychain-access` demoted to severity 1 (informational). Legitimate
tooling opens keychain DBs for TLS trust evaluation and credential helpers — it never justifies a page. The security(1) CLI exec rule (keychain-security-cli) stays severity 3.
- Repeat suppression applies to every immediate-fire rule, not only keychain flags.
- The Endpoint Security collector ships inside the app bundle
(Contents/MacOS/secure-agent-esd plus Contents/Library/LaunchDaemons/com.cavi-ai.secure-agent-esd.plist) and is registered with SMAppService.daemon: the user approves Secure Agent in Login Items and Full Disk Access, with no admin password. The collector verifies its code signature instead of an install-time hash, the setup card names Secure Agent instead of the launchd label, and a collector installed by an earlier version under /Library is removed from the card (one admin prompt) or by packaging/uninstall.sh.
live_acceptance.shreads the workspace root fromSECURE_AGENT_WORKSPACE_ROOT, defaulting to two levels above the main checkout.
Removed
- Menu bar: the unused flag action, incident detail and process detail sheets.
- Menu bar: the unused flag action, incident detail and process detail sheets.
Fixed
- Doctor and posture no longer report the file-telemetry spool missing while the collector rotates it; a daemon starting mid-rotation keeps the spool tail.
- Live acceptance compares Claude turns with Claude prompts in the same capped time window; other harnesses no longer cause false turn-ratio failures.
- Local agent persistence: failed message, run, or plan writes stop dispatch before execution; aborted dispatches close their recorded runs. Stale message updates cannot reopen claimed commands or reviewed recommendations.
- Secret masking removes complete private-key envelopes, including unterminated keys. Headless output is masked before selecting its tail; oversized captures and answer files are withheld instead of exposing fragments.
- Agent chat retention now bounds ordinary messages while preserving pending recommendations.
- Menu bar: the UI has its own application identity, independent of the launcher, while preserving existing preferences and helper identities.
- Console chat renders escaped Markdown with readable lists and code blocks; finding details open from individual flags, and dismissal stays stable during refreshes.
- Guard approvals: Always allow resolves matching queued requests and removes them from the console queue.
- Menu bar: startup creates the status item before setup work. When another app copy is running, the new build offers a clear replacement choice instead of starting a competing daemon on the same socket and console port.
- Settings: a resizable sidebar groups security, monitoring, local AI, and general controls; direct Ollama chat and optional routing have their own pane rather than crowding App settings.
- Menu bar: the status icon uses a colored bitmap shield with a white check, so the status bar cannot render it as a monochrome template; attention uses a yellow shield with a black mark.
- Console Resources: the machine headroom explanation stays inside the available panel instead of being clipped.
- System Agent: the menu bar has an Ask Agent entry, Settings can enable local Agent chat without editing YAML, and the console's off state points to that control.
- System Agent: automatic model selection skips Ollama models marked embedding-only, and an explicitly selected non-chat model reports why it cannot run.
- Doctor: repo-attribution coverage counts sessions inside Git workspaces, so desktop and home-directory sessions no longer produce a false failure.
- Local model traffic: the system agent and advisor reject redirects so a local server cannot forward a chat prompt to another host.
- Findings: a newly read secret or destination raises a new read-then-connect flag even when another read in the same event repeats an earlier pattern.
- Findings: a macOS trust-store read, or a keychain file opened by a TLS client, no longer counts as a secret read for "read a secret, then connected out"; a keychain file opened by a byte-copy tool (
cat,cp,tar,curl, …) or read by an agent tool still does. Open flags from those reads are acknowledged at start. - Findings: a read item names the process that opened the file (
evidence[].pid,evidence[].exe), not the process that connected out. - Findings: a credential used with its owner (gh's token to GitHub,
~/.awsto AWS,~/.azureto Azure,~/.config/gcloudto Google,~/.dockerto Docker registries) from the reading process's tree (the reader, its ancestors, its descendants) is counted instatus.credential_owner_uses, not flagged;credential_ownersconfig. - Findings: one read-then-connect pattern (agent, reader, file, destination org) raises one flag per hour; repeats fold into it (
repeats,last_seen) and patterns count them. - Findings:
.envtemplates and~/.docker/completions(not_secret_paths) are not secret reads; open flags from them are acknowledged at start. - Console findings: a read-then-connect card names the reading process, the file and where it went (
gh (claude) read ~/.config/gh/hosts.yml, then reached Google (…)), and its verdict says why: the destination does not own the file, no owner is on record, an agent tool read it, or a process outside the reader's tree connected./patternsservesflagsanddestinations; read evidence carriesowners. - Uninspected egress: a bare IP in Cloudflare's, GitHub's or Fastly's published ranges, or in CloudFront's IPv6 block, counts as a known carrier, not an unknown endpoint, and its identity names the same org.
- Worktree removal: a worktree with populated submodules failed with git's "working trees containing submodules cannot be moved or removed"; it is removed with
--forceonce each submodule is clean and every submodule commit (branches, HEAD, stash) is on a remote. - A submodule commit or stash that lives only in the worktree's own git dir keeps the worktree, with the submodule named in the reasons.
make app/make install: a Swift edit that compiles to identical objects (comments, whitespace) no longer fails the build as a stale menubar binary; the product is rewritten on every build.- Worktree removal:
git worktree removeruns under a 10-minute deadline instead of the 10-second read limit that killed it mid-delete on large trees (200,000 files take 16 s), and finishes when the request is canceled. - A git failure during removal says whether the worktree is still on disk and registered.
- A Code conversation in the Claude desktop app is rooted at its own
claudeprocess and ends when that process exits; the app (claude-desktop, infra) no longer holds every conversation open until it quits. - Console:
claude-desktopshows as Claude, counted as infra. - Local advisor: finding plans, worktree notes and cleanup plans run under a deadline of at least 5 minutes instead of the triage
timeout_ms; a reasoning model at 60 s timed them out. - Local advisor: cleanup plans get 4,096 tokens (was 2,048); a reasoning model spent the whole 2,048 thinking and returned no plan.
- A daemon writes
guard-cwd-overrides.jsonbeside its own socket. - A second daemon with a socket elsewhere no longer rewrites the default
guard-cwd-overrides.jsonthe hook reads. - Keychain flags from the last hour stamped
untagged:<exe>are relabeled to the agent when the tagger tags that pid. - The console receives each relabeled keychain flag as a flag delta.
- Menu bar: a lost daemon connection also clears posture and the pending guard prompt.
- Menu bar: the Sessions header count matches the "more" overflow count.
- The console token no longer reaches the owner-level
DELETE /guard/rules. - Flag sentences and attention labels keep agent ids such as
untagged:nodeas written. - Posture and
/doctorreport "File monitoring writer is flooding" only while the spool is still being written (within 2 min); garbage left by a removed writer no longer masks the service state. A spool whose service is not loaded shows "File monitoring is off" with the steps to enable it. - Secret patterns count only where the match starts a token (not after a base64 or base64url character, except a JSON
\n,\tor\rescape): vendor-key shapes inside encrypted reasoning items and other encoded blobs no longer raise secret-in-transcript or proxy findings. - Endpoint detail lists an allowance whose approved parent domain covers the host.
- Endpoint drawer resolves sessions by id, so sessions beyond the newest 1,000 are attributed.
- A second process in the same working directory gets its own session, never another root's.
- A codex rollout session joins the process holding the rollout open: root pid set, that tree's process-tree session merged in.
/doctortrace coverage counts transcript and hook sessions seen since boot, not only those started since boot.- A session upsert stores a new parent and never clears a stored one.
- Every event from a pid already resolved carries its session id, not only the first.
- A hook-stamped session carries its process tree's root pid and absorbs that tree's process-tree session.
- Sessions first seen through a trace event record
transcriptconfidence, nothook. - Codex model calls name the model from
turn_contextwhen the rollout has nothread_settings_appliedline. - A codex rollout resumed from a saved offset keeps its session and model.
- Claude
<synthetic>records no longer count as model calls. - A vendor-prefixed model id is priced by its unprefixed entry.
- Spool tailer: lines under 16 bytes or not starting with
{are rejected
before JSON parsing; a per-tick 4 MiB drain budget skips the rest of a flooded tail in bulk instead of scanning it, and posture/doctor report a flooding writer (unparsed share, MB skipped) instead of parsing garbage.
- Console: live-stream frames mark only the panels that read the changed data.
- Console: only the active tab's panels, the header and the tab badges render, at most every 250 ms per panel; hidden panels render on tab switch.
- Console: list panels (findings, attention, incidents, audit, sessions rail, agents, firewall, events) patch rows by key, so focus, open disclosures and a click during a render survive.
- Console: a panel waits while the pointer is down in it or one of its controls has focus (up to 3 s).
- Console: allow, mute, unmute, promote/demote, incident status, guard and resource decisions, source add/remove, allowlist remove and dismiss change the card before the request, revert on failure and leave a 4 s inline note on success.
- Verified by DOM checks on a 300-event burst (hidden-tab render counts, an open rail group, a focused button, a mid-burst Dismiss click) and on allow success and failure.
- Sessions: a tagged child process (a shell under a harness) joins its harness
family's session instead of minting its own — the tagger now reports the family root on every tag, not only in the process listing.
- Sessions: a session is ended only when its root process is confirmed gone,
not when one process-table sample happened to miss it.
- Sessions: orchestrated runs are nested under their orchestrator's session —
the parent lookup walks the OS ancestry past the child's own harness match instead of a chain that stopped at the first match.
- Model pricing: explicit entries for every current Anthropic model (Fable
5.x, Opus 5.5/5/4.8/4.7/4.6/4.5, Sonnet 5/4.6/4.5, Haiku 4.5) at list price; a family prefix only absorbs a date or -latest suffix, so a newer version or a -pro/-mini variant is never billed at another model's price — it is unpriced until an entry exists.
- Sensitive-path classifier: globs with a directory component
(~/.kube/config, ~/.claude/settings.json, the merged guard-rule paths) match the full path only; previously the file name alone matched, so any config, settings.json, hosts.yml, credentials, or shell rc file anywhere classified as sensitive and seeded sensitive-read-then-connect.
- Flag evidence
readitems carryrule(which classifier rule or glob
matched); ES file events from the daemon's own pid are dropped at ingest.
- Event retention: every event kind has its own row budget; the shared
10,000-row cap over all non-trace kinds let a file-open burst evict hook-activity, connection, and transcript-hit rows, which made the posture hook item and the acceptance gate report a firing hook as silent.
- ES grant flow: the Full Disk Access entry is the helper binary
com.cavi-ai.secure-agent-esd, not eslogger — the setup card now names it exactly, and the retry-interval copy matches the real 60s backoff.
- ES service probe parses the FIRST top-level
state =fromlaunchctl print
(nested sections repeat the key and previously overwrote the real service state); the last-exit-code annotation is kept.
- Posture emits at most one item per collector: the spool-based ES probe
supersedes the tailer heartbeat when it reports a failure, so a crash-looping root service no longer double-counts under the eslogger id.
- Session repo identity walks up to the enclosing git root (workspaces are
often subdirectories of a checkout), reports empty when no repo encloses the workspace instead of inventing one from the basename, and the memoized resolution expires after 10 minutes instead of living for the process lifetime.
- Codex rollout discovery reads
CODEX_HOMEoff live codex processes (same
mechanism as ps eww), so orchestrators that relocate the rollout store no longer blind codex tracing until a daemon restart.
- opencode model attribution reads the current schema (
message.data.modelID
top-level) with the older nested model.modelID as fallback — model calls carry their model id again instead of landing unpriced with an empty model.
- Privileged ES collector: the integrity hash moved from the console-user-owned
spool directory to root-owned /Library/Application Support/secure-agent, the binary self-check now refuses non-root-owned executables in addition to writable ones, and the expected pre-grant eslogger permission failure retries inside the process (60s backoff) instead of respawn-churning through launchd.
- Acceptance gate: the ES service check no longer parses JSON through
quote-broken shell interpolation (every probe read as absent), treats not-loaded as a failure when spool-based, asserts guard hook ACTIVITY (events in the last hour) instead of settings.json registration, and scopes the repo-coverage and turn-ratio assertions to the current daemon's boot window — rows and transcript records from before boot cannot be fixed retroactively and previously made the ratio assertions measure old binaries.
- Acceptance gate: time-window queries wrap stored RFC3339 timestamps in
datetime() before comparing — the raw string compare against sqlite's space-separated format made every row from the same day read as "in the last hour", which hid session floods and inflated hook-activity counts.
- Transcript tailer no longer skips new content: a boot re-seed only applies
to files the tailer was already following (tracked files keep their saved offsets), and a transcript discovered for the first time under an already- tracked target is read from the start instead of from its current end — session one in a workspace and lines written while the daemon was down are ingested again.
- Daemon startup repairs rows written by older resolvers: sessions whose repo
carries the old basename heuristic's signature are re-resolved against the enclosing git root, and tool-call rows stranded at "running" past ten minutes (lost completion lines) are closed as error.
- Process-discovered sessions stamp
started_atwith the process's real
start time instead of the discovery time — a daemon restart no longer makes every live agent look like a session created in the last hour.
- Guard hook: read-only socket reads against the agent daemon are allow-listed
by RESOLVED target (a $SOCK variable or http://unix/<path> URL now matches the socket path, not the literal token), covering GETs to /status, /resources, /healthz and /posture; any write verb or /guard path against the socket is still denied, and loopback /guard requests are denied regardless.
- Privileged ES collector exits 0 on failures launchd cannot fix (not root,
binary-integrity failure, eslogger missing) so the service stops crash-looping; genuine startup failures still exit non-zero.
- ES collector integrity check trims the recorded hash — the installer wrote
it via awk '{print $1}' > file, leaving a trailing newline that made every start read as tampered; the installer now writes it without one.
- Flag evidence is structured (
kind/label/sub/ts) instead of display
strings every client re-parsed with its own regexes; the console evidence chain and the menubar action sheet render the served fields, and rows written by older daemons still decode as plain-text items.
- Flag rule titles are served by the daemon (
flag.title) instead of being
copied into the console's RULE_TITLES, the menubar notification switch, and the posture copy — one table, every surface agrees; the client tables remain only as fallbacks for older daemons.
- Console:
claude-desktopshows as Claude, counted as infra. - The console receives each relabeled keychain flag as a flag delta.
- Console: live-stream frames mark only the panels that read the changed data.
- Console: only the active tab's panels, the header and the tab badges render, at most every 250 ms per panel; hidden panels render on tab switch.
- Console: list panels (findings, attention, incidents, audit, sessions rail, agents, firewall, events) patch rows by key, so focus, open disclosures and a click during a render survive.
- Console: a panel waits while the pointer is down in it or one of its controls has focus (up to 3 s).
- Console: allow, mute, unmute, promote/demote, incident status, guard and resource decisions, source add/remove, allowlist remove and dismiss change the card before the request, revert on failure and leave a 4 s inline note on success.
- **"Open console" was a greyed-out dead end when the inspection proxy was
off.** The footer button is now the way OUT of the off state: it offers a one-click "Turn on & open" flow — writes proxy_enabled: true into config.yaml (narrow line-based edit, every other byte preserved), bounces the daemon (waiting for the old one to release the socket so the new one wins the bind race), waits for the port, and opens the console. The button shows an "Enabling…" state and fails loudly if the port never comes up.
- Duplicate console tabs. "Open console" now focuses an already-open
console tab in Safari or Chrome (AppleScript, with plain-open fallback for other browsers and for Automation-consent denial) instead of spawning a fresh dead-end tab on every click.
- Empty fleet panel was permanent noise. The console's Fleet nodes panel
now hides entirely when the node has no collector webhooks configured (/fleet reports fleet_configured), instead of a forever-empty "No remote fleet nodes registered" placeholder on single-machine installs.
- **Console whitelist drift (the "everything runs but the console says it
can't reach the daemon" bug). The proxy listener's console-token whitelist (`isConsoleAPIPath`) had fallen behind the console's fetches: `/stats/rollup`, `/mute`, `/allowlist(+suggestions)` and others answered 407** in the browser, silently blanking the Activity chart, the muted list, and the egress suggestions. The whitelist now covers every endpoint the console fetches, and TestConsoleAPIPathsCoverWebApp parses web_dist/app.js and fails CI on any future drift.
- **THE dashboard-killer:
/fleetreturns a node-status OBJECT, but the
console did fleet.map on it** — throwing inside renderFleet on every poll and silently killing every panel after it in renderAll (flags, incidents, audit, sources, events, activity chart). The live console rendered only agents/firewall/KPIs; the rest was permanently dead. renderFleet now accepts both shapes (object → local node card), and renderAll is crash-isolated per panel so no single bad payload can ever blank half the page again. The DOM harness's /fleet fixture lied (array) — it now matches the real endpoint, with a regression check that panels after fleet render.
- Console: the inspector docks in place without a background scrim.
- Console: IPv6 addresses are accepted as allowed endpoints.
- Console: session rows show the harness icon next to the session name.
- Console: the guard-prompt drawer shows the advisor's recommendation.
- Menu bar: a lost daemon connection also clears posture and the pending guard prompt.
- Menu bar: the Sessions header count matches the "more" overflow count.
- Flag evidence is structured (
kind/label/sub/ts) instead of display
strings every client re-parsed with its own regexes; the console evidence chain and the menubar action sheet render the served fields, and rows written by older daemons still decode as plain-text items.
- Flag rule titles are served by the daemon (
flag.title) instead of being
copied into the console's RULE_TITLES, the menubar notification switch, and the posture copy — one table, every surface agrees; the client tables remain only as fallbacks for older daemons.
- Menubar: decodes
nulllist bodies as empty; transcript sheet gains Retry. - Hero "N flags to review" counted reviewed flags. The popover hero's
Attention branch counted every severity≥2 flag in the fetch window — including acknowledged ones — so it read "20 flags to review" over a list the operator had fully dealt with. It now uses the same unacted filter as the attention section (unactedFlags), and heroModel is pinned by regression tests (reviewed flags never demand review; uninspected-egress alone still earns Attention).
- Menubar build warnings cleared (dead
try?/await/variables,
optional-interpolation, implicit-strong-capture) — the package builds warning-free.
- Notification Center no longer piles up handled alerts. The menubar now
reconciles delivered banners on every poll: banners whose flag was acted on (dismissed in either UI, muted, retro-acknowledged daemon-side — all converge to acknowledged) are withdrawn, and anything older than 7 days is pruned. Previously nothing ever withdrew a delivered notification, so the Center accumulated greyed-out history for alerts the operator had already dealt with. (The reconciliation path also skips getDeliveredNotifications outside a real .app bundle — it throws in the xctest host.)
- Console session no longer dies on reload. The console token was kept
in memory only after stripping #ct= from the URL, so a single reload produced a permanent wall of 403s rendered as "can't reach the daemon". The token now persists for the life of the tab (sessionStorage), and the offline state is honest: auth-expired ("reopen the console from the menu bar") is no longer conflated with unreachable. Every fetch also carries a 5s timeout so a hung endpoint can't wedge the refresh cycle.
- Keychain alert storm had no recourse.
keychain-access(file-open of
keychain DBs) fired a severity-2 notification per (pid, path) every 15 minutes, the mute system was never consulted in the keychain code paths, and the menu bar's action sheet offered only "Kill agent" (dismiss actions required host evidence, which keychain flags don't have).
- Menu bar quick-dismiss errored on keychain groups ("no connection
target to mute") — hostless rules now fall back to the rule-level mute (host: "*"), so every flag group has a working one-gesture dismiss.
- Informational flags no longer demand attention in the popover.
unactedFlags is severity ≥ 2 — routine keychain-db opens queue silently in the console's Findings tab instead of occupying the menu bar's needs-a-decision list.
- Dismiss failures are now diagnosable. The peer gate logs every denial
with the kernel-attested peer (pid/uid/role, endpoint) to daemon-err.log, and DaemonClientError conforms to LocalizedError — the menu bar says "daemon refused the change (HTTP 403)…" instead of "The operation could not be completed. (…error 1.)".
- Menu bar harness accordion was dead for single-session groups. The
outer (harness) header of a one-session harness looked clickable but could never collapse; expansion state is now an explicit override that works in both directions for every group.
- Menu bar: the status icon and the hero read the same attention predicate.
- Menu bar: Settings signals when the Endpoint Security helper needs to be re-granted after an update.
- Menu bar: Claude Code hook registration refreshes automatically instead of only at install.
- A resolved incident leaves the active set.
- A Code conversation in the Claude desktop app is rooted at its own
claudeprocess and ends when that process exits; the app (claude-desktop, infra) no longer holds every conversation open until it quits. - Local advisor: finding plans, worktree notes and cleanup plans run under a deadline of at least 5 minutes instead of the triage
timeout_ms; a reasoning model at 60 s timed them out. - Local advisor: cleanup plans get 4,096 tokens (was 2,048); a reasoning model spent the whole 2,048 thinking and returned no plan.
- A daemon writes
guard-cwd-overrides.jsonbeside its own socket. - A second daemon with a socket elsewhere no longer rewrites the default
guard-cwd-overrides.jsonthe hook reads. - Keychain flags from the last hour stamped
untagged:<exe>are relabeled to the agent when the tagger tags that pid. - Flag sentences and attention labels keep agent ids such as
untagged:nodeas written. - Posture and
/doctorreport "File monitoring writer is flooding" only while the spool is still being written (within 2 min); garbage left by a removed writer no longer masks the service state. A spool whose service is not loaded shows "File monitoring is off" with the steps to enable it. - Secret patterns count only where the match starts a token (not after a base64 or base64url character, except a JSON
\n,\tor\rescape): vendor-key shapes inside encrypted reasoning items and other encoded blobs no longer raise secret-in-transcript or proxy findings. - Endpoint detail lists an allowance whose approved parent domain covers the host.
- Endpoint drawer resolves sessions by id, so sessions beyond the newest 1,000 are attributed.
- A second process in the same working directory gets its own session, never another root's.
- A codex rollout session joins the process holding the rollout open: root pid set, that tree's process-tree session merged in.
/doctortrace coverage counts transcript and hook sessions seen since boot, not only those started since boot.- A session upsert stores a new parent and never clears a stored one.
- Every event from a pid already resolved carries its session id, not only the first.
- A hook-stamped session carries its process tree's root pid and absorbs that tree's process-tree session.
- Sessions first seen through a trace event record
transcriptconfidence, nothook. - Codex model calls name the model from
turn_contextwhen the rollout has nothread_settings_appliedline. - A codex rollout resumed from a saved offset keeps its session and model.
- Claude
<synthetic>records no longer count as model calls. - A vendor-prefixed model id is priced by its unprefixed entry.
- Spool tailer: lines under 16 bytes or not starting with
{are rejected
before JSON parsing; a per-tick 4 MiB drain budget skips the rest of a flooded tail in bulk instead of scanning it, and posture/doctor report a flooding writer (unparsed share, MB skipped) instead of parsing garbage.
- Sessions: a tagged child process (a shell under a harness) joins its harness
family's session instead of minting its own — the tagger now reports the family root on every tag, not only in the process listing.
- Sessions: a session is ended only when its root process is confirmed gone,
not when one process-table sample happened to miss it.
- Sessions: orchestrated runs are nested under their orchestrator's session —
the parent lookup walks the OS ancestry past the child's own harness match instead of a chain that stopped at the first match.
- Model pricing: explicit entries for every current Anthropic model (Fable
5.x, Opus 5.5/5/4.8/4.7/4.6/4.5, Sonnet 5/4.6/4.5, Haiku 4.5) at list price; a family prefix only absorbs a date or -latest suffix, so a newer version or a -pro/-mini variant is never billed at another model's price — it is unpriced until an entry exists.
- Sensitive-path classifier: globs with a directory component
(~/.kube/config, ~/.claude/settings.json, the merged guard-rule paths) match the full path only; previously the file name alone matched, so any config, settings.json, hosts.yml, credentials, or shell rc file anywhere classified as sensitive and seeded sensitive-read-then-connect.
- Flag evidence
readitems carryrule(which classifier rule or glob
matched); ES file events from the daemon's own pid are dropped at ingest.
- Event retention: every event kind has its own row budget; the shared
10,000-row cap over all non-trace kinds let a file-open burst evict hook-activity, connection, and transcript-hit rows, which made the posture hook item and the acceptance gate report a firing hook as silent.
- ES grant flow: the Full Disk Access entry is the helper binary
com.cavi-ai.secure-agent-esd, not eslogger — the setup card now names it exactly, and the retry-interval copy matches the real 60s backoff.
- ES service probe parses the FIRST top-level
state =fromlaunchctl print
(nested sections repeat the key and previously overwrote the real service state); the last-exit-code annotation is kept.
- Posture emits at most one item per collector: the spool-based ES probe
supersedes the tailer heartbeat when it reports a failure, so a crash-looping root service no longer double-counts under the eslogger id.
- Session repo identity walks up to the enclosing git root (workspaces are
often subdirectories of a checkout), reports empty when no repo encloses the workspace instead of inventing one from the basename, and the memoized resolution expires after 10 minutes instead of living for the process lifetime.
- Codex rollout discovery reads
CODEX_HOMEoff live codex processes (same
mechanism as ps eww), so orchestrators that relocate the rollout store no longer blind codex tracing until a daemon restart.
- opencode model attribution reads the current schema (
message.data.modelID
top-level) with the older nested model.modelID as fallback — model calls carry their model id again instead of landing unpriced with an empty model.
- Acceptance gate: the ES service check no longer parses JSON through
quote-broken shell interpolation (every probe read as absent), treats not-loaded as a failure when spool-based, asserts guard hook ACTIVITY (events in the last hour) instead of settings.json registration, and scopes the repo-coverage and turn-ratio assertions to the current daemon's boot window — rows and transcript records from before boot cannot be fixed retroactively and previously made the ratio assertions measure old binaries.
- Acceptance gate: time-window queries wrap stored RFC3339 timestamps in
datetime() before comparing — the raw string compare against sqlite's space-separated format made every row from the same day read as "in the last hour", which hid session floods and inflated hook-activity counts.
- Transcript tailer no longer skips new content: a boot re-seed only applies
to files the tailer was already following (tracked files keep their saved offsets), and a transcript discovered for the first time under an already- tracked target is read from the start instead of from its current end — session one in a workspace and lines written while the daemon was down are ingested again.
- Daemon startup repairs rows written by older resolvers: sessions whose repo
carries the old basename heuristic's signature are re-resolved against the enclosing git root, and tool-call rows stranded at "running" past ten minutes (lost completion lines) are closed as error.
- Process-discovered sessions stamp
started_atwith the process's real
start time instead of the discovery time — a daemon restart no longer makes every live agent look like a session created in the last hour.
- Guard hook: read-only socket reads against the agent daemon are allow-listed
by RESOLVED target (a $SOCK variable or http://unix/<path> URL now matches the socket path, not the literal token), covering GETs to /status, /resources, /healthz and /posture; any write verb or /guard path against the socket is still denied, and loopback /guard requests are denied regardless.
- Privileged ES collector exits 0 on failures launchd cannot fix (not root,
binary-integrity failure, eslogger missing) so the service stops crash-looping; genuine startup failures still exit non-zero.
- ES collector integrity check trims the recorded hash — the installer wrote
it via awk '{print $1}' > file, leaving a trailing newline that made every start read as tampered; the installer now writes it without one.
- List endpoints (
/events,/flags,/incidents,/audit,/stats/rollup) returnednullinstead of[]when empty — crashed strict clients (process transcript sheet). - Daemon shutdown during instance overlap could unlink the successor's live unix socket (running but unreachable).
- Malformed-overlay log storm. A bad
config.yamllogged a WARNING on
every load — and the hot-reload watcher loads every 2s, producing ~1,600 lines/hour (cannot unmarshal !!seq into config.rawConfig). The loader is now silent; boot-time Load logs the warning exactly once, and the watcher keeps its own once-per-state line. Regression test pins the contract.
- Collector version was sticky. The first version a node ever reported
was frozen in the rollup forever; upgrades were invisible. Version now tracks the newest report.
- Uninspected-egress warning was a dead end. Clicking it scrolled to a
panel that showed the same number and nothing else. There is now a real drill-down (GET /egress/uninspected): every endpoint with agent, counts, last-seen, the advisor's verdict, and a one-click Allow.
- Dashboard assets send `Cache-Control: no-cache` (both the unix-socket
and proxy-port handlers) so an upgrade can never pair stale cached assets with a new daemon.
- "Re-run the advisor" acknowledged the flag. The old flow marked the
flag acted-upon after an *informational* action, closing the loop the operator hadn't closed; only true dispositions acknowledge now.
- Endpoint Security collector: a spool line is written before the read buffer compacts.
- Posture: the file-monitoring status reads the spool file's age, not only its presence.
- Trace: call ids survive a daemon restart.
- Trace: turn lines over 4 KB are read in full.
- Installer: steps run in dependency order.
- Sessions: a tool call is stored as one row regardless of retries.
- Sessions: a session join keeps the originating harness.
- Config: a corrupted overlay file no longer crashes the loader.
- `secure-agent fleet` help text claimed remote node telemetry; it shows
this node's fleet identity (remote rollups live at the collector).
- Verified by DOM checks on a 300-event burst (hidden-tab render counts, an open rail group, a focused button, a mid-burst Dismiss click) and on allow success and failure.
- Worktree removal:
git worktree removeruns under a 10-minute deadline instead of the 10-second read limit that killed it mid-delete on large trees (200,000 files take 16 s), and finishes when the request is canceled. - A git failure during removal says whether the worktree is still on disk and registered.
- Packaging: the release build always rebuilds the menu bar binary instead of reusing a stale one.
Security
/worktrees*routes are NoAgent: agent processes cannot read or change the machine's worktrees.- The console token no longer reaches the owner-level
DELETE /guard/rules. - Privileged ES collector: the integrity hash moved from the console-user-owned
spool directory to root-owned /Library/Application Support/secure-agent, the binary self-check now refuses non-root-owned executables in addition to writable ones, and the expected pre-grant eslogger permission failure retries inside the process (60s backoff) instead of respawn-churning through launchd.
[v1.1.0] — 2026-09-11
Released: https://github.com/cavi-ai/secure-agent/releases/tag/v1.1.0 Signed with an Apple Development certificate (stable TCC grants across updates); notarization is the follow-up for public distribution.
A substantial feature + hardening release one day after v1.0.0 — in retrospect, 1.0.0 was effectively the last release candidate; everything below is the delta that earns the "stable" label.
Added
- Actionable criticals — every flag is a decision point: click it for the
evidence chain, the advisor verdict, and one-click dispositions (allow host / dismiss this flag class / kill agent / open incident report).
- Little-Snitch-style dispositions — allowlist a host for an agent, mute
a rule+host pair, or allow one exact file (per-path guard exceptions, exact-match + descendants, fully revocable from Settings → Decisions).
- Agent session trees — the popover groups harness → sessions →
subagents with per-harness brand icons, family memory, and last-activity staleness; collapsed by default, expandable per level.
- Process transcripts — click any process for its live event transcript
(reads / writes / spawns / connections with per-line timestamps) and kill with confirmation.
- Privileged ES collector — file telemetry (eslogger) via a root
LaunchDaemon running the daemon in --es-collector mode writing to a spool; one-click install from onboarding step 3 or Settings → Telemetry.
- Popover quit button — power icon + ⌘Q in the footer (joins the
right-click menu entry).
- Advisor config hot-reload — the daemon watches config.yaml and swaps
the advisor stack live; enabling/disabling/switching the model takes effect within seconds, no daemon restart required. The Settings advisor tab restores the persisted mode/endpoint/model on open (it previously reset to "managed local model" every launch, making the config look unpersisted).
Fixed
- "Disconnected" flapping on a healthy daemon —
/statuslast-seen join
was a MAX(ts) GROUP BY pid SQL scan (2–11s at ~400 pids, past the 3s socket timeout). Replaced with an O(1) in-memory last-seen map; socket timeout relaxed to 10s; two-strike rule before declaring disconnection.
- SSE reconnect crash-loop — Go chunk-encodes the event stream over unix
sockets; the client's SSE parser never dechunked, corrupting every frame into an eternal reconnect. Added an incremental chunked-body decoder.
- Pause semantics — resume re-seeds the notification baseline (no banner
storm of accumulated flags); guard consent prompts still flow while paused (agents no longer hang silently); paused state is now visible in the hero, status icon, and copy.
- Update installs no longer freeze the UI — hdiutil/ditto/git run
off-main with concurrent pipe draining (was main-thread + pipe-deadlock risk); DMG downloads stream to disk while hashing instead of buffering whole in RAM; relaunch after terminate (socket-bind race removed); version comparison zero-pads segments ("v1.0" == "v1.0.0").
- Console token off the wire — dashboard handoff moved from
?ct=query
(logged in browser history/server logs) to a #ct= fragment (never sent).
- Open at Login is now a two-way toggle (was enable-only forever).
- Guard prompt copy — disconnected hero no longer says "monitoring
paused" (collided with the real Pause feature).
- Supervisor abandons deterministic failures immediately — eslogger's
NOT_PRIVILEGED used to retry 14× over 3 minutes; permanent errors now abandon on the first attempt with the actionable stderr in the health record. Abandoned collectors surface in the popover with a Retry affordance.
- Silent failure paths made visible — firewall promote, guard-rule
revoke, and guard-decision resolution failures surface in the popover error banner instead of being swallowed.
- "Open console" is disabled with a reason when the daemon/proxy is
down (was a silent no-op).
Changed
- Settings reorganized by function — Protection (guard modes +
firewall), Providers, Telemetry (ES collector + collector health), Decisions (per-path allows ledger), App, Advisor, Updates.
- Provider support out of the box — 12 harnesses matched by default
(claude, cursor, codex, opencode, antigravity, windsurf, aider, gemini, codeium, copilot) plus local model infrastructure (ollama, lm-studio, tracked but their loopback traffic never flagged), each with vendor allowlists. New Providers tab toggles monitoring per harness (disabled_agents in config.yaml).
- Popover reads in priority order — needs-a-decision (incidents,
criticals) → what's running (agent sessions) → what's enforcing (firewall, guard).
- Incident sheet redesigned — human summary + structured evidence +
dispositions first; the raw markdown remediation report demoted behind a disclosure with one-click copy.
- Agent identity — brand-colored drawn glyphs for claude / cursor /
codex / opencode (and a hashed-hue monogram for unknown harnesses).
Security
- TCC grant stability — the app must be signed with a stable identity
(Developer ID); ad-hoc signing changes the designated requirement's cdhash every build, silently invalidating every Full Disk Access grant on each update. Noted for the release pipeline.
[v1.0.0] — 2026-09-10
The first stable release. rc.3 was folded into 1.0 rather than published separately — everything below ships in v1.0.0:
- Console UI overhaul — evidence chains, liveness, session drill-down,
activity trends, posture banner, menubar redesign.
- Security hardening — inline-handler XSS closed and structurally
eliminated, harness self-protection, Grep/Glob directory-scan gating, Linux build enforced in CI.
- The local advisor — opt-in triage and incident narratives from a
locally served model (loopback-only, advisory-only), with managed-mode provisioning, existing-server discovery, triage backfill, mute dispositions, and injection second opinions.
- Learning loop — allowlist suggestions with advisor assessments,
weekly digest.
- Self-updating — verified stable channel + nightly channel.
- Daemon hardening —
main()decomposition into tested wiring,
both CI flakes root-caused and fixed.
Local advisor (opt-in)
- Local triage advisor. Flags and incidents are offered asynchronously
to a locally served model (MLX or any OpenAI-compatible loopback endpoint) and the verdicts surface as advisory annotations: an advisor: benign|suspicious|malicious chip on flag cards, an "advisor: N of M look benign" line in the posture banner and menubar hero, and a plain-English narrative paragraph on incident cards. Loopback-only is enforced in config validation and again at client construction; verdicts can never flip enforcement; a down/slow model fails silent behind a circuit breaker. See docs/ADVISOR_THREAT_MODEL.md.
- Advisor onboarding step. The setup wizard detects a model server on
the loopback endpoint and offers one-click enable/disable (atomic config.yaml flip, restart note included); /status carries advisor_enabled so UIs can distinguish "no verdicts" from "advisor off". The YAML flip helpers are unit-tested (append-when-absent, flip-only-enabled, block scoping, missing file).
- Guard coverage expansion:
Grep/Globscans rooted at protected
directories (~/.ssh, ~/.aws, ~/Library/Keychains, …) are now gated by the governing rule's mode (monitor/prompt/deny); the harness-config rule covers harness settings & hook scripts (reads mode-governed, writes always denied — the self-removal class); grep/rg are classed as readers so grep '' credentials is the same deny as cat.
- DOM-level console test suite (24 assertions in CI) and an SSE
subscription race fix (subscribe-before-greeting).
- Allowlist suggestions. Recurring uninspected egress endpoints
(≥3 sightings, counted per agent+host in the correlator) now surface in the firewall panel with a one-click "Allow for \<agent\>" — persisted to allowlist-overrides.json (0600, atomic), consulted by the vendor-host check immediately, purged from the blind-spot count, and audited. Hosts are validated as bare hostnames so the allowlist can't be widened by smuggled structure; the endpoint is mutation-gated like firewall promotions.
- Settings window. A real tabbed Settings surface (⌘, from the
menu bar, or the popover gear): General (login item, CLI, routing, uninstall), Guard (the full monitor/prompt/deny rule editor), Firewall (per-rule monitor/block with live counters), Advisor (server detection + enable toggle).
- Activity trends. The store keeps pre-aggregated hourly rollups
(O(buckets) reads, 30-day retention) served at /stats/rollup; the console gains an Activity strip chart (24h/7d, events with rose flag markers), and the advisor's triage prompt now carries week-over-week trend context (rule frequency, host novelty) so "benign vs unusual" is judged against this machine's own history.
- Posture language fix. Dead collectors read as operator language
("File monitoring is off — usually missing Full Disk Access…") instead of process jargon ("Monitor eslogger is abandoned"), and the attention summary pluralizes properly.
- Advisor managed mode.
advisor.managed: truemakes the daemon
download, spawn, and supervise the model server itself (supervised like every collector, killed with the app). Path B: /advisor/discover lists loopback OpenAI-compatible servers you already run (Ollama/MLX/ llama.cpp) plus the curated managed list; the Settings Advisor pane renders both paths as radio + dropdowns — nobody types an endpoint. Reasoning models handled: thinking disabled per-request, think blocks stripped.
- Injection second opinion. Prompt-injection detections now carry a
bounded, secret-scrubbed snippet of what matched (in the event detail → flag evidence), and proxy-prompt-injection flags get a dedicated second-opinion prompt tuned for the scanner's classic false positive (documentation ABOUT injection vs an attack).
- Agent count precision.
active_agentscounts tree roots, not
processes (266 helpers ≠ 266 agents); new tracked_processes field carries the full count; the menubar lists roots only.
- Triage completion loop. The advisor backfills verdicts for
severity-3 flags that fired while it was off (7-day window, bounded); advisor-benign flag rows offer a reversible Mute rule+host disposition (persisted to muted.json, suppressed at the correlator with a muted_flags counter, unmute from the flags panel); open incidents older than 72h surface in posture as aging items.
- Informed suggestions + weekly digest. Suggestion-threshold hosts
get an advisor legitimacy pre-assessment shown inline on the Allow button; Monday 09:00 the menubar sends a one-line weekly digest from rollups ("3 flags · 1 blocked leak · 2 approvals · 0 open incidents").
- In-app updates. Settings → Updates: a Stable channel (latest
GitHub release DMG, SHA-256 verified against the release's checksums.txt before mounting — mismatch or missing checksum is a loud refusal) and a Nightly channel (ff-only build from origin/main of a local checkout via packaging/update_nightly.sh). make_dmg.sh now emits dist/checksums.txt on every build.
- v1.0 polish. The collector-down posture item deep-links one click
into System Settings → Full Disk Access (POST /ui/open-fda); the onboarding wizard deep-links into the Settings window ("More options in Settings…") so Settings is the single owner of ongoing config.
Security fixes
- Console inline-handler XSS closed. Every inline
onclickhandler
interpolated values into single-quoted JS strings, but the HTML escaper never touched ' or \ — a value like ');alert(1);// broke out and executed as script in the operator's console, which holds the console token in memory (telemetry reads and guard self-approval were the escalation). Reachable by a monitored agent via session_id/event detail through activity.jsonl. New escapeJS with correct layering (escapeHTML(escapeJS(v))) applied to all six interpolation sites; removeSource dropped encodeURIComponent (never escaped quotes either). Regression tests count unescaped quotes in the emitted attribute. Follow-up: all inline handlers were then replaced with data-* attributes + a single delegated click listener — the JS-string interpolation context no longer exists at all (grep onclick: 0 hits).
Console (web UI)
- Evidence-chain hero view. Flags are now expandable cards that tell
their causal story as a connected node chain (sensitive read → egress destination → verdict), parsed from the correlator's real evidence sentences with a raw-node fallback that never hides audit data. The most recent flag auto-expands; raw evidence remains visible below the chain.
- Liveness pass. Masthead event-rate sparkline (rolling 60s, SSE-fed
with poll-fallback backfill), fresh timeline rows animate only for events unseen in the previous render (no polling flicker), KPI numbers tween with a directional flash, a proxy verdict pulses the firewall panel and flashes the rule row that intercepted, and the posture banner cross-fades between states. All motion honors prefers-reduced-motion.
- Session drill-down. "View session in timeline" on any flag card
filters the event timeline to that harness session (chip with match count + one-click clear).
- CSS regressions fixed. Undefined
--rose/--text/--mutedtokens
(the critical posture dot was invisible), a duplicate @keyframes pulse that broke the status-dot glow, and a .status-chip class collision that shrank the masthead chip. New check_console_css.sh guard (CI + make test) fails on undefined custom properties and duplicate keyframes.
- Real version badge.
/statuscarries the link-time build version
and the console renders it — the badge can't go stale. Timeline timestamps are now deterministic zero-padded HH:MM:SS (fmtTime), immune to locale quirks.
Menu bar app
- Popover redesign. A 3-state hero (Protected / Attention / Action
needed, plus Disconnected) now answers the three glance questions, mirroring the console's posture banner. The firewall section condenses to rules that have seen suspicious traffic; the guard policy editor moved behind a collapsed "Manage rules…" disclosure. A new severity-3 flag briefly pulses the status-item title.
Testing & engineering
- `main.go` decomposed (was the codebase's biggest untested hotspot,
degree 172) into wire.go: setupFirewall, setupProxy, guardBrokerMS, transcriptTailTargets, startDrainLoop, buildStatusFn, watchParentExit — each with direct unit tests.
- Console JS test harness. DOM-free logic extracted to
lib.js
(escapeHTML, fmtTime, sparkline math, markdown, evidence-chain parser) and covered by a zero-dependency node --test suite wired into CI and make test. A DOM-level suite (packaging/test/console_dom/) renders the real console in headless Chrome against stubbed telemetry and asserts 24 behaviors: telemetry wiring, evidence chain, liveness classes, session drill-down, and the zero-inline-handlers guarantee.
- Linux CI flake fixed.
TestFullBusCorrelatorStorePipelinenow
drains its consumer goroutine before closing the store on every path (was: sql: database is closed races on slow runners).
- README screenshots regenerated — they now show the posture banner,
evidence chain, secret sources, policy audit, fleet, and the correct version badge.
[v0.9.0-rc.2] — 2026-09-07
Release candidate 2: the full audit hardening pass (hooks, daemon, menubar, CI/packaging), SSE push for both UIs, the console-auth fix, session evidence chains, and the guard policy editor.
Linux support
- The daemon is now platform-portable (pure Go, no cgo):
/proc-based
process source and socket lister, SO_PEERCRED peer credentials, eslogger gated behind an availability check (Endpoint Security is macOS-only — file telemetry degrades to the transcript scanner on Linux, everything else is identical). New Linux build + vet + test CI job on ubuntu-latest enforces it; darwin/linux factories (NewProcSource, NewSocketLister, NewPeerChecker) keep platform code in build-tagged files.
Security fixes (hooks)
- Shell `-c` bypass closed.
sh/bash/zsh/dash/kshpayloads are now
recursively analyzed (depth-capped). Previously zsh -c "security dump-keychain" bypassed every check, including the keychain total-ban.
- Executor bypasses closed.
xargsis no longer stripped as a wrapper
(echo ~/.zshrc | xargs rm was allowed); find -exec/-delete over protected paths is denied; archivers (tar/zip/ditto/7z) over protected directories are denied as bulk exfiltration.
- Case-folding on APFS. Path classification is case-insensitive
(~/.ZSHRC is ~/.zshrc on a case-insensitive volume).
- Wider credential surface. Added
~/.netrc,~/.gnupg/**,
~/.kube/config, ~/.docker/config.json, ~/.npmrc, ~/.pypirc, ~/.config/gh/hosts.yml, and *.pem/*.p12/*.pfx to the never-print set. .pub public keys are now correctly *allowed* (they are meant to be shared).
- Obfuscated inline writes denied. Interpreter
-ccode combining a file
write with runtime path composition (chr()/base64/env lookups) is denied as interpreter-obfuscated-write.
- chflags fixed both ways. Unlock detection now matches a known flag set
(nouchg/noschg) instead of "starts with no" — so chflags nodump (a hardening flag) is allowed, while chflags -R nouchg ~ is denied.
- No secrets in the audit trail. Denied commands are redacted (passwords,
tokens, bearer strings, PEM headers) before hitting secret-guard.jsonl / activity.jsonl, and both logs are now 0600 in 0700 dirs.
- Corrupt guard config fails closed. A truncated
guard-modes.json/
guard-cwd-overrides.json now denies (and logs loudly) instead of silently reverting every rule to monitor.
- Injection scanner robustness. NFKC normalization, zero-width/format char
stripping, and Cyrillic-homoglyph folding before matching; added forget…/do not follow…/new goal: pattern families; recursion is depth-bounded and a scanner crash now emits {} instead of dying with no JSON.
Security fixes (daemon)
- Guard broker data race fixed (
Resolveiterated a waiter's channel slice
after dropping the lock while Request appended under it). New -race regression test.
- Authorization wired correctly.
authorizenow uses the role methods
(canRead/canDecide/canMutate): tagged agents may read and ask /guard/decision (previously 403, which broke the prompt flow), but can never mutate.
- CA key regeneration actually lands at 0600.
os.WriteFilepreserves an
existing file's mode, so a regenerated key inherited the old world-readable perms; all security-state files now write via temp+fsync+rename (atomic and crash-consistent).
- Salt rotation is loud, never silent. A truncated/unreadable salt file is
an error with operator instructions instead of silently minting a new salt that orphans every registered fingerprint.
- Fingerprint ingest refuses to purge. A run that reads zero fingerprints
while every source failed returns an error instead of an empty set that would silently wipe the registry; oversized lines no longer truncate scans.
- Config overlay errors are visible (log warnings on unreadable/malformed
YAML), and Load validates values that would panic at runtime (net_sample_interval_ms <= 0 panics time.NewTicker).
- Store correctness.
SetIncidentStatususesRowsAffectedinstead of
SELECT changes() on a possibly-different pooled connection (spurious 404s); incident IDs now include the flag ID (same-second same-pid flags no longer overwrite each other's evidence); flags table has a retention cap like the other tables; QueryEvents returns session_id; timestamps are stored UTC-normalized.
- Proxy correctness. Blocked CONNECT request bodies are drained before the
next read (keep-alive tunnels no longer desync); the plain-HTTP path reuses one transport; token comparison is constant-time.
- Resource bounds. Fleet deliveries capped at 64 in flight (dropped and
counted beyond that); the correlator's uninspected-egress set is capped; the tagger prunes dead pids (also fixes recycled-pid tag inheritance); transcript scanner prunes rotated files and caps line length; eslogger zombies reaped; reverse DNS is async with a deadline instead of blocking the sampler; the event bus refuses subscriptions after close; API POST bodies are size-limited; /guard/resolve validates method and id; lsof failures are logged.
- Collector read auth.
-read-token(or
SECURE_AGENT_COLLECTOR_READ_TOKEN) gates /fleet, /nodes/*, and /; binding a non-loopback address without one logs a loud warning.
- `agent-env.sh` values are shell-quoted — paths with spaces (e.g.
Application Support) no longer break the snippet, and metacharacters can't inject into the sourcing shell.
Menu bar app
- Transport hardening. The unix-socket client now has connect/send/recv
timeouts (a wedged daemon no longer hangs the app or leaks blocked threads), parses the HTTP status line (non-2xx is an error, not JSON), and handles chunked transfer-encoding. Query parameters from daemon-supplied values are percent-encoded.
- No more fail-open guard UI.
/guard/pendingand/guard/rulesdecode
strictly; a malformed response surfaces an error instead of silently showing "nothing to approve".
- No first-launch notification storm. The first fetch seeds the
notification baseline; only genuinely new flags alert. Notification bodies no longer leak paths/hostnames to the lock screen.
- Honest actions. Kill asks for confirmation and reports refusal; a guard
decision that fails to reach the daemon tells you it wasn't recorded; disconnect clears all daemon-derived state instead of showing stale data; a daemon that crashed past the restart limit gets an in-app "Restart" button instead of a silent permanent "Disconnected".
- Incident remediation in the popover. Tapping an incident opens the
daemon-generated rotation checklist (previously only in the web console).
- Fetch loop is serialized (no overlapping out-of-order polls), the unused
1 Hz /events fetch is gone, hook detection requires all three harnesses (allSatisfy), the hook self-test no longer pipe-deadlocks, and guard-modes.json writes are atomic.
CI / packaging
- GitHub Actions are SHA-pinned with
permissions: contents: read. e2e_smoke.shkills all background processes on any exit (failures used to
orphan the daemon/collector with their state dir deleted underneath).
make_dmg.shfails loudly when notarization was requested but fails.make_app.shsanitizes git-derived strings before plist interpolation.uninstall.shonly removes hooks it actually installed and lists leftover
state instead of claiming "completely uninstalled".
run_e2e_verbose.pyanchors at the repo root and always reaps the daemon.
Menu bar: SSE push replaces 1 Hz polling
- The menu bar app now consumes the daemon's
/events/streamSSE feed:
guard prompts arrive at push latency instead of up-to-1s poll latency, and the idle poll drops to a 30s status cadence. Falls back to the 1 Hz poll when the endpoint is unavailable (503 from an older daemon) and reconnects with capped exponential backoff + jitter on transport failure. The stream's 15s heartbeat doubles as the liveness watchdog (45s idle = dead connection, reconnect).
- Daemon publishes two new bus event kinds for the guard lifecycle:
guard-prompt (a prompt was enqueued) and guard-resolved (a prompt was resolved), letting every connected UI refetch immediately.
- Enforced by
e2e_smoke.sh: the stream must carry both guard lifecycle
events during the guard round-trip.
Web console: actually works now, and live
- Fixed a broken headline feature: the dashboard at
http://localhost:8443/dashboard/ loaded, but every telemetry fetch hit the proxy listener's token challenge (407) — the console rendered a permanent offline banner with no data. The proxy port now serves the console's API endpoints behind a new per-install console token (~/.config/secure-agent/console-token, 0600) — deliberately distinct from the proxy token, which agents carry in their environment and could otherwise trade for telemetry reads and guard self-approval. /guard/decision stays off the HTTP listener entirely (peer-attested unix socket only).
- The console now consumes
/events/stream(SSE) with a 2s polling fallback
and a 30s slow refresh for status — guard prompts and flags appear at push latency.
- The menubar's Open console passes the console token automatically; the
page strips it from the address bar after lifting it into memory.
- Enforced by
e2e_smoke.sh: 403 without a token, 403 with the *proxy* token,
200 with the console token; plus TestConsoleAPIGate in Go.
Features & follow-ups
- Session evidence chains survive the store. The
flagstable now
persists session_id (with an in-place migration for existing databases — no more duplicate column log noise on fresh starts), /flags returns it, and the menubar shows the session prefix on flag rows.
- Guard policy editor in the popover. Per-rule
monitor/prompt/
deny toggles write guard-modes.json atomically — Directory Guard policy is now editable without touching JSON by hand. A corrupt modes file is surfaced in the UI (the hook fails closed on it) instead of looking like "everything is monitor".
- docs/GUARD_THREAT_MODEL.md — the Directory Guard's closed bypass
classes, known limits (symlinks, TOCTOU, static inline-code analysis), and the fail-open/fail-closed table, linked from the README.
go mod tidy: dependency graph normalized (direct deps were all marked
indirect).
- Swift model identities no longer collide (
EventModel.idwas
kind-pid-second; AgentSummaryModel.id was bare pid).
AppStateis testable: the daemon client is injected behind a protocol and
notifications go through a closure — new unit tests cover the notification baseline storm-guard, dedupe, low-severity filtering, disconnect state clearing, decode-vs-transport distinction, guard decode surfacing, and pause semantics.
[v0.9.0-rc.1] — 2026-09-02
First release candidate. Everything below has CI enforcement: Go (vet, test, -race), Swift package tests, 138 Python hook cases, an end-to-end smoke test (flag → incident → guard round-trip through a live daemon), and secret scanning of the repository itself.
RC disclaimer. The DMG is ad-hoc signed: recipients must right-click → Open the first time. Notarized builds are planned for v1.0.
Security hardening
- Kernel-attested control socket. Every unix-socket connection is
identified with LOCAL_PEEREPID / LOCAL_PEERCRED; a role gate restricts reads (owner + agents), guard decisions (agents), and mutations (owner-level). Nothing can spoof the menubar or a hook.
- `/kill` allowlist. The daemon only kills PIDs it currently recognizes as
agent processes — the control socket is no longer an arbitrary-process killer, and killing the daemon through its own API is impossible.
- Guard broker bounds. The pending-prompt queue caps at 32 with an explicit
deny("queue-full") on overflow; identical in-flight requests share one waiter; prompts resolve oldest-first.
- Informed consent. Guard prompts disclose what "Allow Always" approves
(scope_text): every path under the rule for that agent, not just the file.
- Filesystem hygiene. State directories
0700; database, WAL, and JSONL
logs 0600.
- Web console hardening. CSP,
X-Frame-Options: DENY,nosniff, and
Referrer-Policy on both the proxy-port and unix-socket dashboard routes.
- Honest telemetry.
/fleetreports a real ldflags-stamped version and a
stable node_id; the fabricated tailnet_ready field and the dead "synchronous blocker" collector stub were removed.
- Race fix.
ProxyServer.portdata race (found by the new-raceCI job)
made atomic.
Directory Guard
- Config-driven
monitor/prompt/denymodes across file tools and Bash. - Per-project policies:
directory_guard.cwd_overridespins one repo
subtree to specific rule modes (deny .env in the production repo, monitor everywhere else) — resolution: cwd overlay → global override → default.
- Native menubar prompts (Allow Once / Allow Always / Deny, with **Deny as the
safe default**), per-(agent, rule) caching, and rule revocation.
- Guard's own control plane is protected from agent writes and from direct
socket forgery via network clients.
Fleet oversight
- Signed webhook delivery of flags, incidents, and guard decisions:
X-SecureAgent-Signature: sha256=HMAC(secret, body), 3 retries with backoff (network/5xx/429 only), 0600 delivery log, never blocks the daemon.
- Stable node identity: 128-bit
node_idper install, reported by
/fleet and stamped on every webhook envelope.
- Session identity: hooks stamp
session_id(Claude session env or per-run
UUID) through events → flags → incidents, so evidence chains survive PID reuse.
- CLI parity:
secure-agent guard list|revoke,firewall mode|sources,
events|audit, plus SECURE_AGENT_SOCK for tunneled remote nodes.
Operator UX
- `GET /posture` — the headline: all-clear / attention / critical, a
needs_you count, and severity-ranked items (recent critical flags, pending guard prompts, dead collectors, uninspected egress).
- Incident workflow — forward-only
open → acknowledged → resolvedwith
audited transitions; reports stay immutable evidence.
- `GET /events/stream` — SSE live feed with heartbeat; replaces polling.
- Console — posture banner with drill-down links, incident ack/resolve
buttons, status chips, resolution notes.
- Onboarding hook self-test — one click fires a synthetic tool call through
the installed guard and verifies the round-trip.
- Human-language guard prompts — "claude wants to read a cloud credential
file" with the stakes explained, not a raw path.
New endpoints (unix-socket API)
/posture · /events/stream (SSE) · /incidents/status · /guard/* · /firewall/* · /fleet · /dashboard/ — full reference in docs/API.md.
Compatibility
- Existing
events.dbfiles are migrated in place (new columns are additive;
nothing is rewritten or reinterpreted).
- Everything ships in monitor mode by default: no rule blocks until you
promote it.
Earlier
Development history lives in the git log; this changelog starts at the first tagged release.