fix: 6 bugs (timeline path value, seer SSE cursor, erasure checksum, subtle compare, build status code, dead WorkerName branch)
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
drjones
2026-07-05 07:06:59 +00:00
parent 3678b199d0
commit f923b1ef54
6 changed files with 61 additions and 16 deletions

View File

@@ -70,7 +70,7 @@ func (h *ForgeHandler) TriggerBuild(w http.ResponseWriter, r *http.Request) {
builds, err := h.Pipeline.BuildAll(req.Public) builds, err := h.Pipeline.BuildAll(req.Public)
if err != nil { if err != nil {
auth.JSON(w, http.StatusOK, map[string]any{ auth.JSON(w, http.StatusInternalServerError, map[string]any{
"ok": false, "ok": false,
"message": err.Error(), "message": err.Error(),
}) })

View File

@@ -36,10 +36,15 @@ func (h *SeerHandler) Stream(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive") w.Header().Set("Connection", "keep-alive")
// Send last 20 events as backlog, track the most recent timestamp as cursor.
var cursor string
events, _ := h.Store.ListSeerEvents(20) events, _ := h.Store.ListSeerEvents(20)
for _, ev := range events { for _, ev := range events {
payload, _ := json.Marshal(ev) payload, _ := json.Marshal(ev)
fmt.Fprintf(w, "data: %s\n\n", payload) fmt.Fprintf(w, "data: %s\n\n", payload)
if ev.CreatedAt > cursor {
cursor = ev.CreatedAt
}
} }
flusher.Flush() flusher.Flush()
@@ -51,13 +56,18 @@ func (h *SeerHandler) Stream(w http.ResponseWriter, r *http.Request) {
case <-r.Context().Done(): case <-r.Context().Done():
return return
case <-ticker.C: case <-ticker.C:
events, err := h.Store.ListSeerEvents(5) newEvents, err := h.Store.ListSeerEventsSince(cursor, 50)
if err != nil { if err != nil {
continue continue
} }
for _, ev := range events { for _, ev := range newEvents {
payload, _ := json.Marshal(ev) payload, _ := json.Marshal(ev)
fmt.Fprintf(w, "data: %s\n\n", payload) fmt.Fprintf(w, "data: %s\n\n", payload)
if ev.CreatedAt > cursor {
cursor = ev.CreatedAt
}
}
if len(newEvents) > 0 {
flusher.Flush() flusher.Flush()
} }
} }

View File

@@ -98,7 +98,7 @@ func (h *SeerHub) fetchEvents(ctx context.Context, since string) ([]SeerEvent, e
// TimelineHandler serves LOTL timeline merged with court events for a host. // TimelineHandler serves LOTL timeline merged with court events for a host.
func TimelineHandler(c *Court) http.HandlerFunc { func TimelineHandler(c *Court) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
hostID := r.PathValue("host_id") hostID := r.PathValue("id")
if hostID == "" { if hostID == "" {
http.Error(w, "host_id required", http.StatusBadRequest) http.Error(w, "host_id required", http.StatusBadRequest)
return return

View File

@@ -176,12 +176,12 @@ func (s *Service) Reconstruct(ctx context.Context, bundleID string, indices []in
for i := 0; i < bundle.DataShards; i++ { for i := 0; i < bundle.DataShards; i++ {
out = append(out, shards[i]...) out = append(out, shards[i]...)
} }
// Trim padding — find actual length via checksum match trimmed := trimNullPadding(out)
sum := sha256.Sum256(out) sum := sha256.Sum256(trimmed)
if hex.EncodeToString(sum[:]) != bundle.Checksum { if hex.EncodeToString(sum[:]) != bundle.Checksum {
// Return best-effort; caller validates return nil, fmt.Errorf("reconstructed data checksum mismatch: bundle may be corrupt")
} }
return trimNullPadding(out), nil return trimmed, nil
} }
func trimNullPadding(b []byte) []byte { func trimNullPadding(b []byte) []byte {

View File

@@ -51,6 +51,47 @@ type SeerEvent struct {
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at"`
} }
// ListSeerEventsSince returns events newer than the given SQLite timestamp string (exclusive).
func (s *Store) ListSeerEventsSince(since string, limit int) ([]SeerEvent, error) {
if limit <= 0 {
limit = 50
}
var rows interface {
Close() error
Next() bool
Scan(dest ...any) error
Err() error
}
var err error
if since == "" {
rows, err = s.db.Query(`
SELECT id, host_id, event_type, payload_json, created_at
FROM seer_events ORDER BY created_at ASC LIMIT ?`, limit)
} else {
rows, err = s.db.Query(`
SELECT id, host_id, event_type, payload_json, created_at
FROM seer_events WHERE created_at > ? ORDER BY created_at ASC LIMIT ?`, since, limit)
}
if err != nil {
return nil, err
}
defer rows.Close()
var events []SeerEvent
for rows.Next() {
var e SeerEvent
var hostID sql.NullString
if err := rows.Scan(&e.ID, &hostID, &e.EventType, &e.PayloadJSON, &e.CreatedAt); err != nil {
return nil, err
}
if hostID.Valid {
e.HostID = hostID.String
}
events = append(events, e)
}
return events, rows.Err()
}
func nullString(s string) sql.NullString { func nullString(s string) sql.NullString {
if s == "" { if s == "" {
return sql.NullString{} return sql.NullString{}

View File

@@ -1,6 +1,7 @@
package fleet package fleet
import ( import (
"crypto/subtle"
"encoding/json" "encoding/json"
"log" "log"
"net/http" "net/http"
@@ -329,14 +330,7 @@ func (h *Hub) HandleBeacon(store *Store) http.HandlerFunc {
} }
func constantTimeEqual(a, b string) bool { func constantTimeEqual(a, b string) bool {
if len(a) != len(b) { return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
return false
}
var v byte
for i := 0; i < len(a); i++ {
v |= a[i] ^ b[i]
}
return v == 0
} }
func coalesce(values ...string) string { func coalesce(values ...string) string {