From c6987a796284fbfab4a48a73f562ef97d5efe2ea Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Fri, 20 Feb 2026 22:18:53 +0100 Subject: [PATCH] Remove deprecated audit endpoint - Remove audit endpoint routes from 9P filesystem - Delete audit handler functions (readAuditLog, GetAuditLog) - Remove audit package and all audit logging code - Remove audit log viewer from TUI (anvillm) - Remove audit log streaming from web UI (anvilweb) - Update documentation to reference events system instead - All audit functionality replaced by events system --- BEADS.md | 6 +- SECURITY.md | 2 +- cmd/anvillm/main.go | 60 ------------------- cmd/anvilweb/main.go | 60 ------------------- internal/audit/audit.go | 112 ------------------------------------ internal/mailbox/manager.go | 15 +---- internal/p9/server.go | 56 ------------------ 7 files changed, 5 insertions(+), 306 deletions(-) delete mode 100644 internal/audit/audit.go diff --git a/BEADS.md b/BEADS.md index 6ff04b6..876a7a6 100644 --- a/BEADS.md +++ b/BEADS.md @@ -37,7 +37,7 @@ agent/beads/ ├── dependencies-meta # Dependencies with metadata (JSON) ├── dependents-meta # Dependents with metadata (JSON) ├── tree # Dependency tree (JSON) - └── events # Audit trail events (JSON) + └── events # Event history (JSON) ``` ## Control Commands @@ -123,7 +123,7 @@ echo "complete bd-a1b2" | 9p write agent/beads/ctl # Read dependency tree 9p read agent/beads/bd-a1b2/tree -# Read audit trail +# Read event history 9p read agent/beads/bd-a1b2/events # Add dependency (parent blocks child) @@ -208,7 +208,7 @@ anvilmcp exposes beads operations as MCP tools (calls `9p write agent/beads/ctl` - **Crash resilience** — Beads persist in Dolt database - **Resumability** — Agents pick up where others left off - **Dependency tracking** — Automatic blocking relationships -- **Version control** — Full audit trail via Dolt +- **Version control** — Full history via Dolt - **Git portability** — JSONL export syncs via git - **Scriptability** — Standard file operations - **Pure Go** — No Python dependency diff --git a/SECURITY.md b/SECURITY.md index d6cb395..50e1c6d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -77,6 +77,6 @@ Backend sessions run in tmux windows with: Potential security enhancements for consideration: 1. Add authentication to 9P protocol (shared secret, capabilities) 2. Per-session access control lists -3. Audit logging of 9P operations +3. Enhanced event logging of 9P operations 4. Encrypted transport for 9P (though Unix sockets are local-only) 5. Namespace isolation via Linux namespaces or FreeBSD jails diff --git a/cmd/anvillm/main.go b/cmd/anvillm/main.go index 2b35f2e..9dd7d79 100644 --- a/cmd/anvillm/main.go +++ b/cmd/anvillm/main.go @@ -116,9 +116,6 @@ func setupUI() { case 'a': showAliasDialog() return nil - case 'l': - showLogViewer() - return nil case 'd': showDaemonStatus() return nil @@ -585,63 +582,6 @@ func killSelectedSession() { } } -func showLogViewer() { - // Read the centralized audit log - path := "audit" - logData, err := readLogFile(path) - if err != nil { - updateStatus(fmt.Sprintf("[red]Error reading audit log: %v", err)) - return - } - - logText := string(logData) - if logText == "" { - logText = "[gray]No audit log entries yet[-]" - } - - textView := tview.NewTextView(). - SetDynamicColors(true). - SetText(logText). - SetScrollable(true). - SetWordWrap(true) - - textView.SetBorder(true). - SetTitle(" Audit Log "). - SetTitleAlign(tview.AlignLeft) - - // Scroll to bottom - textView.ScrollToEnd() - - textView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - switch event.Rune() { - case 'q': - pages.RemovePage("log") - return nil - case 'r': - // Refresh log - logData, err := readLogFile(path) - if err != nil { - updateStatus(fmt.Sprintf("[red]Error refreshing log: %v", err)) - return nil - } - logText := string(logData) - if logText == "" { - logText = "[gray]No audit log entries yet[-]" - } - textView.SetText(logText) - textView.ScrollToEnd() - return nil - } - if event.Key() == tcell.KeyEscape { - pages.RemovePage("log") - return nil - } - return event - }) - - pages.AddPage("log", createModalDynamic(textView, 8, 25), true, true) -} - func showDaemonStatus() { // TODO: Implement daemon status view updateStatus("[yellow]Daemon status view not yet implemented") diff --git a/cmd/anvilweb/main.go b/cmd/anvilweb/main.go index ef696d1..2dc2fe0 100644 --- a/cmd/anvilweb/main.go +++ b/cmd/anvilweb/main.go @@ -51,7 +51,6 @@ func main() { http.Handle("/", http.FileServer(http.FS(staticFS))) http.HandleFunc("/api/sessions", handleSessions) http.HandleFunc("/api/session/", handleSession) - http.HandleFunc("/api/log", handleLogStream) http.HandleFunc("/api/inbox", handleInbox) http.HandleFunc("/api/inbox/count", handleInboxCount) http.HandleFunc("/api/inbox/reply", handleInboxReply) @@ -501,65 +500,6 @@ func updateSessionFile(w http.ResponseWriter, r *http.Request, id, file string) w.WriteHeader(http.StatusOK) } -func handleLogStream(w http.ResponseWriter, r *http.Request) { - // Stream the centralized audit log - // Set headers for SSE - w.Header().Set("Content-Type", "text/event-stream") - w.Header().Set("Cache-Control", "no-cache") - w.Header().Set("Connection", "keep-alive") - - fs, err := connect() - if err != nil { - http.Error(w, err.Error(), http.StatusServiceUnavailable) - return - } - defer fs.Close() - - // Open audit log file for streaming - fid, err := fs.Open("audit", plan9.OREAD) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - defer fid.Close() - - // Flush immediately to establish connection - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } - - // Stream log data - buf := make([]byte, 4096) - for { - n, err := fid.Read(buf) - if n > 0 { - // Escape data for SSE format - each line must be prefixed with "data: " - data := string(buf[:n]) - lines := strings.Split(data, "\n") - for _, line := range lines { - if line != "" || len(lines) > 1 { - fmt.Fprintf(w, "data: %s\n", line) - } - } - fmt.Fprintf(w, "\n") - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } - } - if err != nil { - // Connection closed or error - break - } - - // Check if client disconnected - select { - case <-r.Context().Done(): - return - default: - } - } -} - type Message struct { ID string `json:"id"` From string `json:"from"` diff --git a/internal/audit/audit.go b/internal/audit/audit.go deleted file mode 100644 index ef043b0..0000000 --- a/internal/audit/audit.go +++ /dev/null @@ -1,112 +0,0 @@ -package audit - -import ( - "fmt" - "strings" - "sync" - "time" -) - -const ( - maxSize = 64 * 1024 * 1024 // 64MB - trimPercent = 25 // Remove 25% when limit reached -) - -// Log maintains a centralized audit log of all messages -type Log struct { - entries []string - totalBytes int - mu sync.RWMutex - notifyCh chan struct{} -} - -// NewLog creates a new audit log -func NewLog() *Log { - return &Log{ - entries: make([]string, 0, 1000), - notifyCh: make(chan struct{}, 1), - } -} - -// Append adds a new entry to the audit log -// Format: {timestamp} {message-type} {sender}->{recipient} {subject}: {body} -func (l *Log) Append(msgType, sender, recipient, subject, body string) { - l.mu.Lock() - defer l.mu.Unlock() - - // Format timestamp - timestamp := time.Now().Format(time.RFC3339) - - // Escape newlines in subject and body for single-line grep/awk - // But for human readability, we'll use multi-line format with blank line separator - entry := fmt.Sprintf("%s %s %s->%s\nSubject: %s\n%s\n", - timestamp, msgType, sender, recipient, subject, body) - - l.entries = append(l.entries, entry) - l.totalBytes += len(entry) - - // Trim if needed - if l.totalBytes > maxSize { - l.trim() - } - - // Notify waiters - select { - case l.notifyCh <- struct{}{}: - default: - } -} - -// trim removes the oldest 25% of entries -func (l *Log) trim() { - if len(l.entries) == 0 { - return - } - - removeCount := len(l.entries) * trimPercent / 100 - if removeCount == 0 { - removeCount = 1 - } - - // Calculate bytes to remove - removedBytes := 0 - for i := 0; i < removeCount && i < len(l.entries); i++ { - removedBytes += len(l.entries[i]) - } - - // Remove entries - l.entries = l.entries[removeCount:] - l.totalBytes -= removedBytes -} - -// Read returns the entire audit log as a string -func (l *Log) Read() string { - l.mu.RLock() - defer l.mu.RUnlock() - - return strings.Join(l.entries, "\n") -} - -// ReadFrom returns audit log content from a specific byte offset -func (l *Log) ReadFrom(offset int64) (string, bool) { - l.mu.RLock() - defer l.mu.RUnlock() - - fullLog := strings.Join(l.entries, "\n") - if offset >= int64(len(fullLog)) { - return "", false - } - return fullLog[offset:], true -} - -// WaitForData returns a channel that signals when new data is available -func (l *Log) WaitForData() <-chan struct{} { - return l.notifyCh -} - -// Size returns the current size in bytes -func (l *Log) Size() int { - l.mu.RLock() - defer l.mu.RUnlock() - return l.totalBytes -} diff --git a/internal/mailbox/manager.go b/internal/mailbox/manager.go index 6c36766..062325f 100644 --- a/internal/mailbox/manager.go +++ b/internal/mailbox/manager.go @@ -1,7 +1,6 @@ package mailbox import ( - "anvillm/internal/audit" "fmt" "sync" "time" @@ -20,7 +19,6 @@ type Manager struct { completed map[string][]*Message mu sync.RWMutex idCounter uint64 - auditLog *audit.Log sessions SessionGetter onSend func(senderID string, msg *Message) onRecv func(receiverID string, msg *Message) @@ -32,7 +30,6 @@ func NewManager() *Manager { inboxes: make(map[string][]*Message), outboxes: make(map[string][]*Message), completed: make(map[string][]*Message), - auditLog: audit.NewLog(), } // Initialize user mailbox m.inboxes["user"] = []*Message{} @@ -147,13 +144,6 @@ func (m *Manager) DeliverToInbox(sessionID string, msg *Message) error { m.inboxes[sessionID] = append(m.inboxes[sessionID], msg) - // Format sender and receiver with aliases - sender := m.formatParticipant(msg.From) - receiver := m.formatParticipant(msg.To) - - // Audit log the delivery - m.auditLog.Append(string(msg.Type), sender, receiver, msg.Subject, msg.Body) - if m.onRecv != nil { m.onRecv(sessionID, msg) } @@ -307,7 +297,4 @@ func (m *Manager) MoveToCompleted(sessionID string, msg *Message) { m.completed[sessionID] = append(m.completed[sessionID], msg) } -// GetAuditLog returns the audit log -func (m *Manager) GetAuditLog() *audit.Log { - return m.auditLog -} + diff --git a/internal/p9/server.go b/internal/p9/server.go index b92582e..fd6614c 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -68,7 +68,6 @@ const ( qidCtl qidList qidStatus - qidAudit // agent/audit qidEvents // agent/events qidUser // user directory qidUserInbox // user/inbox @@ -287,9 +286,6 @@ func (s *Server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall { case "list": qid = plan9.Qid{Type: QTFile, Path: qidList} newPath = "/list" - case "audit": - qid = plan9.Qid{Type: QTFile, Path: qidAudit} - newPath = "/audit" case "events": qid = plan9.Qid{Type: QTFile, Path: qidEvents} newPath = "/events" @@ -434,9 +430,6 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall) *plan9.Fcall { if f.qid.Type&QTDir != 0 { data = s.readDir(f.path, fc.Offset, fc.Count) - } else if f.path == "/audit" { - // Streaming audit log (like tail -f) - data = s.readAuditLog(fc.Offset, fc.Count) } else { content := s.readFile(f.path) if fc.Offset < uint64(len(content)) { @@ -865,10 +858,6 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte { Qid: plan9.Qid{Type: QTFile, Path: qidStatus}, Mode: 0444, Name: "status", Uid: "q", Gid: "q", Muid: "q", }) - dirs = append(dirs, plan9.Dir{ - Qid: plan9.Qid{Type: QTFile, Path: qidAudit}, Mode: 0444, Name: "audit", - Uid: "q", Gid: "q", Muid: "q", - }) dirs = append(dirs, plan9.Dir{ Qid: plan9.Qid{Type: QTFile, Path: qidEvents}, Mode: 0644, Name: "events", Uid: "q", Gid: "q", Muid: "q", @@ -1047,39 +1036,6 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte { return data[offset:end] } -func (s *Server) readAuditLog(offset uint64, count uint32) []byte { - mailMgr := s.mgr.GetMailManager() - if mailMgr == nil { - return nil - } - - auditLog := mailMgr.GetAuditLog() - if auditLog == nil { - return nil - } - - // Try to read from current offset - logData, hasMore := auditLog.ReadFrom(int64(offset)) - if hasMore && len(logData) > 0 { - // Return available data - end := min(int(count), len(logData)) - return []byte(logData[:end]) - } - - // No data available at this offset, wait for new data - waitCh := auditLog.WaitForData() - <-waitCh - - // Check again after new data arrived - logData, hasMore = auditLog.ReadFrom(int64(offset)) - if hasMore && len(logData) > 0 { - end := min(int(count), len(logData)) - return []byte(logData[:end]) - } - - return nil -} - func (s *Server) readFile(path string) string { // Handle beads paths if strings.HasPrefix(path, "/beads/") { @@ -1117,18 +1073,6 @@ func (s *Server) readFile(path string) string { return string(s.events.Read()) } - if path == "/audit" { - mailMgr := s.mgr.GetMailManager() - if mailMgr == nil { - return "" - } - auditLog := mailMgr.GetAuditLog() - if auditLog == nil { - return "" - } - return auditLog.Read() - } - if path == "/status" { var lines []string mailMgr := s.mgr.GetMailManager()