feat: add human detection scoring and webhook notifications
Implement phase 2.1 (human detection) and 2.2 (notifications): - Detection scorer computes 0.0-1.0 human likelihood from keystroke timing variance, special key usage, typing speed, command diversity, and session duration - Webhook notifier sends JSON POST to configured endpoints with deduplication, custom headers, and event filtering - RecordingChannel gains an event callback for feeding keystrokes to the scorer without coupling shell and detection packages - Server wires scorer into session lifecycle with periodic updates and threshold-based notification triggers - Web UI shows human score in session tables with highlighting - New config sections: [detection] and [[notify.webhooks]] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,13 +9,15 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
SSH SSHConfig `toml:"ssh"`
|
||||
Auth AuthConfig `toml:"auth"`
|
||||
Storage StorageConfig `toml:"storage"`
|
||||
Shell ShellConfig `toml:"shell"`
|
||||
Web WebConfig `toml:"web"`
|
||||
LogLevel string `toml:"log_level"`
|
||||
LogFormat string `toml:"log_format"` // "text" (default) or "json"
|
||||
SSH SSHConfig `toml:"ssh"`
|
||||
Auth AuthConfig `toml:"auth"`
|
||||
Storage StorageConfig `toml:"storage"`
|
||||
Shell ShellConfig `toml:"shell"`
|
||||
Web WebConfig `toml:"web"`
|
||||
Detection DetectionConfig `toml:"detection"`
|
||||
Notify NotifyConfig `toml:"notify"`
|
||||
LogLevel string `toml:"log_level"`
|
||||
LogFormat string `toml:"log_format"` // "text" (default) or "json"
|
||||
}
|
||||
|
||||
type WebConfig struct {
|
||||
@@ -59,6 +61,25 @@ type Credential struct {
|
||||
Password string `toml:"password"`
|
||||
}
|
||||
|
||||
type DetectionConfig struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
Threshold float64 `toml:"threshold"`
|
||||
UpdateInterval string `toml:"update_interval"`
|
||||
|
||||
// Parsed duration, not from TOML directly.
|
||||
UpdateIntervalDuration time.Duration `toml:"-"`
|
||||
}
|
||||
|
||||
type NotifyConfig struct {
|
||||
Webhooks []WebhookNotifyConfig `toml:"webhooks"`
|
||||
}
|
||||
|
||||
type WebhookNotifyConfig struct {
|
||||
URL string `toml:"url"`
|
||||
Headers map[string]string `toml:"headers"`
|
||||
Events []string `toml:"events"` // empty = all events
|
||||
}
|
||||
|
||||
func Load(path string) (*Config, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -127,6 +148,12 @@ func applyDefaults(cfg *Config) {
|
||||
if cfg.Shell.Banner == "" {
|
||||
cfg.Shell.Banner = "Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-89-generic x86_64)\r\n\r\n"
|
||||
}
|
||||
if cfg.Detection.Threshold == 0 {
|
||||
cfg.Detection.Threshold = 0.6
|
||||
}
|
||||
if cfg.Detection.UpdateInterval == "" {
|
||||
cfg.Detection.UpdateInterval = "5s"
|
||||
}
|
||||
}
|
||||
|
||||
// knownShellKeys are top-level keys in [shell] that are not per-shell sub-tables.
|
||||
@@ -189,5 +216,33 @@ func validate(cfg *Config) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate detection config.
|
||||
if cfg.Detection.Enabled {
|
||||
if cfg.Detection.Threshold < 0 || cfg.Detection.Threshold > 1 {
|
||||
return fmt.Errorf("detection.threshold must be between 0 and 1, got %f", cfg.Detection.Threshold)
|
||||
}
|
||||
ui, err := time.ParseDuration(cfg.Detection.UpdateInterval)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid detection.update_interval %q: %w", cfg.Detection.UpdateInterval, err)
|
||||
}
|
||||
if ui <= 0 {
|
||||
return fmt.Errorf("detection.update_interval must be positive, got %s", ui)
|
||||
}
|
||||
cfg.Detection.UpdateIntervalDuration = ui
|
||||
}
|
||||
|
||||
// Validate notify config.
|
||||
knownEvents := map[string]bool{"human_detected": true, "session_started": true}
|
||||
for i, wh := range cfg.Notify.Webhooks {
|
||||
if wh.URL == "" {
|
||||
return fmt.Errorf("notify.webhooks[%d]: url must not be empty", i)
|
||||
}
|
||||
for j, ev := range wh.Events {
|
||||
if !knownEvents[ev] {
|
||||
return fmt.Errorf("notify.webhooks[%d].events[%d]: unknown event %q", i, j, ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user