chore: add copyright headers, CI tests, and sanitize gitignore

This commit is contained in:
DmitrL-dev 2026-03-31 22:13:34 +10:00
parent 5cbb3d89d3
commit d1f844235e
325 changed files with 2267 additions and 902 deletions

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
// Package soc provides SOC analytics: event trends, severity distribution,
// top sources, MITRE ATT&CK coverage, and time-series aggregation.
package soc

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
package soc
import (

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
package soc
import (
@ -524,4 +528,3 @@ func TestE2E_CrescendoEscalation(t *testing.T) {
assert.Equal(t, domsoc.SeverityCritical, lastInc.Severity)
assert.Contains(t, lastInc.MITREMapping, "T1059")
}

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
package soc
import (

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
package soc
import (

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
// Package soc provides application services for the SENTINEL AI SOC subsystem.
package soc
@ -29,14 +33,14 @@ const (
// Service orchestrates the SOC event pipeline:
// Step 0: Secret Scanner (INVARIANT) → DIP → Decision Logger → Persist → Correlation.
type Service struct {
mu sync.RWMutex
repo domsoc.SOCRepository
logger *audit.DecisionLogger
rules []domsoc.SOCCorrelationRule
mu sync.RWMutex
repo domsoc.SOCRepository
logger *audit.DecisionLogger
rules []domsoc.SOCCorrelationRule
playbookEngine *domsoc.PlaybookEngine
executorRegistry *domsoc.ExecutorRegistry
sensors map[string]*domsoc.Sensor
draining bool // §15.7: graceful shutdown mode — rejects new events
sensors map[string]*domsoc.Sensor
draining bool // §15.7: graceful shutdown mode — rejects new events
// Alert Clustering engine (§7.6): groups related alerts.
clusterEngine *domsoc.ClusterEngine
@ -45,8 +49,8 @@ type Service struct {
eventBus *domsoc.EventBus
// Rate limiting per sensor (§17.3): sensorID → timestamps of recent events.
sensorRates map[string][]time.Time
rateLimitDisabled bool
sensorRates map[string][]time.Time
rateLimitDisabled bool
// Sensor authentication (§17.3 T-01): sensorID → pre-shared key.
sensorKeys map[string]string
@ -85,9 +89,9 @@ func NewService(repo domsoc.SOCRepository, logger *audit.DecisionLogger) *Servic
// Build executor registry with all SOAR action handlers
reg := domsoc.NewExecutorRegistry()
reg.Register(&domsoc.BlockIPExecutor{})
reg.Register(domsoc.NewNotifyExecutor("")) // URL configured via SetNotifyURL()
reg.Register(domsoc.NewNotifyExecutor("")) // URL configured via SetNotifyURL()
reg.Register(domsoc.NewQuarantineExecutor())
reg.Register(domsoc.NewEscalateExecutor("")) // URL configured via SetEscalateURL()
reg.Register(domsoc.NewEscalateExecutor("")) // URL configured via SetEscalateURL()
// Webhook executor configured separately via SetWebhookConfig()
// Create playbook engine with live executor handler (not just logging)
@ -100,21 +104,21 @@ func NewService(repo domsoc.SOCRepository, logger *audit.DecisionLogger) *Servic
)
return &Service{
repo: repo,
logger: logger,
rules: domsoc.DefaultSOCCorrelationRules(),
playbookEngine: pe,
executorRegistry: reg,
sensors: make(map[string]*domsoc.Sensor),
clusterEngine: domsoc.NewClusterEngine(domsoc.DefaultClusterConfig()),
eventBus: domsoc.NewEventBus(256),
sensorRates: make(map[string][]time.Time),
zeroG: domsoc.NewZeroGMode(),
p2pSync: domsoc.NewP2PSyncService(),
anomaly: domsoc.NewAnomalyDetector(),
repo: repo,
logger: logger,
rules: domsoc.DefaultSOCCorrelationRules(),
playbookEngine: pe,
executorRegistry: reg,
sensors: make(map[string]*domsoc.Sensor),
clusterEngine: domsoc.NewClusterEngine(domsoc.DefaultClusterConfig()),
eventBus: domsoc.NewEventBus(256),
sensorRates: make(map[string][]time.Time),
zeroG: domsoc.NewZeroGMode(),
p2pSync: domsoc.NewP2PSyncService(),
anomaly: domsoc.NewAnomalyDetector(),
threatIntelEngine: domsoc.NewThreatIntelEngine(),
retention: domsoc.NewDataRetentionPolicy(),
scanSemaphore: make(chan struct{}, 8), // §20.1: max 8 concurrent scans
retention: domsoc.NewDataRetentionPolicy(),
scanSemaphore: make(chan struct{}, 8), // §20.1: max 8 concurrent scans
}
}
@ -213,7 +217,6 @@ func (s *Service) TestWebhook() []WebhookResult {
return wh.NotifyIncident("webhook_test", testIncident)
}
// Drain puts the service into drain mode (§15.7 Stage 1).
// New events are rejected with ErrDraining; existing processing continues.
func (s *Service) Drain() {
@ -301,6 +304,7 @@ func (s *Service) runRetentionPurge() {
}
}
}
// IngestEvent processes an incoming security event through the SOC pipeline.
// Returns the event ID and any incident created by correlation.
//
@ -523,7 +527,7 @@ func (s *Service) isRateLimited(sensorID string) bool {
pruned = append(pruned, ts)
}
}
rateLimited := len(pruned) >= MaxEventsPerSecondPerSensor
if !rateLimited {
pruned = append(pruned, now)
@ -768,9 +772,9 @@ func (s *Service) GetRecentDecisions(limit int) []map[string]any {
return []map[string]any{
{
"total_decisions": s.logger.Count(),
"hash_chain": s.logger.PrevHash(),
"log_path": s.logger.Path(),
"status": "operational",
"hash_chain": s.logger.PrevHash(),
"log_path": s.logger.Path(),
"status": "operational",
},
}
}
@ -979,10 +983,10 @@ func (s *Service) ListIncidentsAdvanced(f IncidentFilter) (*IncidentFilterResult
// BulkAction defines a batch operation on incidents.
type BulkAction struct {
Action string `json:"action"` // assign, status, close, delete
Action string `json:"action"` // assign, status, close, delete
IncidentIDs []string `json:"incident_ids"`
Value string `json:"value"` // analyst email, new status
Actor string `json:"actor"` // who initiated
Value string `json:"value"` // analyst email, new status
Actor string `json:"actor"` // who initiated
}
// BulkActionResult is the result of a batch operation.
@ -1030,12 +1034,12 @@ type SLAThreshold struct {
// SLAStatus represents an incident's SLA compliance state.
type SLAStatus struct {
ResponseBreached bool `json:"response_breached"`
ResolutionBreached bool `json:"resolution_breached"`
ResponseRemaining float64 `json:"response_remaining_min"` // minutes remaining (negative = breached)
ResponseBreached bool `json:"response_breached"`
ResolutionBreached bool `json:"resolution_breached"`
ResponseRemaining float64 `json:"response_remaining_min"` // minutes remaining (negative = breached)
ResolutionRemaining float64 `json:"resolution_remaining_min"`
ResponseTarget float64 `json:"response_target_min"`
ResolutionTarget float64 `json:"resolution_target_min"`
ResponseTarget float64 `json:"response_target_min"`
ResolutionTarget float64 `json:"resolution_target_min"`
}
// DefaultSLAThresholds returns SLA targets per severity.
@ -1161,7 +1165,7 @@ func (s *Service) Dashboard(tenantID string) (*DashboardData, error) {
return nil, err
}
lastHourEvents, err := s.repo.CountEventsSince(tenantID, time.Now().Add(-1 * time.Hour))
lastHourEvents, err := s.repo.CountEventsSince(tenantID, time.Now().Add(-1*time.Hour))
if err != nil {
return nil, err
}

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
package soc
import (

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
package soc
import (
@ -17,7 +21,7 @@ type STIXBundle struct {
// STIXObject represents a generic STIX 2.1 object.
type STIXObject struct {
Type string `json:"type"` // indicator, malware, attack-pattern, etc.
Type string `json:"type"` // indicator, malware, attack-pattern, etc.
ID string `json:"id"`
Created time.Time `json:"created"`
Modified time.Time `json:"modified"`

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
package soc
import (
@ -111,8 +115,8 @@ func TestProcessBundle_FiltersNonIndicators(t *testing.T) {
Objects: []STIXObject{
{Type: "indicator", Pattern: "[ipv4-addr:value = '10.0.0.1']", Modified: time.Now()},
{Type: "malware", Name: "BadMalware"}, // should be skipped
{Type: "indicator", Pattern: ""}, // empty pattern → skipped
{Type: "attack-pattern", Name: "Phish"}, // should be skipped
{Type: "indicator", Pattern: ""}, // empty pattern → skipped
{Type: "attack-pattern", Name: "Phish"}, // should be skipped
{Type: "indicator", Pattern: "[domain-name:value = 'bad.com']", Modified: time.Now()},
},
}

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
// Package soc provides a threat intelligence feed integration
// for enriching SOC events and correlation rules.
//
@ -36,9 +40,9 @@ const (
type IOC struct {
Type IOCType `json:"type"`
Value string `json:"value"`
Source string `json:"source"` // Feed name
Severity string `json:"severity"` // critical/high/medium/low
Tags []string `json:"tags"` // MITRE ATT&CK, campaign, etc.
Source string `json:"source"` // Feed name
Severity string `json:"severity"` // critical/high/medium/low
Tags []string `json:"tags"` // MITRE ATT&CK, campaign, etc.
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
Confidence float64 `json:"confidence"` // 0.0-1.0
@ -46,31 +50,31 @@ type IOC struct {
// ThreatFeed represents a configured threat intelligence source.
type ThreatFeed struct {
Name string `json:"name"`
URL string `json:"url"`
Type string `json:"type"` // stix, csv, json
Enabled bool `json:"enabled"`
Interval time.Duration `json:"interval"`
APIKey string `json:"api_key,omitempty"`
LastFetch time.Time `json:"last_fetch"`
IOCCount int `json:"ioc_count"`
LastError string `json:"last_error,omitempty"`
Name string `json:"name"`
URL string `json:"url"`
Type string `json:"type"` // stix, csv, json
Enabled bool `json:"enabled"`
Interval time.Duration `json:"interval"`
APIKey string `json:"api_key,omitempty"`
LastFetch time.Time `json:"last_fetch"`
IOCCount int `json:"ioc_count"`
LastError string `json:"last_error,omitempty"`
}
// ─── Threat Intel Store ─────────────────────────────────
// ThreatIntelStore manages IOCs from multiple feeds.
type ThreatIntelStore struct {
mu sync.RWMutex
iocs map[string]*IOC // key: type:value
feeds []ThreatFeed
mu sync.RWMutex
iocs map[string]*IOC // key: type:value
feeds []ThreatFeed
client *http.Client
// Stats
TotalIOCs int `json:"total_iocs"`
TotalFeeds int `json:"total_feeds"`
TotalIOCs int `json:"total_iocs"`
TotalFeeds int `json:"total_feeds"`
LastRefresh time.Time `json:"last_refresh"`
MatchesFound int64 `json:"matches_found"`
MatchesFound int64 `json:"matches_found"`
}
// NewThreatIntelStore creates an empty threat intel store.

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
package soc
import (

View file

@ -1,3 +1,7 @@
// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
// Package webhook provides outbound SOAR webhook notifications
// for the SOC pipeline. Fires HTTP POST on incident creation/update.
package soc
@ -38,8 +42,8 @@ type WebhookConfig struct {
type WebhookPayload struct {
EventType string `json:"event_type"` // incident_created, incident_updated, sensor_offline
Timestamp time.Time `json:"timestamp"`
Source string `json:"source"`
Data json.RawMessage `json:"data"`
Source string `json:"source"`
Data json.RawMessage `json:"data"`
}
// WebhookResult tracks delivery status per endpoint.
@ -80,8 +84,6 @@ func NewWebhookNotifier(config WebhookConfig) *WebhookNotifier {
}
}
// NotifyIncident sends an incident webhook to all configured endpoints.
// Non-blocking: fires goroutines for each endpoint.
func (w *WebhookNotifier) NotifyIncident(eventType string, incident *domsoc.Incident) []WebhookResult {
@ -105,7 +107,7 @@ func (w *WebhookNotifier) NotifyIncident(eventType string, incident *domsoc.Inci
EventType: eventType,
Timestamp: time.Now().UTC(),
Source: "sentinel-soc",
Data: data,
Data: data,
}
body, err := json.Marshal(payload)
@ -151,7 +153,7 @@ func (w *WebhookNotifier) NotifySensorOffline(sensor domsoc.Sensor) []WebhookRes
EventType: "sensor_offline",
Timestamp: time.Now().UTC(),
Source: "sentinel-soc",
Data: data,
Data: data,
}
body, _ := json.Marshal(payload)