fix(mcp): correct fleet online status, unsafe version filter, audit totals

summariseServer compared Status against "online", a value never assigned
anywhere (the real vocabulary is pending/active/offline), so every server
misreported as offline. search_fleet's version_below used a lexicographic
comparison across dpkg/rpm/apk version schemes with no common ordering, so
it refuses that filter now and returns all matches instead of a wrong
answer. listAuditResult's Total carried the "shown" JSON tag and the
capped count; it now reports the real total alongside shown.
This commit is contained in:
2026-09-09 07:24:09 +00:00
parent ed79df4270
commit 191a8e9074
3 changed files with 35 additions and 11 deletions
+2 -2
View File
@@ -20,14 +20,14 @@ type serverSummary struct {
}
// models.Server has no Online bool: it stores Status as one of "pending",
// "online" or "offline" (see internal/services/servers.go). Online here
// "active" or "offline" (see internal/services/servers.go). Online here
// mirrors that string the same way the REST layer treats it.
func summariseServer(s models.Server) serverSummary {
return serverSummary{
ID: s.ServerID,
Hostname: s.Hostname,
OS: s.OSInfo,
Online: s.Status == "online",
Online: s.Status == "active",
Tags: s.Tags,
}
}
+18
View File
@@ -58,3 +58,21 @@ func TestServerSummaryStaysSmall(t *testing.T) {
t.Errorf("30 servers serialise to %d bytes, want at most 8000", len(out))
}
}
// The real status vocabulary is "pending" / "active" / "offline" (see
// internal/services/servers.go) — "online" is never assigned anywhere. A
// server carrying the live status ("active") must project as Online: true,
// or list_servers/get_server misreport the entire fleet as down.
func TestSummariseServerReportsActiveAsOnline(t *testing.T) {
active := summariseServer(models.Server{ServerID: "srv-active", Status: "active"})
if !active.Online {
t.Errorf("server with status %q should be online, got Online=false", "active")
}
for _, status := range []string{"pending", "offline"} {
s := summariseServer(models.Server{ServerID: "srv-" + status, Status: status})
if s.Online {
t.Errorf("server with status %q should not be online, got Online=true", status)
}
}
}
+15 -9
View File
@@ -133,7 +133,8 @@ type auditEventSummary struct {
type listAuditResult struct {
Events []auditEventSummary `json:"events"`
Total int `json:"shown"`
Total int64 `json:"total"`
Shown int `json:"shown"`
}
// ---- secrets ----
@@ -433,14 +434,22 @@ func init() {
Name: "search_fleet",
Scope: "vulns:read",
Description: "Search every server's installed packages by name across the whole fleet — " +
"answers questions like \"which hosts still run OpenSSL 1.1\". Pass version_below to " +
"further narrow to versions that sort earlier than the given string.",
"answers questions like \"which hosts still run OpenSSL 1.1\". version_below is not " +
"currently supported: filtering package versions correctly requires knowing each " +
"distribution's own version-ordering scheme (dpkg/rpm/apk), which this tool cannot " +
"determine, so it refuses rather than guess with a lexicographic comparison. Every " +
"matching install is returned; compare versions yourself.",
Handler: func(ctx context.Context, c Caller, args map[string]any) (any, error) {
name := stringArg(args, "name")
if name == "" {
return nil, fmt.Errorf("name is required")
}
versionBelow := stringArg(args, "version_below")
if stringArg(args, "version_below") != "" {
return nil, fmt.Errorf("version_below is not supported: correct version ordering " +
"depends on each host's distribution (dpkg/rpm/apk each order differently), " +
"which this tool cannot resolve here — omit version_below and every matching " +
"install is returned instead")
}
hits, err := services.SearchPackages(c.InstanceID, name)
if err != nil {
@@ -453,9 +462,6 @@ func init() {
if len(out) == limit {
break
}
if versionBelow != "" && h.Version >= versionBelow {
continue
}
// A hit's server must be resolved through the token's own
// scope: SearchPackages runs unscoped across the instance,
// so a server outside the token's tag restriction is
@@ -477,7 +483,7 @@ func init() {
"Filter by event_type prefix (e.g. \"workflow\", \"key\", \"server\").",
Handler: func(ctx context.Context, c Caller, args map[string]any) (any, error) {
limit := int64(pageLimit(args))
events, _, err := services.ListAuditEvents(c.InstanceID, services.AuditFilter{
events, total, err := services.ListAuditEvents(c.InstanceID, services.AuditFilter{
Category: stringArg(args, "event_type"),
Limit: limit,
})
@@ -488,7 +494,7 @@ func init() {
for _, e := range events {
out = append(out, auditEventSummary{At: e.CreatedAt, Type: e.EventType, Actor: e.Actor, Detail: e.Details})
}
return listAuditResult{Events: out, Total: len(out)}, nil
return listAuditResult{Events: out, Total: total, Shown: len(out)}, nil
},
})