mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-20 06:27:53 +02:00
fix: preserve attack-chain summary provenance and evidence access
This commit is contained in:
1 parent
983c981b92
commit
2b76b31355
15 files changed
+1022
-66
No files matched your search
@@ -136,11 +136,6 @@ func (h *AttackChainHandler) RegenerateAttackChain(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 删除旧的攻击链
|
||||
if err := h.db.DeleteAttackChain(conversationID); err != nil {
|
||||
h.logger.Warn("删除旧攻击链失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 使用锁机制防止并发生成
|
||||
lockInterface, _ := h.generatingLocks.LoadOrStore(conversationID, &sync.Mutex{})
|
||||
lock := lockInterface.(*sync.Mutex)
|
||||
@@ -153,6 +148,11 @@ func (h *AttackChainHandler) RegenerateAttackChain(c *gin.Context) {
|
||||
}
|
||||
defer lock.Unlock()
|
||||
|
||||
// 只有持锁的生成请求可以删除旧图;返回 409 的请求不得修改缓存。
|
||||
if err := h.db.DeleteAttackChain(conversationID); err != nil {
|
||||
h.logger.Warn("删除旧攻击链失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 生成新的攻击链
|
||||
h.logger.Info("重新生成攻击链", zap.String("conversationId", conversationID))
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"cyberstrike-ai/internal/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestBusyAttackChainRegenerationPreservesCachedGraph(t *testing.T) {
|
||||
db, err := database.NewDB(filepath.Join(t.TempDir(), "chain.db"), zap.NewNop())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
conv, err := db.CreateConversation("cached", database.ConversationCreateMeta{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, id := range []string{"target", "action"} {
|
||||
if err := db.SaveAttackChainNode(conv.ID, id, id, "saved "+id, "", `{}`, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := db.SaveAttackChainEdge(conv.ID, "edge", "target", "action", "leads_to", 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h := NewAttackChainHandler(db, nil, zap.NewNop())
|
||||
lock := &sync.Mutex{}
|
||||
lock.Lock() // Another generation is still writing this conversation's graph.
|
||||
defer lock.Unlock()
|
||||
h.generatingLocks.Store(conv.ID, lock)
|
||||
router := gin.New()
|
||||
router.POST("/api/attack-chain/:conversationId/regenerate", h.RegenerateAttackChain)
|
||||
for i := 0; i < 2; i++ {
|
||||
recorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/api/attack-chain/"+conv.ID+"/regenerate", nil))
|
||||
if recorder.Code != http.StatusConflict {
|
||||
t.Fatalf("busy regeneration returned %d: %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
nodes, err := db.LoadAttackChainNodes(conv.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
edges, err := db.LoadAttackChainEdges(conv.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(nodes) != 2 || len(edges) != 1 || edges[0].Source != "target" || edges[0].Target != "action" {
|
||||
t.Fatalf("409 request modified the graph being generated: nodes=%+v edges=%+v", nodes, edges)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user