| package report |
|
|
| import ( |
| "os" |
| "path/filepath" |
| "testing" |
|
|
| "github.com/pinchtab/pinchtab/internal/config" |
| ) |
|
|
| func TestAssessSecurityWarnings(t *testing.T) { |
| t.Run("safe local defaults stay quiet", func(t *testing.T) { |
| cfg := &config.RuntimeConfig{ |
| Bind: "127.0.0.1", |
| Token: "secret", |
| AttachAllowHosts: []string{"127.0.0.1", "localhost", "::1"}, |
| AttachAllowSchemes: []string{"ws", "wss"}, |
| IDPI: config.IDPIConfig{ |
| Enabled: true, |
| AllowedDomains: []string{"127.0.0.1", "localhost", "::1"}, |
| StrictMode: true, |
| ScanContent: true, |
| WrapContent: true, |
| }, |
| } |
|
|
| warnings := assessSecurityWarnings(cfg) |
| if len(warnings) != 0 { |
| t.Fatalf("expected no warnings, got %+v", warnings) |
| } |
| }) |
|
|
| t.Run("website whitelist missing and other security gaps are flagged", func(t *testing.T) { |
| cfg := &config.RuntimeConfig{ |
| Bind: "0.0.0.0", |
| Token: "", |
| AllowEvaluate: true, |
| AllowDownload: true, |
| AttachEnabled: true, |
| AttachAllowHosts: []string{"localhost", "chrome.internal"}, |
| IDPI: config.IDPIConfig{ |
| Enabled: true, |
| }, |
| } |
|
|
| warnings := assessSecurityWarnings(cfg) |
| ids := make(map[string]bool, len(warnings)) |
| for _, warning := range warnings { |
| ids[warning.ID] = true |
| } |
|
|
| for _, expected := range []string{ |
| "sensitive_endpoints_enabled", |
| "api_auth_disabled", |
| "sensitive_endpoints_without_auth", |
| "non_loopback_bind", |
| "idpi_whitelist_not_set", |
| "idpi_warn_mode", |
| "idpi_content_protection_disabled", |
| "attach_external_hosts", |
| } { |
| if !ids[expected] { |
| t.Fatalf("expected warning %q, got %+v", expected, warnings) |
| } |
| } |
| }) |
|
|
| t.Run("wildcard whitelist is warned", func(t *testing.T) { |
| cfg := &config.RuntimeConfig{ |
| Bind: "127.0.0.1", |
| Token: "secret", |
| IDPI: config.IDPIConfig{ |
| Enabled: true, |
| AllowedDomains: []string{"*"}, |
| StrictMode: true, |
| ScanContent: true, |
| }, |
| } |
|
|
| warnings := assessSecurityWarnings(cfg) |
| ids := make(map[string]bool, len(warnings)) |
| for _, warning := range warnings { |
| ids[warning.ID] = true |
| } |
|
|
| if !ids["idpi_whitelist_allows_all"] { |
| t.Fatalf("expected wildcard whitelist warning, got %+v", warnings) |
| } |
| }) |
|
|
| t.Run("disabled IDPI is warned", func(t *testing.T) { |
| cfg := &config.RuntimeConfig{ |
| Bind: "127.0.0.1", |
| Token: "secret", |
| AttachAllowHosts: []string{"127.0.0.1", "localhost", "::1"}, |
| AttachAllowSchemes: []string{"ws", "wss"}, |
| } |
|
|
| warnings := assessSecurityWarnings(cfg) |
| ids := make(map[string]bool, len(warnings)) |
| for _, warning := range warnings { |
| ids[warning.ID] = true |
| } |
|
|
| if !ids["idpi_disabled"] { |
| t.Fatalf("expected idpi_disabled warning, got %+v", warnings) |
| } |
| }) |
| } |
|
|
| func TestAssessSecurityPosture(t *testing.T) { |
| t.Run("fully locked local config scores all defaults", func(t *testing.T) { |
| cfg := &config.RuntimeConfig{ |
| Bind: "127.0.0.1", |
| Token: "secret", |
| AttachAllowHosts: []string{"127.0.0.1", "localhost", "::1"}, |
| AttachAllowSchemes: []string{"ws", "wss"}, |
| IDPI: config.IDPIConfig{ |
| Enabled: true, |
| AllowedDomains: []string{"127.0.0.1", "localhost", "::1"}, |
| StrictMode: true, |
| ScanContent: true, |
| WrapContent: true, |
| }, |
| } |
|
|
| posture := assessSecurityPosture(cfg) |
| if posture.Passed != posture.Total { |
| t.Fatalf("expected all checks to pass, got %d/%d", posture.Passed, posture.Total) |
| } |
| if posture.Level != "LOCKED" { |
| t.Fatalf("expected LOCKED posture, got %q", posture.Level) |
| } |
| }) |
|
|
| t.Run("exposed config drops posture score", func(t *testing.T) { |
| cfg := &config.RuntimeConfig{ |
| Bind: "0.0.0.0", |
| AllowEvaluate: true, |
| AllowDownload: true, |
| AttachEnabled: true, |
| AttachAllowHosts: []string{"chrome.internal"}, |
| IDPI: config.IDPIConfig{ |
| Enabled: true, |
| }, |
| } |
|
|
| posture := assessSecurityPosture(cfg) |
| if posture.Passed >= 3 { |
| t.Fatalf("expected exposed posture below 3 passed checks, got %d/%d", posture.Passed, posture.Total) |
| } |
| if posture.Level != "EXPOSED" { |
| t.Fatalf("expected EXPOSED posture, got %q", posture.Level) |
| } |
| }) |
| } |
|
|
| func TestApplyRecommendedSecurityDefaults(t *testing.T) { |
| allowEvaluate := true |
| attachEnabled := true |
| fc := &config.FileConfig{ |
| Server: config.ServerConfig{ |
| Port: "9999", |
| Bind: "0.0.0.0", |
| Token: "secret", |
| }, |
| Security: config.SecurityConfig{ |
| AllowEvaluate: &allowEvaluate, |
| Attach: config.AttachConfig{ |
| Enabled: &attachEnabled, |
| AllowHosts: []string{"chrome.internal"}, |
| }, |
| IDPI: config.IDPIConfig{ |
| Enabled: false, |
| }, |
| }, |
| } |
|
|
| applyRecommendedSecurityDefaults(fc) |
|
|
| if fc.Server.Port != "9999" { |
| t.Fatalf("expected port to be preserved, got %q", fc.Server.Port) |
| } |
| if fc.Server.Token != "secret" { |
| t.Fatalf("expected token to be preserved, got %q", fc.Server.Token) |
| } |
| if fc.Server.Bind != "127.0.0.1" { |
| t.Fatalf("expected bind to reset to loopback, got %q", fc.Server.Bind) |
| } |
| if fc.Security.AllowEvaluate == nil || *fc.Security.AllowEvaluate { |
| t.Fatalf("expected allowEvaluate to reset to false, got %+v", fc.Security.AllowEvaluate) |
| } |
| if !fc.Security.IDPI.Enabled { |
| t.Fatalf("expected idpi to be enabled") |
| } |
| } |
|
|
| func TestApplyRecommendedSecurityDefaults_GeneratesTokenWhenMissing(t *testing.T) { |
| fc := &config.FileConfig{} |
|
|
| applyRecommendedSecurityDefaults(fc) |
|
|
| if fc.Server.Token == "" { |
| t.Fatalf("expected generated token, got empty") |
| } |
| } |
|
|
| func TestRestoreSecurityDefaults(t *testing.T) { |
| tmpDir := t.TempDir() |
| configPath := filepath.Join(tmpDir, "config.json") |
| t.Setenv("PINCHTAB_CONFIG", configPath) |
|
|
| allowEvaluate := true |
| attachEnabled := true |
| fc := &config.FileConfig{ |
| Server: config.ServerConfig{ |
| Port: "9999", |
| Bind: "0.0.0.0", |
| Token: "secret", |
| }, |
| Security: config.SecurityConfig{ |
| AllowEvaluate: &allowEvaluate, |
| Attach: config.AttachConfig{ |
| Enabled: &attachEnabled, |
| AllowHosts: []string{"chrome.internal"}, |
| }, |
| IDPI: config.IDPIConfig{ |
| Enabled: false, |
| }, |
| }, |
| } |
| if err := config.SaveFileConfig(fc, configPath); err != nil { |
| t.Fatalf("SaveFileConfig() error = %v", err) |
| } |
|
|
| gotPath, changed, err := restoreSecurityDefaults() |
| if err != nil { |
| t.Fatalf("restoreSecurityDefaults() error = %v", err) |
| } |
| if gotPath != configPath { |
| t.Fatalf("restoreSecurityDefaults() path = %q, want %q", gotPath, configPath) |
| } |
| if !changed { |
| t.Fatalf("restoreSecurityDefaults() changed = false, want true") |
| } |
|
|
| saved, err := os.ReadFile(configPath) |
| if err != nil { |
| t.Fatalf("ReadFile() error = %v", err) |
| } |
| loaded := &config.FileConfig{} |
| if err := config.PatchConfigJSON(loaded, string(saved)); err != nil { |
| t.Fatalf("PatchConfigJSON() error = %v", err) |
| } |
| if loaded.Server.Port != "9999" { |
| t.Fatalf("expected port to be preserved, got %q", loaded.Server.Port) |
| } |
| if loaded.Server.Token != "secret" { |
| t.Fatalf("expected token to be preserved, got %q", loaded.Server.Token) |
| } |
| if loaded.Server.Bind != "127.0.0.1" { |
| t.Fatalf("expected bind to be restored, got %q", loaded.Server.Bind) |
| } |
| if !loaded.Security.IDPI.Enabled { |
| t.Fatalf("expected idpi to be enabled after restore") |
| } |
| } |
|
|
| func TestRestoreSecurityDefaults_TokenOnlyChangeIsSaved(t *testing.T) { |
| tmpDir := t.TempDir() |
| configPath := filepath.Join(tmpDir, "config.json") |
| t.Setenv("PINCHTAB_CONFIG", configPath) |
|
|
| fc := config.DefaultFileConfig() |
| fc.Server.Token = "" |
| if err := config.SaveFileConfig(&fc, configPath); err != nil { |
| t.Fatalf("SaveFileConfig() error = %v", err) |
| } |
|
|
| _, changed, err := restoreSecurityDefaults() |
| if err != nil { |
| t.Fatalf("restoreSecurityDefaults() error = %v", err) |
| } |
| if !changed { |
| t.Fatalf("restoreSecurityDefaults() changed = false, want true") |
| } |
|
|
| loaded, _, err := config.LoadFileConfig() |
| if err != nil { |
| t.Fatalf("LoadFileConfig() error = %v", err) |
| } |
| if loaded.Server.Token == "" { |
| t.Fatalf("expected generated token to be persisted") |
| } |
| } |
|
|