From 15c5f3503a72b71b8c759fe32d9366c9297ca7e8 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Wed, 8 Oct 2025 09:10:44 +0530 Subject: [PATCH 1/3] Update root.go --- cmd/cli/root.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/cli/root.go b/cmd/cli/root.go index 9b61366..2818023 100644 --- a/cmd/cli/root.go +++ b/cmd/cli/root.go @@ -1,6 +1,3 @@ -/* -Copyright © 2025 NAME HERE -*/ package cmd import ( From 2244fb23715e26b7749920804d9506643bad9931 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Thu, 9 Oct 2025 22:45:14 +0530 Subject: [PATCH 2/3] Add detection for: Azure credentials Google Cloud credentials SSH keys in different formats API keys from more providers (Stripe, Twilio, etc.) Connection strings for various databases --- .gitignore | 1 + internal/scrubber/scrubber.go | 193 ++++++++++++++++++++++++++++- internal/scrubber/scrubber_test.go | 2 +- 3 files changed, 192 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 447b5b8..0ef7b96 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ HACKTOBERFEST_SETUP.md PR_DESCRIPTION.md commit test.txt +AGENTS.md diff --git a/internal/scrubber/scrubber.go b/internal/scrubber/scrubber.go index 69edb29..70f95b9 100644 --- a/internal/scrubber/scrubber.go +++ b/internal/scrubber/scrubber.go @@ -44,6 +44,50 @@ var ( Redact: "${1}=\"[REDACTED_AWS_SECRET]\"", }, + // Azure Credentials + { + Name: "Azure Client Secret", + Pattern: regexp.MustCompile(`(?i)(azure[_-]?client[_-]?secret|AZURE_CLIENT_SECRET)\s*[=:]\s*["\']?([a-zA-Z0-9_\-\.~]{20,})["\']?`), + Redact: "${1}=\"[REDACTED_AZURE_CLIENT_SECRET]\"", + }, + { + Name: "Azure Subscription Key", + Pattern: regexp.MustCompile(`(?i)(azure[_-]?subscription[_-]?key|AZURE_SUBSCRIPTION_KEY)\s*[=:]\s*["\']?([a-zA-Z0-9]{32})["\']?`), + Redact: "${1}=\"[REDACTED_AZURE_SUBSCRIPTION_KEY]\"", + }, + { + Name: "Azure Storage Key", + Pattern: regexp.MustCompile(`(?i)(azure[_-]?storage[_-]?key|AZURE_STORAGE_KEY|AccountKey)\s*[=:]\s*["\']?([a-zA-Z0-9/+=]{88})["\']?`), + Redact: "${1}=\"[REDACTED_AZURE_STORAGE_KEY]\"", + }, + { + Name: "Azure Service Principal", + Pattern: regexp.MustCompile(`(?i)(azure[_-]?service[_-]?principal|AZURE_SERVICE_PRINCIPAL)\s*[=:]\s*["\']?([a-f0-9-]{36})["\']?`), + Redact: "${1}=\"[REDACTED_AZURE_SERVICE_PRINCIPAL]\"", + }, + + // Google Cloud Credentials + { + Name: "Google Cloud Service Account Key", + Pattern: regexp.MustCompile(`(?i)(gcp[_-]?service[_-]?account[_-]?key|GOOGLE_APPLICATION_CREDENTIALS)\s*[=:]\s*["\']?([a-zA-Z0-9_\-\.@]+\.json)["\']?`), + Redact: "${1}=\"[REDACTED_GCP_SA_KEY]\"", + }, + { + Name: "Google Cloud API Key", + Pattern: regexp.MustCompile(`(?i)(gcp[_-]?api[_-]?key|GOOGLE_CLOUD_API_KEY)\s*[=:]\s*["\']?([a-zA-Z0-9_\-]{39})["\']?`), + Redact: "${1}=\"[REDACTED_GCP_API_KEY]\"", + }, + { + Name: "Google Cloud OAuth Client", + Pattern: regexp.MustCompile(`(?i)(gcp[_-]?oauth[_-]?client[_-]?secret|GOOGLE_OAUTH_CLIENT_SECRET)\s*[=:]\s*["\']?([a-zA-Z0-9_\-]{24})["\']?`), + Redact: "${1}=\"[REDACTED_GCP_OAUTH_SECRET]\"", + }, + { + Name: "Google Cloud JSON Credentials", + Pattern: regexp.MustCompile(`(?s)"type":\s*"service_account".*?"private_key":\s*"-----BEGIN PRIVATE KEY-----.*?-----END PRIVATE KEY-----`), + Redact: "\"type\": \"service_account\",\n\"private_key\": \"[REDACTED_GCP_PRIVATE_KEY]\"", + }, + // Database Credentials { Name: "Database URL with Password", @@ -88,12 +132,155 @@ var ( Redact: "${1}=\"[REDACTED_SLACK_TOKEN]\"", }, - // Private Keys + // Payment & Communication APIs + { + Name: "Stripe API Key", + Pattern: regexp.MustCompile(`(?i)(stripe[_-]?api[_-]?key|STRIPE_API_KEY)\s*[=:]\s*["\']?(sk_live_[a-zA-Z0-9]{24})["\']?`), + Redact: "${1}=\"[REDACTED_STRIPE_KEY]\"", + }, + { + Name: "Stripe Publishable Key", + Pattern: regexp.MustCompile(`(?i)(stripe[_-]?publishable[_-]?key|STRIPE_PUBLISHABLE_KEY)\s*[=:]\s*["\']?(pk_live_[a-zA-Z0-9]{24})["\']?`), + Redact: "${1}=\"[REDACTED_STRIPE_PUBLISHABLE_KEY]\"", + }, + { + Name: "Twilio API Key", + Pattern: regexp.MustCompile(`(?i)(twilio[_-]?api[_-]?key|TWILIO_API_KEY)\s*[=:]\s*["\']?(SK[a-f0-9]{32})["\']?`), + Redact: "${1}=\"[REDACTED_TWILIO_API_KEY]\"", + }, + { + Name: "Twilio Auth Token", + Pattern: regexp.MustCompile(`(?i)(twilio[_-]?auth[_-]?token|TWILIO_AUTH_TOKEN)\s*[=:]\s*["\']?([a-f0-9]{32})["\']?`), + Redact: "${1}=\"[REDACTED_TWILIO_AUTH_TOKEN]\"", + }, + { + Name: "Twilio Account SID", + Pattern: regexp.MustCompile(`(?i)(twilio[_-]?account[_-]?sid|TWILIO_ACCOUNT_SID)\s*[=:]\s*["\']?(AC[a-f0-9]{32})["\']?`), + Redact: "${1}=\"[REDACTED_TWILIO_ACCOUNT_SID]\"", + }, + + // Cloud & Infrastructure APIs + { + Name: "DigitalOcean API Key", + Pattern: regexp.MustCompile(`(?i)(digitalocean[_-]?api[_-]?key|DIGITALOCEAN_API_KEY)\s*[=:]\s*["\']?([a-f0-9]{64})["\']?`), + Redact: "${1}=\"[REDACTED_DIGITALOCEAN_KEY]\"", + }, + { + Name: "Heroku API Key", + Pattern: regexp.MustCompile(`(?i)(heroku[_-]?api[_-]?key|HEROKU_API_KEY)\s*[=:]\s*["\']?([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})["\']?`), + Redact: "${1}=\"[REDACTED_HEROKU_KEY]\"", + }, + { + Name: "Vercel API Key", + Pattern: regexp.MustCompile(`(?i)(vercel[_-]?api[_-]?key|VERCEL_API_KEY)\s*[=:]\s*["\']?([a-zA-Z0-9_\-]{24})["\']?`), + Redact: "${1}=\"[REDACTED_VERCEL_KEY]\"", + }, + { + Name: "Netlify API Key", + Pattern: regexp.MustCompile(`(?i)(netlify[_-]?api[_-]?key|NETLIFY_API_KEY)\s*[=:]\s*["\']?([a-zA-Z0-9_\-]{40})["\']?`), + Redact: "${1}=\"[REDACTED_NETLIFY_KEY]\"", + }, + + // Database & Analytics APIs { - Name: "Private Key", - Pattern: regexp.MustCompile(`(?s)(-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----).*?(-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----)`), + Name: "MongoDB Atlas API Key", + Pattern: regexp.MustCompile(`(?i)(mongodb[_-]?api[_-]?key|MONGODB_API_KEY)\s*[=:]\s*["\']?([a-f0-9]{24}-[a-f0-9]{24})["\']?`), + Redact: "${1}=\"[REDACTED_MONGODB_API_KEY]\"", + }, + { + Name: "SendGrid API Key", + Pattern: regexp.MustCompile(`(?i)(sendgrid[_-]?api[_-]?key|SENDGRID_API_KEY)\s*[=:]\s*["\']?(SG\.[a-zA-Z0-9_\-\.]{22,}\.[a-zA-Z0-9_\-\.]{43})["\']?`), + Redact: "${1}=\"[REDACTED_SENDGRID_KEY]\"", + }, + { + Name: "Mailgun API Key", + Pattern: regexp.MustCompile(`(?i)(mailgun[_-]?api[_-]?key|MAILGUN_API_KEY)\s*[=:]\s*["\']?(key-[a-f0-9]{32})["\']?`), + Redact: "${1}=\"[REDACTED_MAILGUN_KEY]\"", + }, + + // Social & Auth APIs + { + Name: "Facebook App Secret", + Pattern: regexp.MustCompile(`(?i)(facebook[_-]?app[_-]?secret|FACEBOOK_APP_SECRET)\s*[=:]\s*["\']?([a-f0-9]{32})["\']?`), + Redact: "${1}=\"[REDACTED_FACEBOOK_SECRET]\"", + }, + { + Name: "Twitter API Key", + Pattern: regexp.MustCompile(`(?i)(twitter[_-]?api[_-]?key|TWITTER_API_KEY)\s*[=:]\s*["\']?([a-zA-Z0-9]{25})["\']?`), + Redact: "${1}=\"[REDACTED_TWITTER_API_KEY]\"", + }, + { + Name: "Twitter API Secret", + Pattern: regexp.MustCompile(`(?i)(twitter[_-]?api[_-]?secret|TWITTER_API_SECRET)\s*[=:]\s*["\']?([a-zA-Z0-9]{50})["\']?`), + Redact: "${1}=\"[REDACTED_TWITTER_API_SECRET]\"", + }, + { + Name: "LinkedIn Client Secret", + Pattern: regexp.MustCompile(`(?i)(linkedin[_-]?client[_-]?secret|LINKEDIN_CLIENT_SECRET)\s*[=:]\s*["\']?([a-zA-Z0-9]{16})["\']?`), + Redact: "${1}=\"[REDACTED_LINKEDIN_SECRET]\"", + }, + + // SSH Keys and Private Keys in various formats + { + Name: "RSA Private Key", + Pattern: regexp.MustCompile(`(?s)(-----BEGIN RSA PRIVATE KEY-----).*?(-----END RSA PRIVATE KEY-----)`), + Redact: "${1}\n[REDACTED_RSA_PRIVATE_KEY]\n${2}", + }, + { + Name: "EC Private Key", + Pattern: regexp.MustCompile(`(?s)(-----BEGIN EC PRIVATE KEY-----).*?(-----END EC PRIVATE KEY-----)`), + Redact: "${1}\n[REDACTED_EC_PRIVATE_KEY]\n${2}", + }, + { + Name: "DSA Private Key", + Pattern: regexp.MustCompile(`(?s)(-----BEGIN DSA PRIVATE KEY-----).*?(-----END DSA PRIVATE KEY-----)`), + Redact: "${1}\n[REDACTED_DSA_PRIVATE_KEY]\n${2}", + }, + { + Name: "OpenSSH Private Key", + Pattern: regexp.MustCompile(`(?s)(-----BEGIN OPENSSH PRIVATE KEY-----).*?(-----END OPENSSH PRIVATE KEY-----)`), + Redact: "${1}\n[REDACTED_OPENSSH_PRIVATE_KEY]\n${2}", + }, + { + Name: "PKCS#8 Private Key", + Pattern: regexp.MustCompile(`(?s)(-----BEGIN PRIVATE KEY-----).*?(-----END PRIVATE KEY-----)`), Redact: "${1}\n[REDACTED_PRIVATE_KEY]\n${2}", }, + { + Name: "PGP Private Key", + Pattern: regexp.MustCompile(`(?s)(-----BEGIN PGP PRIVATE KEY BLOCK-----).*?(-----END PGP PRIVATE KEY BLOCK-----)`), + Redact: "${1}\n[REDACTED_PGP_PRIVATE_KEY]\n${2}", + }, + { + Name: "SSH Public Key (RSA)", + Pattern: regexp.MustCompile(`(?i)(ssh[_-]?rsa[_-]?public[_-]?key|SSH_RSA_PUBLIC_KEY)\s*[=:]\s*["\']?(ssh-rsa [a-zA-Z0-9/+=]+ [a-zA-Z0-9._@-]+)["\']?`), + Redact: "${1}=\"[REDACTED_SSH_RSA_PUBLIC_KEY]\"", + }, + { + Name: "SSH Public Key (ED25519)", + Pattern: regexp.MustCompile(`(?i)(ssh[_-]?ed25519[_-]?public[_-]?key|SSH_ED25519_PUBLIC_KEY)\s*[=:]\s*["\']?(ssh-ed25519 [a-zA-Z0-9]+ [a-zA-Z0-9._@-]+)["\']?`), + Redact: "${1}=\"[REDACTED_SSH_ED25519_PUBLIC_KEY]\"", + }, + { + Name: "SSH Public Key (ECDSA)", + Pattern: regexp.MustCompile(`(?i)(ssh[_-]?ecdsa[_-]?public[_-]?key|SSH_ECDSA_PUBLIC_KEY)\s*[=:]\s*["\']?(ecdsa-sha2-[a-zA-Z0-9-]+ [a-zA-Z0-9/+=]+ [a-zA-Z0-9._@-]+)["\']?`), + Redact: "${1}=\"[REDACTED_SSH_ECDSA_PUBLIC_KEY]\"", + }, + { + Name: "SSH Public Key (Generic)", + Pattern: regexp.MustCompile(`(?i)(ssh[_-]?public[_-]?key|SSH_PUBLIC_KEY)\s*[=:]\s*["\']?(ssh-[a-zA-Z0-9-]+ [a-zA-Z0-9/+=]+ [a-zA-Z0-9._@-]+)["\']?`), + Redact: "${1}=\"[REDACTED_SSH_PUBLIC_KEY]\"", + }, + { + Name: "SSH Authorized Keys", + Pattern: regexp.MustCompile(`(?i)(ssh[_-]?authorized[_-]?keys|SSH_AUTHORIZED_KEYS)\s*[=:]\s*["\']?(ssh-[a-zA-Z0-9-]+ [a-zA-Z0-9/+=]+ [a-zA-Z0-9._@-]+)["\']?`), + Redact: "${1}=\"[REDACTED_SSH_AUTHORIZED_KEYS]\"", + }, + { + Name: "SSH Private Key File Content", + Pattern: regexp.MustCompile(`(?s)(-----BEGIN [A-Z ]+PRIVATE KEY-----\n)([A-Za-z0-9+/=\n]+)(\n-----END [A-Z ]+PRIVATE KEY-----)`), + Redact: "${1}[REDACTED_SSH_PRIVATE_KEY_CONTENT]${3}", + }, // JWT Tokens { diff --git a/internal/scrubber/scrubber_test.go b/internal/scrubber/scrubber_test.go index cfff4e5..63e3f8b 100644 --- a/internal/scrubber/scrubber_test.go +++ b/internal/scrubber/scrubber_test.go @@ -134,7 +134,7 @@ ghijklmnopqrstuvwxyz if strings.Contains(result, "MIIEpAIBAAKCAQEA") || strings.Contains(result, "ghijklmnopqrstuvwxyz") { t.Errorf("ScrubDiff() failed to redact private key.\nOutput: %s", result) } - if !strings.Contains(result, "[REDACTED_PRIVATE_KEY]") { + if !strings.Contains(result, "[REDACTED_RSA_PRIVATE_KEY]") { t.Errorf("ScrubDiff() did not add redaction marker.\nOutput: %s", result) } } From dd2a0980709c67de7afb1a5aa9fe328237550044 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Thu, 9 Oct 2025 22:51:00 +0530 Subject: [PATCH 3/3] Feat: Add redaction patterns for cloud credentials - Expands scrubber to cover Azure, GCP, Stripe, Twilio, and other sensitive keys for enhanced security. - Updates tests for new redaction markers. - Adds AGENTS.md for project guidelines. --- .gitignore | 1 + internal/scrubber/scrubber.go | 2 +- internal/scrubber/scrubber_test.go | 140 +++++++++++++++++++++++++++-- 3 files changed, 134 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 0ef7b96..4f994ce 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ PR_DESCRIPTION.md commit test.txt AGENTS.md +commit~ diff --git a/internal/scrubber/scrubber.go b/internal/scrubber/scrubber.go index 70f95b9..0bb6004 100644 --- a/internal/scrubber/scrubber.go +++ b/internal/scrubber/scrubber.go @@ -258,7 +258,7 @@ var ( }, { Name: "SSH Public Key (ED25519)", - Pattern: regexp.MustCompile(`(?i)(ssh[_-]?ed25519[_-]?public[_-]?key|SSH_ED25519_PUBLIC_KEY)\s*[=:]\s*["\']?(ssh-ed25519 [a-zA-Z0-9]+ [a-zA-Z0-9._@-]+)["\']?`), + Pattern: regexp.MustCompile(`(?i)(ssh[_-]?ed25519[_-]?public[_-]?key|SSH_ED25519_PUBLIC_KEY)\s*[=:]\s*["\']?(ssh-ed25519 [a-zA-Z0-9+/]+ [a-zA-Z0-9._@-]+)["\']?`), Redact: "${1}=\"[REDACTED_SSH_ED25519_PUBLIC_KEY]\"", }, { diff --git a/internal/scrubber/scrubber_test.go b/internal/scrubber/scrubber_test.go index 63e3f8b..c8485ac 100644 --- a/internal/scrubber/scrubber_test.go +++ b/internal/scrubber/scrubber_test.go @@ -397,29 +397,153 @@ func TestScrubEmailInCredentials(t *testing.T) { } func TestScrubCreditCard(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "Credit card with spaces", + input: "4111 1111 1111 1111", + expected: "[REDACTED_CREDIT_CARD]", + }, + { + name: "Credit card with dashes", + input: "4111-1111-1111-1111", + expected: "[REDACTED_CREDIT_CARD]", + }, + { + name: "Credit card no separators", + input: "4111111111111111", + expected: "[REDACTED_CREDIT_CARD]", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ScrubDiff(tt.input) + if !strings.Contains(result, "[REDACTED_CREDIT_CARD]") { + t.Errorf("ScrubDiff() failed to redact credit card.\nInput: %s\nOutput: %s", tt.input, result) + } + }) + } +} + +func TestScrubAzureCredentials(t *testing.T) { tests := []struct { name string input string }{ { - name: "Credit card with spaces", - input: `card: "4532 1234 5678 9010"`, + name: "Azure Client Secret", + input: `AZURE_CLIENT_SECRET="abc123def456ghi789jkl012mno345pqr"`, }, { - name: "Credit card with dashes", - input: `4532-1234-5678-9010`, + name: "Azure Subscription Key", + input: `azure_subscription_key: "abcdefghijklmnopqrstuvwxyz123456"`, }, { - name: "Credit card no separators", - input: `4532123456789010`, + name: "Azure Storage Key", + input: `AccountKey="abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890123456789012345678901234567890123456789012345678901234567890"`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ScrubDiff(tt.input) - if strings.Contains(result, "4532") && strings.Contains(result, "9010") { - t.Errorf("ScrubDiff() failed to redact credit card.\nInput: %s\nOutput: %s", tt.input, result) + if !strings.Contains(result, "[REDACTED_AZURE") { + t.Errorf("ScrubDiff() failed to redact Azure credentials.\nInput: %s\nOutput: %s", tt.input, result) + } + }) + } +} + +func TestScrubGoogleCloudCredentials(t *testing.T) { + tests := []struct { + name string + input string + }{ + { + name: "Google Cloud Service Account Key", + input: `GOOGLE_APPLICATION_CREDENTIALS="my-service-account-key.json"`, + }, + { + name: "Google Cloud API Key", + input: `gcp_api_key="abcdefghijklmnopqrstuvwxyz1234567890123456789"`, + }, + { + name: "Google Cloud JSON Credentials", + input: `"type": "service_account",\n"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5\n-----END PRIVATE KEY-----"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ScrubDiff(tt.input) + if !strings.Contains(result, "[REDACTED_") { + t.Errorf("ScrubDiff() failed to redact Google Cloud credentials.\nInput: %s\nOutput: %s", tt.input, result) + } + }) + } +} + +func TestScrubAdditionalAPIKeys(t *testing.T) { + tests := []struct { + name string + input string + }{ + { + name: "Stripe API Key", + input: `STRIPE_API_KEY="sk_live_1234567890abcdefghijklmnopqrstuvwxyz"`, + }, + { + name: "Twilio Auth Token", + input: `twilio_auth_token: "1234567890abcdef1234567890abcdef"`, + }, + { + name: "DigitalOcean API Key", + input: `digitalocean_api_key="1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"`, + }, + { + name: "SendGrid API Key", + input: `SENDGRID_API_KEY="SG.1234567890abcdef1234567890abcdef.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12345678"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ScrubDiff(tt.input) + if !strings.Contains(result, "[REDACTED_") { + t.Errorf("ScrubDiff() failed to redact additional API keys.\nInput: %s\nOutput: %s", tt.input, result) + } + }) + } +} + +func TestScrubSSHKeys(t *testing.T) { + tests := []struct { + name string + input string + }{ + { + name: "OpenSSH Private Key", + input: `-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAIEA2K8Qv8a4a5K6B2J8RiQzL1h4w0F2R3B4C5D6E7F8G9H0I1J2K3L4\n-----END OPENSSH PRIVATE KEY-----`, + }, + { + name: "SSH RSA Public Key", + input: `ssh_rsa_public_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCeStA8tG2r5a user@hostname"`, + }, + { + name: "SSH ED25519 Public Key", + input: `SSH_ED25519_PUBLIC_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGbWQrKyUBUHvL3+d1H5Q8UQrBw6M5J user@example.com"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ScrubDiff(tt.input) + if !strings.Contains(result, "[REDACTED_") { + t.Errorf("ScrubDiff() failed to redact SSH keys.\nInput: %s\nOutput: %s", tt.input, result) } }) }