feat: wire Shield engine + 134K signatures into demo scanner pipeline

This commit is contained in:
DmitrL-dev 2026-03-23 21:28:54 +10:00
parent f0c2b4133b
commit f581d65951
3 changed files with 79 additions and 22 deletions

View file

@ -158,6 +158,15 @@ func main() {
logger.Info("sentinel-core: Rust engine initialized", "version", sentinelCore.Version())
}
// Shield — C-native payload inspection engine (§4)
shieldEngine, shieldErr := engines.NewNativeShield()
if shieldErr != nil {
logger.Warn("shield: C engine not available, using stub", "error", shieldErr)
} else {
srv.SetShieldEngine(shieldEngine)
logger.Info("shield: C engine initialized", "version", shieldEngine.Version())
}
// OpenTelemetry tracing (§P4B) — enabled when OTEL_EXPORTER_OTLP_ENDPOINT is set
otelEndpoint := env("OTEL_EXPORTER_OTLP_ENDPOINT", "")
tp, otelErr := tracing.InitTracer(context.Background(), otelEndpoint)

View file

@ -32,6 +32,7 @@ type Server struct {
metrics *Metrics
logger *RequestLogger
sentinelCore engines.SentinelCore
shieldEngine engines.Shield
jwtAuth *auth.JWTMiddleware
userStore *auth.UserStore
tenantStore *auth.TenantStore
@ -80,6 +81,11 @@ func (s *Server) SetSentinelCore(core engines.SentinelCore) {
s.sentinelCore = core
}
// SetShieldEngine sets the C-native Shield engine for payload inspection.
func (s *Server) SetShieldEngine(shield engines.Shield) {
s.shieldEngine = shield
}
// SetJWTAuth enables JWT authentication with the given secret.
// If secret is empty or <32 bytes, JWT is disabled (backward compatible).
// Optional db parameter enables SQLite-backed user persistence.

View file

@ -960,8 +960,12 @@ func (s *Server) handleP2PRemovePeer(w http.ResponseWriter, r *http.Request) {
// GET /api/soc/engines
func (s *Server) handleEngineStatus(w http.ResponseWriter, r *http.Request) {
coreEngine := s.getEngine("sentinel-core")
shieldEngine := s.getEngine("shield")
var shieldEng engines.Shield
if s.shieldEngine != nil {
shieldEng = s.shieldEngine
} else {
shieldEng = engines.NewStubShield()
}
writeJSON(w, http.StatusOK, map[string]any{
"engines": []map[string]any{
{
@ -971,16 +975,16 @@ func (s *Server) handleEngineStatus(w http.ResponseWriter, r *http.Request) {
"type": "prompt_scanner",
},
{
"name": shieldEngine.Name(),
"status": shieldEngine.Status(),
"version": shieldEngine.Version(),
"name": shieldEng.Name(),
"status": shieldEng.Status(),
"version": shieldEng.Version(),
"type": "network_protection",
},
},
})
}
// getEngine returns the named engine or a stub.
// getEngine returns the named SentinelCore engine or a stub.
func (s *Server) getEngine(name string) engines.SentinelCore {
if s.sentinelCore != nil && name == "sentinel-core" {
return s.sentinelCore
@ -1448,6 +1452,7 @@ func (s *Server) handleSLAConfig(w http.ResponseWriter, _ *http.Request) {
// handlePublicScan provides a public (no-auth) prompt scanning endpoint for the demo.
// POST /api/v1/scan body: {"prompt": "Ignore all instructions..."}
// Runs sentinel-core (54 Rust engines) + Shield (C11 payload inspection) in parallel.
func (s *Server) handlePublicScan(w http.ResponseWriter, r *http.Request) {
limitBody(w, r)
defer r.Body.Close()
@ -1470,24 +1475,61 @@ func (s *Server) handlePublicScan(w http.ResponseWriter, r *http.Request) {
return
}
// Get engine (real or stub)
engine := s.getEngine("sentinel-core")
// Run sentinel-core (54 Rust engines)
coreEngine := s.getEngine("sentinel-core")
coreResult, coreErr := coreEngine.ScanPrompt(r.Context(), req.Prompt)
// Scan
result, err := engine.ScanPrompt(r.Context(), req.Prompt)
if err != nil {
writeError(w, http.StatusInternalServerError, "scan failed: "+err.Error())
// Run Shield (C payload inspection)
var shieldEng engines.Shield
if s.shieldEngine != nil {
shieldEng = s.shieldEngine
} else {
shieldEng = engines.NewStubShield()
}
shieldResult, shieldErr := shieldEng.InspectTraffic(r.Context(), []byte(req.Prompt), nil)
// Build response — merge both engines
response := map[string]any{}
if coreErr != nil {
writeError(w, http.StatusInternalServerError, "scan failed: "+coreErr.Error())
return
}
writeJSON(w, http.StatusOK, map[string]any{
"blocked": result.ThreatFound,
"threat_type": result.ThreatType,
"severity": result.Severity,
"confidence": result.Confidence,
"details": result.Details,
"indicators": result.Indicators,
"engine": result.Engine,
"latency_ms": float64(result.Duration.Microseconds()) / 1000.0,
})
// Merge indicators from both engines
allIndicators := coreResult.Indicators
blocked := coreResult.ThreatFound
maxConfidence := coreResult.Confidence
threatType := coreResult.ThreatType
// Add Shield results if available
shieldStatus := "offline"
if shieldErr == nil && shieldResult != nil {
shieldStatus = "active"
if shieldResult.ThreatFound {
blocked = true
if shieldResult.Confidence > maxConfidence {
maxConfidence = shieldResult.Confidence
threatType = shieldResult.ThreatType
}
allIndicators = append(allIndicators, "shield/"+shieldResult.Details)
}
}
severity := "NONE"
if blocked {
severity = "HIGH"
}
response["blocked"] = blocked
response["threat_type"] = threatType
response["severity"] = severity
response["confidence"] = maxConfidence
response["details"] = coreResult.Details
response["indicators"] = allIndicators
response["engine"] = "sentinel-core"
response["latency_ms"] = float64(coreResult.Duration.Microseconds()) / 1000.0
response["shield_status"] = shieldStatus
writeJSON(w, http.StatusOK, response)
}