mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-05-09 03:22:37 +02:00
Release prep: 54 engines, self-hosted signatures, i18n, dashboard updates
This commit is contained in:
parent
694e32be26
commit
41cbfd6e0a
178 changed files with 36008 additions and 399 deletions
109
internal/domain/identity/pinning.go
Normal file
109
internal/domain/identity/pinning.go
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package identity
|
||||
|
||||
// Context-aware trimming with security event pinning (SDD-003 M5).
|
||||
//
|
||||
// Security events are pinned in context and exempt from trimming
|
||||
// when the context window overflows. This prevents attackers from
|
||||
// waiting for security events to be evicted.
|
||||
|
||||
// Message represents a context window message.
|
||||
type Message struct {
|
||||
Role string `json:"role"` // "user", "assistant", "system", "security"
|
||||
Content string `json:"content"`
|
||||
TokenCount int `json:"token_count"`
|
||||
IsPinned bool `json:"is_pinned"` // Security events are pinned
|
||||
EventType string `json:"event_type,omitempty"` // For security messages
|
||||
}
|
||||
|
||||
// PinnedEventTypes are security events that MUST NOT be trimmed from context.
|
||||
var PinnedEventTypes = map[string]bool{
|
||||
"permission_denied": true,
|
||||
"injection_detected": true,
|
||||
"circuit_breaker_open": true,
|
||||
"credential_access_blocked": true,
|
||||
"exfiltration_attempt": true,
|
||||
"ssrf_blocked": true,
|
||||
"genai_credential_access": true,
|
||||
"genai_persistence": true,
|
||||
}
|
||||
|
||||
// IsPinnedEvent returns true if the event type should be pinned (never trimmed).
|
||||
func IsPinnedEvent(eventType string) bool {
|
||||
return PinnedEventTypes[eventType]
|
||||
}
|
||||
|
||||
// TrimContext trims context messages to fit within maxTokens,
|
||||
// preserving all pinned security events.
|
||||
//
|
||||
// Algorithm:
|
||||
// 1. Separate pinned and unpinned messages
|
||||
// 2. Calculate token budget remaining after pinned messages
|
||||
// 3. Trim unpinned messages (oldest first) to fit budget
|
||||
// 4. Merge: pinned messages in original positions + surviving unpinned
|
||||
func TrimContext(messages []Message, maxTokens int) []Message {
|
||||
if len(messages) == 0 {
|
||||
return messages
|
||||
}
|
||||
|
||||
// Calculate total tokens
|
||||
totalTokens := 0
|
||||
for _, m := range messages {
|
||||
totalTokens += m.TokenCount
|
||||
}
|
||||
|
||||
// If within budget, return as-is
|
||||
if totalTokens <= maxTokens {
|
||||
return messages
|
||||
}
|
||||
|
||||
// Separate pinned and unpinned, preserving original indices
|
||||
type indexedMsg struct {
|
||||
idx int
|
||||
msg Message
|
||||
}
|
||||
var pinned, unpinned []indexedMsg
|
||||
pinnedTokens := 0
|
||||
|
||||
for i, m := range messages {
|
||||
if m.IsPinned || IsPinnedEvent(m.EventType) {
|
||||
pinned = append(pinned, indexedMsg{i, m})
|
||||
pinnedTokens += m.TokenCount
|
||||
} else {
|
||||
unpinned = append(unpinned, indexedMsg{i, m})
|
||||
}
|
||||
}
|
||||
|
||||
// Budget for unpinned messages
|
||||
remainingBudget := maxTokens - pinnedTokens
|
||||
if remainingBudget < 0 {
|
||||
remainingBudget = 0
|
||||
}
|
||||
|
||||
// Trim unpinned from the beginning (oldest first)
|
||||
var survivingUnpinned []indexedMsg
|
||||
usedTokens := 0
|
||||
// Keep messages from the END (newest) that fit
|
||||
for i := len(unpinned) - 1; i >= 0; i-- {
|
||||
if usedTokens + unpinned[i].msg.TokenCount <= remainingBudget {
|
||||
survivingUnpinned = append([]indexedMsg{unpinned[i]}, survivingUnpinned...)
|
||||
usedTokens += unpinned[i].msg.TokenCount
|
||||
}
|
||||
}
|
||||
|
||||
// Merge by original index order
|
||||
all := append(pinned, survivingUnpinned...)
|
||||
// Sort by original index
|
||||
for i := 0; i < len(all); i++ {
|
||||
for j := i + 1; j < len(all); j++ {
|
||||
if all[j].idx < all[i].idx {
|
||||
all[i], all[j] = all[j], all[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]Message, len(all))
|
||||
for i, im := range all {
|
||||
result[i] = im.msg
|
||||
}
|
||||
return result
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue