Watch
1
0
Fork
You've already forked RedFlag
0

fix: comma-ok guards on agent-supplied metadata and gin context

Malformed agent metadata could panic the server (rapid_polling
fields, buffered event metadata, timeout params). scanner_config
asserted uuid.UUID on a user_id the middleware stores as string —
guaranteed panic on both admin endpoints.
This commit is contained in:
Fimeg 2026-06-11 20:17:26 -04:00
commit 230b2f9ab9
5 changed files with 49 additions and 39 deletions

View file

@ -527,6 +527,8 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
message := getStringFromMap(eventMap, "message")
if eventType != "" && eventSubtype != "" && severity != "" {
// metadata is optional and agent-supplied; don't trust its shape
metadata, _ := eventMap["metadata"].(map[string]interface{})
event := &models.SystemEvent{
AgentID: &agentID,
EventType: eventType,
@ -534,7 +536,7 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
Severity: severity,
Component: component,
Message: message,
Metadata: eventMap["metadata"].(map[string]interface{}),
Metadata: metadata,
CreatedAt: time.Now().UTC(),
}
@ -637,12 +639,12 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
// Process heartbeat metadata from agent check-ins
if metrics.Metadata != nil {
if rapidPollingEnabled, exists := metrics.Metadata["rapid_polling_enabled"]; exists {
if rapidPollingUntil, exists := metrics.Metadata["rapid_polling_until"]; exists {
if rapidPollingEnabled, ok := metrics.Metadata["rapid_polling_enabled"].(bool); ok {
if rapidPollingUntil, ok := metrics.Metadata["rapid_polling_until"].(string); ok {
// Parse the until timestamp
if untilTime, err := time.Parse(time.RFC3339, rapidPollingUntil.(string)); err == nil {
if untilTime, err := time.Parse(time.RFC3339, rapidPollingUntil); err == nil {
// Validate if rapid polling is still active (not expired)
isActive := rapidPollingEnabled.(bool) && time.Now().UTC().Before(untilTime)
isActive := rapidPollingEnabled && time.Now().UTC().Before(untilTime)
// Store heartbeat status in agent metadata
agent.Metadata["rapid_polling_enabled"] = rapidPollingEnabled
@ -683,12 +685,12 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
if metrics.Metadata != nil {
agent, err := h.agentQueries.GetAgentByID(agentID)
if err == nil && agent.Metadata != nil {
if rapidPollingEnabled, exists := metrics.Metadata["rapid_polling_enabled"]; exists {
if rapidPollingUntil, exists := metrics.Metadata["rapid_polling_until"]; exists {
if rapidPollingEnabled, ok := metrics.Metadata["rapid_polling_enabled"].(bool); ok {
if rapidPollingUntil, ok := metrics.Metadata["rapid_polling_until"].(string); ok {
// Parse the until timestamp
if untilTime, err := time.Parse(time.RFC3339, rapidPollingUntil.(string)); err == nil {
if untilTime, err := time.Parse(time.RFC3339, rapidPollingUntil); err == nil {
// Validate if rapid polling is still active (not expired)
isActive := rapidPollingEnabled.(bool) && time.Now().UTC().Before(untilTime)
isActive := rapidPollingEnabled && time.Now().UTC().Before(untilTime)
// Store heartbeat status in agent metadata
agent.Metadata["rapid_polling_enabled"] = rapidPollingEnabled
@ -899,8 +901,8 @@ func (h *AgentHandler) GetCommands(c *gin.Context) {
// Check if agent is reporting heartbeat in this check-in
agentReportingHeartbeat := false
if metrics.Metadata != nil {
if agentEnabled, exists := metrics.Metadata["rapid_polling_enabled"]; exists {
agentReportingHeartbeat = agentEnabled.(bool)
if agentEnabled, ok := metrics.Metadata["rapid_polling_enabled"].(bool); ok {
agentReportingHeartbeat = agentEnabled
}
}
@ -1131,28 +1133,28 @@ func (h *AgentHandler) GetHeartbeatStatus(c *gin.Context) {
if agent.Metadata != nil {
// Check if heartbeat is enabled in metadata
if enabled, exists := agent.Metadata["rapid_polling_enabled"]; exists {
response["enabled"] = enabled.(bool)
if enabled, ok := agent.Metadata["rapid_polling_enabled"].(bool); ok {
response["enabled"] = enabled
// If enabled, get the until time and check if still active
if enabled.(bool) {
if untilStr, exists := agent.Metadata["rapid_polling_until"]; exists {
response["until"] = untilStr.(string)
if enabled {
if untilStr, ok := agent.Metadata["rapid_polling_until"].(string); ok {
response["until"] = untilStr
// Parse the until timestamp to check if still active
if untilTime, err := time.Parse(time.RFC3339, untilStr.(string)); err == nil {
if untilTime, err := time.Parse(time.RFC3339, untilStr); err == nil {
response["active"] = time.Now().UTC().Before(untilTime)
}
}
// Get duration if available
if duration, exists := agent.Metadata["rapid_polling_duration_minutes"]; exists {
response["duration_minutes"] = duration.(float64)
if duration, ok := agent.Metadata["rapid_polling_duration_minutes"].(float64); ok {
response["duration_minutes"] = duration
}
// Get source if available
if source, exists := agent.Metadata["heartbeat_source"]; exists {
response["source"] = source.(string)
if source, ok := agent.Metadata["heartbeat_source"].(string); ok {
response["source"] = source
}
}
}

View file

@ -68,15 +68,9 @@ func (h *RateLimitHandler) ResetRateLimitSettings(c *gin.Context) {
func (h *RateLimitHandler) GetRateLimitStats(c *gin.Context) {
settings := h.rateLimiter.GetSettings()
// Calculate total requests and windows
stats := gin.H{
"total_configured_limits": 6,
"enabled_limits": 0,
"total_requests_per_minute": 0,
"settings": settings,
}
// Count enabled limits and total requests
enabledLimits := 0
totalRequestsPerMinute := 0
for _, config := range []middleware.RateLimitConfig{
settings.AgentRegistration,
settings.AgentCheckIn,
@ -86,9 +80,16 @@ func (h *RateLimitHandler) GetRateLimitStats(c *gin.Context) {
settings.PublicAccess,
} {
if config.Enabled {
stats["enabled_limits"] = stats["enabled_limits"].(int) + 1
enabledLimits++
}
stats["total_requests_per_minute"] = stats["total_requests_per_minute"].(int) + config.Requests
totalRequestsPerMinute += config.Requests
}
stats := gin.H{
"total_configured_limits": 6,
"enabled_limits": enabledLimits,
"total_requests_per_minute": totalRequestsPerMinute,
"settings": settings,
}
c.JSON(http.StatusOK, stats)

View file

@ -7,7 +7,6 @@ import (
"github.com/Fimeg/RedFlag/server/internal/database/queries"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid/v5"
"github.com/jmoiron/sqlx"
)
@ -77,7 +76,8 @@ func (h *ScannerConfigHandler) UpdateScannerTimeout(c *gin.Context) {
}
// Create audit event in History table (ETHOS compliance)
userID := c.MustGet("user_id").(uuid.UUID)
// user_id is stored as a string by WebAuthMiddleware
userID := c.GetString("user_id")
/*
event := &models.SystemEvent{
ID: uuid.Must(uuid.NewV4()),
@ -129,7 +129,7 @@ func (h *ScannerConfigHandler) ResetScannerTimeout(c *gin.Context) {
}
// Audit log
userID := c.MustGet("user_id").(uuid.UUID)
userID := c.GetString("user_id")
log.Printf("[AUDIT] User %s reset scanner timeout: %s to default %v", userID, scannerName, defaultTimeout)
c.JSON(http.StatusOK, gin.H{

View file

@ -187,9 +187,10 @@ func (h *SecurityHandler) MachineBindingStatus(c *gin.Context) {
response["checks"].(map[string]interface{})["version_compliance"] = compliantAgents
}
// Set recent violations based on version compliance gap
boundAgents := response["checks"].(map[string]interface{})["bound_agents"].(int)
versionCompliance := response["checks"].(map[string]interface{})["version_compliance"].(int)
// Set recent violations based on version compliance gap.
// Either count may be absent if its query failed above; default to 0.
boundAgents, _ := response["checks"].(map[string]interface{})["bound_agents"].(int)
versionCompliance, _ := response["checks"].(map[string]interface{})["version_compliance"].(int)
violations := boundAgents - versionCompliance
if violations < 0 {
violations = 0

View file

@ -257,9 +257,15 @@ func (ts *TimeoutService) updateRelatedPackageStatus(command *models.AgentComman
"failure_reason": "operation timed out",
}
packageType, typeOK := command.Params["package_type"].(string)
packageName, nameOK := command.Params["package_name"].(string)
if !typeOK || !nameOK {
return fmt.Errorf("command %s has update_id but missing package_type/package_name params", command.ID)
}
return ts.updateQueries.UpdatePackageStatus(command.AgentID,
command.Params["package_type"].(string),
command.Params["package_name"].(string),
packageType,
packageName,
models.StatusFailed,
metadata,
nil) // nil = use time.Now().UTC() for timeout operations