mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-05-15 06:12: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
193
internal/domain/guidance/guidance.go
Normal file
193
internal/domain/guidance/guidance.go
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
// Package guidance implements the Security Context MCP server domain (SDD-006).
|
||||
//
|
||||
// Provides security guidance, safe patterns, and standards references
|
||||
// for AI agents working with code. Transforms Syntrex from "blocker"
|
||||
// to "advisor" by proactively injecting security knowledge.
|
||||
package guidance
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Reference points to a security standard or source document.
|
||||
type Reference struct {
|
||||
Source string `json:"source"`
|
||||
Section string `json:"section"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// GuidanceEntry is a single piece of security guidance.
|
||||
type GuidanceEntry struct {
|
||||
Topic string `json:"topic"`
|
||||
Title string `json:"title"`
|
||||
Guidance string `json:"guidance"`
|
||||
SafePatterns []string `json:"safe_patterns,omitempty"`
|
||||
Standards []Reference `json:"standards"`
|
||||
Severity string `json:"severity"` // "critical", "high", "medium", "low"
|
||||
Languages []string `json:"languages,omitempty"` // Applicable languages
|
||||
}
|
||||
|
||||
// GuidanceRequest is the input for the security.getGuidance MCP tool.
|
||||
type GuidanceRequest struct {
|
||||
Topic string `json:"topic"`
|
||||
Context string `json:"context"` // Code snippet or description
|
||||
Lang string `json:"lang"` // Programming language
|
||||
}
|
||||
|
||||
// GuidanceResponse is the output from security.getGuidance.
|
||||
type GuidanceResponse struct {
|
||||
Entries []GuidanceEntry `json:"entries"`
|
||||
Query string `json:"query"`
|
||||
Language string `json:"language,omitempty"`
|
||||
}
|
||||
|
||||
// Store holds the security guidance knowledge base.
|
||||
type Store struct {
|
||||
entries []GuidanceEntry
|
||||
}
|
||||
|
||||
// NewStore creates a new guidance store.
|
||||
func NewStore() *Store {
|
||||
return &Store{}
|
||||
}
|
||||
|
||||
// LoadFromDir loads guidance entries from a directory of JSON files.
|
||||
func (s *Store) LoadFromDir(dir string) error {
|
||||
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil || info.IsDir() || filepath.Ext(path) != ".json" {
|
||||
return err
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read %s: %w", path, err)
|
||||
}
|
||||
var entries []GuidanceEntry
|
||||
if err := json.Unmarshal(data, &entries); err != nil {
|
||||
// Try single entry
|
||||
var entry GuidanceEntry
|
||||
if err2 := json.Unmarshal(data, &entry); err2 != nil {
|
||||
return fmt.Errorf("parse %s: %w", path, err)
|
||||
}
|
||||
entries = []GuidanceEntry{entry}
|
||||
}
|
||||
s.entries = append(s.entries, entries...)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// AddEntry adds a guidance entry manually.
|
||||
func (s *Store) AddEntry(entry GuidanceEntry) {
|
||||
s.entries = append(s.entries, entry)
|
||||
}
|
||||
|
||||
// Search finds guidance entries matching the topic and optional language.
|
||||
func (s *Store) Search(topic, lang string) []GuidanceEntry {
|
||||
topic = strings.ToLower(topic)
|
||||
var matches []GuidanceEntry
|
||||
|
||||
for _, entry := range s.entries {
|
||||
if matchesTopic(entry, topic) {
|
||||
if lang == "" || matchesLanguage(entry, lang) {
|
||||
matches = append(matches, entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
// Count returns the number of loaded guidance entries.
|
||||
func (s *Store) Count() int {
|
||||
return len(s.entries)
|
||||
}
|
||||
|
||||
func matchesTopic(entry GuidanceEntry, topic string) bool {
|
||||
entryTopic := strings.ToLower(entry.Topic)
|
||||
title := strings.ToLower(entry.Title)
|
||||
// Exact or substring match on topic or title
|
||||
return strings.Contains(entryTopic, topic) ||
|
||||
strings.Contains(topic, entryTopic) ||
|
||||
strings.Contains(title, topic)
|
||||
}
|
||||
|
||||
func matchesLanguage(entry GuidanceEntry, lang string) bool {
|
||||
if len(entry.Languages) == 0 {
|
||||
return true // Universal guidance
|
||||
}
|
||||
lang = strings.ToLower(lang)
|
||||
for _, l := range entry.Languages {
|
||||
if strings.ToLower(l) == lang {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DefaultOWASPLLMTop10 returns built-in OWASP LLM Top 10 guidance.
|
||||
func DefaultOWASPLLMTop10() []GuidanceEntry {
|
||||
return []GuidanceEntry{
|
||||
{
|
||||
Topic: "injection", Title: "LLM01: Prompt Injection",
|
||||
Guidance: "Validate and sanitize all user inputs before sending to LLM. Use sentinel-core's 67 engines for real-time detection. Never trust LLM output for security-critical decisions without validation.",
|
||||
Severity: "critical",
|
||||
Standards: []Reference{{Source: "OWASP LLM Top 10", Section: "LLM01", URL: "https://genai.owasp.org/llmrisk/llm01-prompt-injection/"}},
|
||||
},
|
||||
{
|
||||
Topic: "output_handling", Title: "LLM02: Insecure Output Handling",
|
||||
Guidance: "Never render LLM output as raw HTML/JS. Sanitize all outputs before display. Use Content Security Policy headers. Validate output format before processing.",
|
||||
Severity: "high",
|
||||
Standards: []Reference{{Source: "OWASP LLM Top 10", Section: "LLM02"}},
|
||||
},
|
||||
{
|
||||
Topic: "training_data", Title: "LLM03: Training Data Poisoning",
|
||||
Guidance: "Verify training data provenance. Use data integrity checks. Monitor for anomalous model outputs indicating poisoned training data.",
|
||||
Severity: "high",
|
||||
Standards: []Reference{{Source: "OWASP LLM Top 10", Section: "LLM03"}},
|
||||
},
|
||||
{
|
||||
Topic: "denial_of_service", Title: "LLM04: Model Denial of Service",
|
||||
Guidance: "Implement rate limiting (Shield). Set token limits per request. Monitor resource consumption. Use circuit breakers for runaway inference.",
|
||||
Severity: "medium",
|
||||
Standards: []Reference{{Source: "OWASP LLM Top 10", Section: "LLM04"}},
|
||||
},
|
||||
{
|
||||
Topic: "supply_chain", Title: "LLM05: Supply Chain Vulnerabilities",
|
||||
Guidance: "Pin model versions. Verify model checksums. Use isolated environments for model loading. Monitor for backdoors in fine-tuned models.",
|
||||
Severity: "high",
|
||||
Standards: []Reference{{Source: "OWASP LLM Top 10", Section: "LLM05"}},
|
||||
},
|
||||
{
|
||||
Topic: "sensitive_data", Title: "LLM06: Sensitive Information Disclosure",
|
||||
Guidance: "Use PII detection (sentinel-core privacy engines). Implement data masking. Never include secrets in prompts. Use Document Review Bridge for external LLM calls.",
|
||||
Severity: "critical",
|
||||
Standards: []Reference{{Source: "OWASP LLM Top 10", Section: "LLM06"}},
|
||||
},
|
||||
{
|
||||
Topic: "plugin_design", Title: "LLM07: Insecure Plugin Design",
|
||||
Guidance: "Use DIP Oracle for tool call validation. Implement per-tool permissions. Minimize plugin privileges. Validate all plugin inputs/outputs.",
|
||||
Severity: "high",
|
||||
Standards: []Reference{{Source: "OWASP LLM Top 10", Section: "LLM07"}},
|
||||
},
|
||||
{
|
||||
Topic: "excessive_agency", Title: "LLM08: Excessive Agency",
|
||||
Guidance: "Implement capability bounding (SDD-003 NHI). Use fail-safe closed permissions. Require human approval for critical actions. Log all agent decisions.",
|
||||
Severity: "critical",
|
||||
Standards: []Reference{{Source: "OWASP LLM Top 10", Section: "LLM08"}},
|
||||
},
|
||||
{
|
||||
Topic: "overreliance", Title: "LLM09: Overreliance",
|
||||
Guidance: "Never use LLM output as sole input for security decisions. Implement cross-validation with deterministic engines. Maintain human-in-the-loop for critical paths.",
|
||||
Severity: "medium",
|
||||
Standards: []Reference{{Source: "OWASP LLM Top 10", Section: "LLM09"}},
|
||||
},
|
||||
{
|
||||
Topic: "model_theft", Title: "LLM10: Model Theft",
|
||||
Guidance: "Implement access controls on model endpoints. Monitor for extraction attacks (many queries with crafted inputs). Rate limit API access. Use model watermarking.",
|
||||
Severity: "high",
|
||||
Standards: []Reference{{Source: "OWASP LLM Top 10", Section: "LLM10"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
107
internal/domain/guidance/guidance_test.go
Normal file
107
internal/domain/guidance/guidance_test.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package guidance
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultOWASPCount(t *testing.T) {
|
||||
entries := DefaultOWASPLLMTop10()
|
||||
if len(entries) != 10 {
|
||||
t.Errorf("expected 10 OWASP entries, got %d", len(entries))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreSearch(t *testing.T) {
|
||||
store := NewStore()
|
||||
for _, e := range DefaultOWASPLLMTop10() {
|
||||
store.AddEntry(e)
|
||||
}
|
||||
|
||||
// Search for injection
|
||||
results := store.Search("injection", "")
|
||||
if len(results) == 0 {
|
||||
t.Fatal("expected results for 'injection'")
|
||||
}
|
||||
if results[0].Topic != "injection" {
|
||||
t.Errorf("expected topic 'injection', got %q", results[0].Topic)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreSearchOWASP(t *testing.T) {
|
||||
store := NewStore()
|
||||
for _, e := range DefaultOWASPLLMTop10() {
|
||||
store.AddEntry(e)
|
||||
}
|
||||
|
||||
results := store.Search("sensitive_data", "")
|
||||
if len(results) == 0 {
|
||||
t.Fatal("expected results for 'sensitive_data'")
|
||||
}
|
||||
if results[0].Severity != "critical" {
|
||||
t.Errorf("expected critical severity, got %s", results[0].Severity)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreSearchUnknownTopic(t *testing.T) {
|
||||
store := NewStore()
|
||||
for _, e := range DefaultOWASPLLMTop10() {
|
||||
store.AddEntry(e)
|
||||
}
|
||||
|
||||
results := store.Search("quantum_computing_vulnerability", "")
|
||||
if len(results) != 0 {
|
||||
t.Errorf("expected 0 results for unknown topic, got %d", len(results))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreSearchWithLanguage(t *testing.T) {
|
||||
store := NewStore()
|
||||
store.AddEntry(GuidanceEntry{
|
||||
Topic: "sql_injection",
|
||||
Title: "SQL Injection Prevention",
|
||||
Guidance: "Use parameterized queries",
|
||||
Severity: "critical",
|
||||
Languages: []string{"python", "go", "java"},
|
||||
})
|
||||
store.AddEntry(GuidanceEntry{
|
||||
Topic: "sql_injection",
|
||||
Title: "SQL Injection (Rust)",
|
||||
Guidance: "Use sqlx with compile-time checked queries",
|
||||
Severity: "critical",
|
||||
Languages: []string{"rust"},
|
||||
})
|
||||
|
||||
pythonResults := store.Search("sql_injection", "python")
|
||||
if len(pythonResults) != 1 {
|
||||
t.Errorf("expected 1 python result, got %d", len(pythonResults))
|
||||
}
|
||||
|
||||
rustResults := store.Search("sql_injection", "rust")
|
||||
if len(rustResults) != 1 {
|
||||
t.Errorf("expected 1 rust result, got %d", len(rustResults))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreCount(t *testing.T) {
|
||||
store := NewStore()
|
||||
if store.Count() != 0 {
|
||||
t.Error("empty store should have 0 entries")
|
||||
}
|
||||
for _, e := range DefaultOWASPLLMTop10() {
|
||||
store.AddEntry(e)
|
||||
}
|
||||
if store.Count() != 10 {
|
||||
t.Errorf("expected 10, got %d", store.Count())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuidanceHasStandards(t *testing.T) {
|
||||
for _, entry := range DefaultOWASPLLMTop10() {
|
||||
if len(entry.Standards) == 0 {
|
||||
t.Errorf("entry %q missing standards references", entry.Topic)
|
||||
}
|
||||
if entry.Standards[0].Source != "OWASP LLM Top 10" {
|
||||
t.Errorf("entry %q: expected OWASP source, got %q", entry.Topic, entry.Standards[0].Source)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue