- Remove sensitive data from debug logs (vault/secrets.go, secret/version.go) - Add input validation for GPG key IDs and keychain item names - Resolve GPG key IDs to full fingerprints before storing in metadata - Add comprehensive test coverage for validation functions - Add golangci-lint configuration with additional linters Security improvements: - Debug logs no longer expose decrypted secret values or private keys - GPG and keychain commands now validate input to prevent injection attacks - All validation uses precompiled regex patterns for performance
298 lines
5.8 KiB
Go
298 lines
5.8 KiB
Go
package secret
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestValidateGPGKeyID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
keyID string
|
|
wantErr bool
|
|
}{
|
|
// Valid cases
|
|
{
|
|
name: "valid email address",
|
|
keyID: "test@example.com",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid email with dots and hyphens",
|
|
keyID: "test.user-name@example-domain.co.uk",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid email with plus",
|
|
keyID: "test+tag@example.com",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid short key ID (8 hex chars)",
|
|
keyID: "ABCDEF12",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid long key ID (16 hex chars)",
|
|
keyID: "ABCDEF1234567890",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid fingerprint (40 hex chars)",
|
|
keyID: "ABCDEF1234567890ABCDEF1234567890ABCDEF12",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid lowercase hex fingerprint",
|
|
keyID: "abcdef1234567890abcdef1234567890abcdef12",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid mixed case hex",
|
|
keyID: "AbCdEf1234567890",
|
|
wantErr: false,
|
|
},
|
|
|
|
// Invalid cases
|
|
{
|
|
name: "empty key ID",
|
|
keyID: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with spaces",
|
|
keyID: "test user@example.com",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with semicolon (command injection)",
|
|
keyID: "test@example.com; rm -rf /",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with pipe (command injection)",
|
|
keyID: "test@example.com | cat /etc/passwd",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with backticks (command injection)",
|
|
keyID: "test@example.com`whoami`",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with dollar sign (command injection)",
|
|
keyID: "test@example.com$(whoami)",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with quotes",
|
|
keyID: "test\"@example.com",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with single quotes",
|
|
keyID: "test'@example.com",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with backslash",
|
|
keyID: "test\\@example.com",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with newline",
|
|
keyID: "test@example.com\nrm -rf /",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with carriage return",
|
|
keyID: "test@example.com\rrm -rf /",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "hex with invalid length (7 chars)",
|
|
keyID: "ABCDEF1",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "hex with invalid length (9 chars)",
|
|
keyID: "ABCDEF123",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "hex with non-hex characters",
|
|
keyID: "ABCDEFGH",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "mixed format (email with hex)",
|
|
keyID: "test@ABCDEF12",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with ampersand",
|
|
keyID: "test@example.com & echo test",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with redirect",
|
|
keyID: "test@example.com > /tmp/test",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key ID with null byte",
|
|
keyID: "test@example.com\x00",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := validateGPGKeyID(tt.keyID)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("validateGPGKeyID() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateKeychainItemName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
itemName string
|
|
wantErr bool
|
|
}{
|
|
// Valid cases
|
|
{
|
|
name: "valid simple name",
|
|
itemName: "my-secret-key",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid name with dots",
|
|
itemName: "com.example.app.key",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid name with underscores",
|
|
itemName: "my_secret_key_123",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid alphanumeric",
|
|
itemName: "Secret123Key",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid with hyphen at start",
|
|
itemName: "-my-key",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid with dot at start",
|
|
itemName: ".hidden-key",
|
|
wantErr: false,
|
|
},
|
|
|
|
// Invalid cases
|
|
{
|
|
name: "empty item name",
|
|
itemName: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with spaces",
|
|
itemName: "my secret key",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with semicolon",
|
|
itemName: "key;rm -rf /",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with pipe",
|
|
itemName: "key|cat /etc/passwd",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with backticks",
|
|
itemName: "key`whoami`",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with dollar sign",
|
|
itemName: "key$(whoami)",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with quotes",
|
|
itemName: "key\"name",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with single quotes",
|
|
itemName: "key'name",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with backslash",
|
|
itemName: "key\\name",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with newline",
|
|
itemName: "key\nname",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with carriage return",
|
|
itemName: "key\rname",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with ampersand",
|
|
itemName: "key&echo test",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with redirect",
|
|
itemName: "key>/tmp/test",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with null byte",
|
|
itemName: "key\x00name",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with parentheses",
|
|
itemName: "key(test)",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with brackets",
|
|
itemName: "key[test]",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with asterisk",
|
|
itemName: "key*",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "item name with question mark",
|
|
itemName: "key?",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := validateKeychainItemName(tt.itemName)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("validateKeychainItemName() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|