feat: add configurable tool call blocking and monitoring

This commit is contained in:
Ed1s0nZ
2026-09-08 09:44:32 +08:00
parent c70da22de7
commit 6ad9ea2d13
54 changed files with 4635 additions and 152 deletions
+10
View File
@@ -13,6 +13,7 @@ import (
"strings"
"cyberstrike-ai/internal/termout"
"cyberstrike-ai/internal/toolguard"
"gopkg.in/yaml.v3"
)
@@ -30,6 +31,7 @@ type Config struct {
Shodan SpaceSearchConfig `yaml:"shodan,omitempty" json:"shodan,omitempty"`
Agent AgentConfig `yaml:"agent"`
Hitl HitlConfig `yaml:"hitl,omitempty" json:"hitl,omitempty"`
ToolGuard *toolguard.Config `yaml:"tool_guard,omitempty" json:"tool_guard,omitempty"`
Security SecurityConfig `yaml:"security"`
Database DatabaseConfig `yaml:"database"`
Auth AuthConfig `yaml:"auth"`
@@ -1439,6 +1441,14 @@ func Load(path string) (*Config, error) {
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("解析配置文件失败: %w", err)
}
if cfg.ToolGuard != nil {
if err := validateToolGuardYAML(data); err != nil {
return nil, fmt.Errorf("调用拦截配置无效: %w", err)
}
}
if _, err := toolguard.Compile(cfg.EffectiveToolGuard()); err != nil {
return nil, fmt.Errorf("调用拦截配置无效: %w", err)
}
if cfg.Auth.SessionDurationHours <= 0 {
cfg.Auth.SessionDurationHours = 12
+39
View File
@@ -0,0 +1,39 @@
package config
import (
"fmt"
"cyberstrike-ai/internal/toolguard"
"gopkg.in/yaml.v3"
)
// EffectiveToolGuard enables the default government-domain protection for old
// configurations as well as new installs. An explicit config may disable it.
func (c *Config) EffectiveToolGuard() toolguard.Config {
if c.ToolGuard == nil {
return toolguard.DefaultConfig()
}
return *c.ToolGuard
}
// validateToolGuardYAML requires an explicit decision for both protection and
// its rules whenever a non-null section is supplied. Otherwise a typo or partial
// section could silently turn the enabled-by-default protection off. Pointer
// fields distinguish false/[] from omitted or null values, and the YAML decoder
// continues to support aliases and merged configuration mappings.
func validateToolGuardYAML(data []byte) error {
var document struct {
ToolGuard *struct {
Enabled *bool `yaml:"enabled"`
Rules *[]toolguard.Rule `yaml:"rules"`
} `yaml:"tool_guard"`
}
if err := yaml.Unmarshal(data, &document); err != nil {
return err
}
if section := document.ToolGuard; section != nil && (section.Enabled == nil || section.Rules == nil) {
return fmt.Errorf("tool_guard 必须明确提供 enabled 和 rules;清空规则请提供空数组")
}
return nil
}
+45
View File
@@ -0,0 +1,45 @@
package config
import (
"os"
"path/filepath"
"testing"
)
func TestLoadToolGuardDefaultsAndValidation(t *testing.T) {
for _, tc := range []struct {
name, yaml string
enabled, wantErr bool
}{
{"legacy config", "server: {port: 8080}\n", true, false},
{"null section", "tool_guard: null\n", true, false},
{"implicit null section", "tool_guard:\n", true, false},
{"explicit off", "tool_guard: {enabled: false, rules: []}\n", false, false},
{"explicit empty", "tool_guard: {enabled: true, rules: []}\n", true, false},
{"merged explicit config", "guard_defaults: &guard_defaults {enabled: false, rules: []}\ntool_guard: {<<: *guard_defaults}\n", false, false},
{"empty section", "tool_guard: {}\n", false, true},
{"missing enabled", "tool_guard: {rules: []}\n", false, true},
{"null enabled", "tool_guard: {enabled: null, rules: []}\n", false, true},
{"missing rules while off", "tool_guard: {enabled: false}\n", false, true},
{"missing rules while on", "tool_guard: {enabled: true}\n", false, true},
{"null rules", "tool_guard: {enabled: false, rules: null}\n", false, true},
{"mistyped enabled field", "tool_guard: {enable: false, rules: []}\n", false, true},
{"malformed rules while off", "tool_guard: {enabled: false, rules: disabled}\n", false, true},
{"malformed rule while off", "tool_guard: {enabled: false, rules: [invalid]}\n", false, true},
{"invalid pattern", "tool_guard:\n enabled: false\n rules:\n - {id: invalid, name: invalid, enabled: false, pattern: '['}\n", false, true},
} {
t.Run(tc.name, func(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.yaml")
if err := os.WriteFile(path, []byte(tc.yaml), 0600); err != nil {
t.Fatal(err)
}
cfg, err := Load(path)
if (err != nil) != tc.wantErr {
t.Fatalf("load error: %v", err)
}
if err == nil && cfg.EffectiveToolGuard().Enabled != tc.enabled {
t.Fatal("wrong effective enabled state")
}
})
}
}