Skip to content
Plano de Implementação - Guardian Security Engine

Plano de Implementação - Guardian Security Engine

PT | EN

O Guardian é o motor de governança do Vectora, responsável por interceptar todas as chamadas de ferramentas e garantir que elas cumpram as políticas de segurança antes da execução. Na transição para Golang, o Guardian opera como código compilado e imutável.

Arquitetura de Segurança

    graph LR
    A[Agent Principal] -- Tool Call --> G{Guardian Go}
    G -- "1. Pattern Matching" --> B[Blocklist Compilada]
    G -- "2. Schema Validation" --> C[Strongly Typed Structs]
    G -- "3. Path Sanitization" --> D[Trust Folder Resolver]
    G -- "4. Output Masking" --> E[Secret Redaction]
    G -- Aprovado --> F[Harness Runtime]
    G -- Rejeitado --> G2[Audit Log + Error]
  

Fases de Implementação

Fase 1: Blocklist Compilada & Pattern Matching

Duração: 1 semana

Deliverables:

  • Enum de padrões sensíveis compilados
  • Regex patterns para detecção de segredos
  • Audit logging para tentativas bloqueadas

Código de Exemplo - Blocklist Compilada:

// pkg/guardian/blocklist.go
package guardian

import (
    "regexp"
    "sync"
)

// BlockPattern define um padrão que deve ser bloqueado
type BlockPattern struct {
    ID string
    Pattern *regexp.Regexp
    Description string
    Severity BlockSeverity
}

type BlockSeverity string

const (
    SeverityCritical BlockSeverity = "critical"
    SeverityHigh BlockSeverity = "high"
    SeverityMedium BlockSeverity = "medium"
)

// CompiledBlocklist contém padrões que nunca podem ser modificados em runtime
type CompiledBlocklist struct {
    mu sync.RWMutex
    patterns map[string]*BlockPattern
}

func NewCompiledBlocklist() *CompiledBlocklist {
    cb := &CompiledBlocklist{
        patterns: make(map[string]*BlockPattern),
    }

    // Padrões compilados na inicialização (imutáveis)
    cb.registerPattern("env_files", `\.env(?:\.[a-z]+)?$`, "Environment files", SeverityCritical)
    cb.registerPattern("private_keys", `\.(pem|key|p12|jks)$`, "Private keys", SeverityCritical)
    cb.registerPattern("credentials", `(?i)(password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key)`, "Credential patterns", SeverityHigh)
    cb.registerPattern("system_files", `^(/etc/passwd|/etc/shadow|/etc/sudoers|C:\\Windows\\System32)`, "System files", SeverityCritical)
    cb.registerPattern("git_config", `\.git(?:/|\\)config`, "Git configuration", SeverityHigh)
    cb.registerPattern("aws_credentials", `(?i)(aws_access_key|aws_secret|~/.aws)`, "AWS credentials", SeverityCritical)
    cb.registerPattern("database_strings", `(?i)(connection[_-]?string|mongodb[_+]?uri|sql[_+]?password)`, "Database credentials", SeverityHigh)

    return cb
}

func (cb *CompiledBlocklist) registerPattern(id, pattern, desc string, severity BlockSeverity) {
    re := regexp.MustCompile(pattern)
    cb.patterns[id] = &BlockPattern{
        ID: id,
        Pattern: re,
        Description: desc,
        Severity: severity,
    }
}

func (cb *CompiledBlocklist) MatchesPattern(input string) (*BlockPattern, bool) {
    cb.mu.RLock()
    defer cb.mu.RUnlock()

    for _, pattern := range cb.patterns {
        if pattern.Pattern.MatchString(input) {
            return pattern, true
        }
    }

    return nil, false
}

func (cb *CompiledBlocklist) AllPatterns() []*BlockPattern {
    cb.mu.RLock()
    defer cb.mu.RUnlock()

    patterns := make([]*BlockPattern, 0, len(cb.patterns))
    for _, p := range cb.patterns {
        patterns = append(patterns, p)
    }
    return patterns
}

Fase 2: Trust Folder & Path Validation

Duração: 1 semana

Deliverables:

  • Struct TrustFolder com validação de paths
  • Resolução atômica de symlinks
  • Testes de bypass (jailbreak attempts)

Código de Exemplo - Trust Folder:

// pkg/guardian/trust_folder.go
package guardian

import (
    "fmt"
    "os"
    "path/filepath"
    "strings"
)

type TrustFolder struct {
    rootPath string
    absPath string
}

func NewTrustFolder(rootPath string) (*TrustFolder, error) {
    // Resolver para caminho absoluto
    absPath, err := filepath.Abs(rootPath)
    if err != nil {
        return nil, fmt.Errorf("invalid root path: %w", err)
    }

    // Resolver symlinks
    realPath, err := filepath.EvalSymlinks(absPath)
    if err != nil {
        return nil, fmt.Errorf("failed to resolve symlinks: %w", err)
    }

    // Validar que o diretório existe
    info, err := os.Stat(realPath)
    if err != nil {
        return nil, fmt.Errorf("trust folder does not exist: %w", err)
    }
    if !info.IsDir() {
        return nil, fmt.Errorf("trust folder path is not a directory")
    }

    return &TrustFolder{
        rootPath: rootPath,
        absPath: realPath,
    }, nil
}

func (tf *TrustFolder) IsPathAllowed(requestPath string) bool {
    // Resolver o caminho solicitado
    absRequestPath, err := filepath.Abs(requestPath)
    if err != nil {
        return false
    }

    // Resolver symlinks no caminho solicitado
    realPath, err := filepath.EvalSymlinks(absRequestPath)
    if err != nil {
        // Se não conseguir resolver symlinks, usar o caminho absoluto
        realPath = absRequestPath
    }

    // Verificar se está dentro do trust folder
    rel, err := filepath.Rel(tf.absPath, realPath)
    if err != nil {
        return false
    }

    // Bloquecar tentativas de "../" ou caminhos que escapam
    if strings.HasPrefix(rel, "..") || strings.HasPrefix(rel, string(filepath.Separator)+"..") {
        return false
    }

    // Não permitir caminhos absolutos fora do trust folder
    if filepath.IsAbs(rel) {
        return false
    }

    return true
}

func (tf *TrustFolder) ResolvePath(requestPath string) (string, error) {
    if !tf.IsPathAllowed(requestPath) {
        return "", fmt.Errorf("path is outside trust folder: %s", requestPath)
    }

    absPath, err := filepath.Abs(requestPath)
    if err != nil {
        return "", err
    }

    return absPath, nil
}

Fase 3: Schema Validation (Typed Structs)

Duração: 1 semana

Deliverables:

  • Validação de structs com tags
  • Custom validators para tipos complexos
  • Testes de mutação

Código de Exemplo - Schema Validation:

// pkg/guardian/validation.go
package guardian

import (
    "fmt"
    "regexp"
)

// ToolCallSchema define as regras de validação para uma tool call
type ToolCallSchema struct {
    ToolName string `validate:"required,min=1,max=100"`
    Arguments map[string]interface{} `validate:"required"`
}

type ToolCallValidator struct {
    blocklist *CompiledBlocklist
    trustFolder *TrustFolder
}

func NewToolCallValidator(blocklist *CompiledBlocklist, trustFolder *TrustFolder) *ToolCallValidator {
    return &ToolCallValidator{
        blocklist: blocklist,
        trustFolder: trustFolder,
    }
}

func (v *ToolCallValidator) ValidateToolCall(toolName string, args map[string]interface{}) error {
    // 1. Validar nome da ferramenta
    if toolName == "" {
        return fmt.Errorf("tool name cannot be empty")
    }

    if len(toolName) > 100 {
        return fmt.Errorf("tool name exceeds max length of 100")
    }

    // 2. Validar argumentos contra blocklist
    for argName, argValue := range args {
        if err := v.validateArgument(argName, argValue); err != nil {
            return err
        }
    }

    return nil
}

func (v *ToolCallValidator) validateArgument(name string, value interface{}) error {
    switch val := value.(type) {
    case string:
        return v.validateStringArgument(name, val)
    case []interface{}:
        for _, item := range val {
            if err := v.validateArgument(name, item); err != nil {
                return err
            }
        }
    case map[string]interface{}:
        for k, v := range val {
            if err := v.validateArgument(k, v); err != nil {
                return err
            }
        }
    }

    return nil
}

func (v *ToolCallValidator) validateStringArgument(name, value string) error {
    // Verificar contra blocklist
    if pattern, matched := v.blocklist.MatchesPattern(value); matched {
        return fmt.Errorf("blocked pattern detected in argument '%s': %s", name, pattern.Description)
    }

    // Se é um caminho, validar contra trust folder
    if name == "path" || name == "file" || name == "directory" {
        if !v.trustFolder.IsPathAllowed(value) {
            return fmt.Errorf("path is outside trust folder: %s", value)
        }
    }

    return nil
}

Fase 4: Output Sanitization & Secret Redaction

Duração: 1 semana

Deliverables:

  • Detector de segredos em output
  • Redação automática
  • Heurísticas para tokens/keys

Código de Exemplo - Output Sanitizer:

// pkg/guardian/sanitizer.go
package guardian

import (
    "fmt"
    "regexp"
    "strings"
)

type OutputSanitizer struct {
    secretPatterns []*SecretPattern
}

type SecretPattern struct {
    Name string
    Pattern *regexp.Regexp
    Replace string
}

func NewOutputSanitizer() *OutputSanitizer {
    return &OutputSanitizer{
        secretPatterns: []*SecretPattern{
            {
                Name: "api_key",
                Pattern: regexp.MustCompile(`(api[_-]?key)[:\s=]+([a-zA-Z0-9\-_]{32,})`),
                Replace: "$1: [REDACTED_KEY]",
            },
            {
                Name: "password",
                Pattern: regexp.MustCompile(`(?i)(password)[:\s=]+([^\s,\n]+)`),
                Replace: "$1: [REDACTED_PASSWORD]",
            },
            {
                Name: "token",
                Pattern: regexp.MustCompile(`(token|authorization)[:\s=]+([a-zA-Z0-9\-_\.]+)`),
                Replace: "$1: [REDACTED_TOKEN]",
            },
            {
                Name: "bearer_token",
                Pattern: regexp.MustCompile(`(?i)(bearer\s+)([a-zA-Z0-9\-_\.]+)`),
                Replace: "$1[REDACTED_TOKEN]",
            },
            {
                Name: "mongodb_uri",
                Pattern: regexp.MustCompile(`mongodb\+srv://[^:\s]+:([^@]+)@`),
                Replace: "mongodb+srv://[REDACTED_USER]:[REDACTED_PASSWORD]@",
            },
            {
                Name: "aws_access_key",
                Pattern: regexp.MustCompile(`(AKIA[0-9A-Z]{16})`),
                Replace: "[REDACTED_AWS_KEY]",
            },
            {
                Name: "private_key",
                Pattern: regexp.MustCompile(`-----BEGIN (PRIVATE|RSA|EC) KEY-----[\s\S]+?-----END \1 KEY-----`),
                Replace: "[REDACTED_PRIVATE_KEY]",
            },
        },
    }
}

func (os *OutputSanitizer) Sanitize(output string) string {
    sanitized := output

    for _, pattern := range os.secretPatterns {
        sanitized = pattern.Pattern.ReplaceAllString(sanitized, pattern.Replace)
    }

    // Sanitização adicional: remover linhas que parecem ser JSON com secrets
    sanitized = os.sanitizeJSON(sanitized)

    return sanitized
}

func (os *OutputSanitizer) sanitizeJSON(output string) string {
    // Padrão simples para detectar potenciais JSON malformado com segredos
    lines := strings.Split(output, "\n")
    var result []string

    for _, line := range lines {
        // Se contém padrões suspeitos, mascarar
        if strings.Contains(line, "secret") || strings.Contains(line, "key") || strings.Contains(line, "password") {
            // Aplicar sanitização linha a linha
            line = os.sanitizeLine(line)
        }
        result = append(result, line)
    }

    return strings.Join(result, "\n")
}

func (os *OutputSanitizer) sanitizeLine(line string) string {
    sanitized := line

    for _, pattern := range os.secretPatterns {
        sanitized = pattern.Pattern.ReplaceAllString(sanitized, pattern.Replace)
    }

    return sanitized
}

Fase 5: Audit Logging & Metrics

Duração: 1 semana

Deliverables:

  • Log estruturado de todas as validações
  • Métricas de tentativas bloqueadas
  • Alertas para padrões de ataque

Código de Exemplo - Audit Logger:

// pkg/guardian/audit.go
package guardian

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
    "time"
)

type AuditLog struct {
    Timestamp time.Time `json:"timestamp"`
    EventType string `json:"event_type"`
    ToolName string `json:"tool_name,omitempty"`
    Status string `json:"status"` // approved, rejected, error
    Reason string `json:"reason,omitempty"`
    PatternID string `json:"pattern_id,omitempty"`
    Severity string `json:"severity,omitempty"`
    UserID string `json:"user_id,omitempty"`
    SessionID string `json:"session_id,omitempty"`
}

type AuditLogger struct {
    file *os.File
    mu sync.Mutex
}

func NewAuditLogger(logPath string) (*AuditLogger, error) {
    file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return nil, fmt.Errorf("failed to open audit log: %w", err)
    }

    return &AuditLogger{
        file: file,
    }, nil
}

func (al *AuditLogger) Log(event *AuditLog) error {
    al.mu.Lock()
    defer al.mu.Unlock()

    event.Timestamp = time.Now()

    data, err := json.Marshal(event)
    if err != nil {
        return fmt.Errorf("failed to marshal audit log: %w", err)
    }

    _, err = al.file.WriteString(string(data) + "\n")
    return err
}

func (al *AuditLogger) Close() error {
    return al.file.Close()
}

Fase 6: Testes de Segurança

Duração: 1 semana

Deliverables:

  • Testes de bypass (jailbreak attempts)
  • Fuzzing de inputs
  • Regression tests para vulnerabilidades conhecidas

Código de Exemplo - Security Tests:

// pkg/guardian/security_test.go
package guardian

import (
    "testing"
)

func TestBlocklistMatching(t *testing.T) {
    blocklist := NewCompiledBlocklist()

    tests := []struct {
        input string
        blocked bool
    }{
        {".env", true},
        {".env.local", true},
        {"/etc/passwd", true},
        {"~/.aws/credentials", true},
        {"config.yaml", false},
        {"src/main.go", false},
    }

    for _, tt := range tests {
        _, matched := blocklist.MatchesPattern(tt.input)
        if matched != tt.blocked {
            t.Errorf("MatchesPattern(%q) = %v, want %v", tt.input, matched, tt.blocked)
        }
    }
}

func TestTrustFolderBypass(t *testing.T) {
    trustFolder, err := NewTrustFolder("/home/user/project")
    if err != nil {
        t.Fatalf("NewTrustFolder failed: %v", err)
    }

    tests := []struct {
        path string
        allowed bool
    }{
        {"/home/user/project/src/main.go", true},
        {"/home/user/project/../../../etc/passwd", false},
        {"/etc/passwd", false},
        {"/home/user/secret.env", false},
    }

    for _, tt := range tests {
        allowed := trustFolder.IsPathAllowed(tt.path)
        if allowed != tt.allowed {
            t.Errorf("IsPathAllowed(%q) = %v, want %v", tt.path, allowed, tt.allowed)
        }
    }
}

func TestOutputSanitization(t *testing.T) {
    sanitizer := NewOutputSanitizer()

    tests := []struct {
        input string
        shouldHave string
        shouldNotHave string
    }{
        {
            input: `api_key=sk-12345678901234567890`,
            shouldHave: "[REDACTED_KEY]",
            shouldNotHave: "sk-12345678901234567890",
        },
        {
            input: `password: mySecretPassword123`,
            shouldHave: "[REDACTED_PASSWORD]",
            shouldNotHave: "mySecretPassword123",
        },
    }

    for _, tt := range tests {
        sanitized := sanitizer.Sanitize(tt.input)

        if tt.shouldHave != "" && !strings.Contains(sanitized, tt.shouldHave) {
            t.Errorf("Sanitize(%q) should contain %q", tt.input, tt.shouldHave)
        }

        if tt.shouldNotHave != "" && strings.Contains(sanitized, tt.shouldNotHave) {
            t.Errorf("Sanitize(%q) should not contain %q", tt.input, tt.shouldNotHave)
        }
    }
}

Garantias de Segurança

ProteçãoMecanismoGarantia
Immutable BlocklistCompilada no binárioImpossível bypasser via arquivo de config
Path TraversalResolução atômica de symlinksNão escapa da trust folder
Secret LeakageSanitização em outputTokens/keys nunca atingem o LLM
Runtime ValidationGo type system + custom validatorsSchemas rejeitam inputs inválidos na desserialização
Audit TrailLogging estruturado de todas as validaçõesRastreabilidade completa de decisões

Métricas de Sucesso

  • 100% de cobertura de testes para Guardian
  • Zero falsos negativos em testes de bypass
  • Latência de validação <10ms por tool call
  • Audit logs com <100 bytes por entrada
  • Todos os padrões de bloqueio documentados

Parte do ecossistema Vectora · Engenharia Interna