chore: clean .gitignore cache

This commit is contained in:
GitHub Actions
2026-01-26 19:21:33 +00:00
parent 1b1b3a70b1
commit e5f0fec5db
1483 changed files with 0 additions and 472793 deletions

View File

@@ -1,42 +0,0 @@
package util
import (
"crypto/subtle"
)
// ConstantTimeCompare compares two strings in constant time to prevent comparison timing attacks.
//
// PROTECTION SCOPE:
// This function protects against timing attacks during the comparison operation itself,
// where an attacker might measure how long it takes to compare two strings byte-by-byte
// to infer information about the expected value.
//
// IMPORTANT LIMITATIONS:
// This does NOT protect against timing variance in database queries. If you retrieve a token
// from the database (e.g., WHERE invite_token = ?), the DB query timing will vary based on
// whether the token exists, potentially revealing information to an attacker through timing analysis.
// See backend/internal/api/handlers/user_handler.go for examples of this limitation.
//
// DEFENSE-IN-DEPTH:
// Despite this limitation, using constant-time comparison is still valuable as part of a
// defense-in-depth strategy. It eliminates one potential timing leak and should be used
// when comparing sensitive values like API keys, tokens, or passwords that are already
// in memory.
//
// Returns true if the strings are equal, false otherwise.
func ConstantTimeCompare(a, b string) bool {
aBytes := []byte(a)
bBytes := []byte(b)
// subtle.ConstantTimeCompare returns 1 if equal, 0 if not
return subtle.ConstantTimeCompare(aBytes, bBytes) == 1
}
// ConstantTimeCompareBytes compares two byte slices in constant time to prevent comparison timing attacks.
//
// This function has the same protection scope and limitations as ConstantTimeCompare.
// See ConstantTimeCompare documentation for details on what this protects against and
// what it does NOT protect against (e.g., database query timing variance).
func ConstantTimeCompareBytes(a, b []byte) bool {
return subtle.ConstantTimeCompare(a, b) == 1
}

View File

@@ -1,84 +0,0 @@
package util
import (
"testing"
)
func TestConstantTimeCompare(t *testing.T) {
t.Parallel()
tests := []struct {
name string
a string
b string
expected bool
}{
{"equal strings", "secret123", "secret123", true},
{"different strings", "secret123", "secret456", false},
{"different lengths", "short", "muchlonger", false},
{"empty strings", "", "", true},
{"one empty", "notempty", "", false},
{"unicode equal", "héllo", "héllo", true},
{"unicode different", "héllo", "hëllo", false},
{"special chars equal", "!@#$%^&*()", "!@#$%^&*()", true},
{"whitespace matters", "hello ", "hello", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ConstantTimeCompare(tt.a, tt.b)
if result != tt.expected {
t.Errorf("ConstantTimeCompare(%q, %q) = %v, want %v", tt.a, tt.b, result, tt.expected)
}
})
}
}
func TestConstantTimeCompareBytes(t *testing.T) {
t.Parallel()
tests := []struct {
name string
a []byte
b []byte
expected bool
}{
{"equal bytes", []byte{1, 2, 3}, []byte{1, 2, 3}, true},
{"different bytes", []byte{1, 2, 3}, []byte{1, 2, 4}, false},
{"different lengths", []byte{1, 2}, []byte{1, 2, 3}, false},
{"empty slices", []byte{}, []byte{}, true},
{"nil slices", nil, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ConstantTimeCompareBytes(tt.a, tt.b)
if result != tt.expected {
t.Errorf("ConstantTimeCompareBytes(%v, %v) = %v, want %v", tt.a, tt.b, result, tt.expected)
}
})
}
}
// BenchmarkConstantTimeCompare ensures the function remains constant-time.
func BenchmarkConstantTimeCompare(b *testing.B) {
secret := "a]3kL9#mP2$vN7@qR5*wX1&yT4^uI8%oE0!"
b.Run("equal", func(b *testing.B) {
for i := 0; i < b.N; i++ {
ConstantTimeCompare(secret, secret)
}
})
b.Run("different_first_char", func(b *testing.B) {
different := "b]3kL9#mP2$vN7@qR5*wX1&yT4^uI8%oE0!"
for i := 0; i < b.N; i++ {
ConstantTimeCompare(secret, different)
}
})
b.Run("different_last_char", func(b *testing.B) {
different := "a]3kL9#mP2$vN7@qR5*wX1&yT4^uI8%oE0?"
for i := 0; i < b.N; i++ {
ConstantTimeCompare(secret, different)
}
})
}

View File

@@ -1,57 +0,0 @@
// Package util provides utility functions used across the application.
package util
import (
"net"
"regexp"
"strings"
)
// SanitizeForLog removes control characters and newlines from user content before logging.
func SanitizeForLog(s string) string {
if s == "" {
return s
}
s = strings.ReplaceAll(s, "\r\n", " ")
s = strings.ReplaceAll(s, "\n", " ")
re := regexp.MustCompile(`[\x00-\x1F\x7F]+`)
s = re.ReplaceAllString(s, " ")
return s
}
// CanonicalizeIPForSecurity normalizes an IP string for security decisions
// (rate limiting keys, allow-list CIDR checks, etc.). It preserves Gin's
// trust proxy behavior by operating on the already-resolved client IP string.
//
// Normalizations:
// - IPv6 loopback (::1) -> 127.0.0.1 (stable across IPv4/IPv6 localhost)
// - IPv4-mapped IPv6 (e.g. ::ffff:127.0.0.1) -> 127.0.0.1
func CanonicalizeIPForSecurity(ipStr string) string {
ipStr = strings.TrimSpace(ipStr)
if ipStr == "" {
return ipStr
}
// Defensive normalization in case the input is not a plain IP string.
// Gin's Context.ClientIP() should return an IP, but in proxy/test setups
// we may still see host:port or comma-separated values.
if idx := strings.IndexByte(ipStr, ','); idx >= 0 {
ipStr = strings.TrimSpace(ipStr[:idx])
}
if host, _, err := net.SplitHostPort(ipStr); err == nil {
ipStr = host
}
ipStr = strings.Trim(ipStr, "[]")
ip := net.ParseIP(ipStr)
if ip == nil {
return ipStr
}
if v4 := ip.To4(); v4 != nil {
return v4.String()
}
if ip.IsLoopback() {
return "127.0.0.1"
}
return ip.String()
}

View File

@@ -1,72 +0,0 @@
package util
import "testing"
func TestSanitizeForLog(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
expected string
}{
{
name: "empty string",
input: "",
expected: "",
},
{
name: "clean string",
input: "Hello World",
expected: "Hello World",
},
{
name: "string with newline",
input: "Hello\nWorld",
expected: "Hello World",
},
{
name: "string with carriage return and newline",
input: "Hello\r\nWorld",
expected: "Hello World",
},
{
name: "string with multiple newlines",
input: "Hello\nWorld\nTest",
expected: "Hello World Test",
},
{
name: "string with control characters",
input: "Hello\x00\x01\x1FWorld",
expected: "Hello World",
},
{
name: "string with DEL character (0x7F)",
input: "Hello\x7FWorld",
expected: "Hello World",
},
{
name: "complex string with mixed control chars",
input: "Line1\r\nLine2\nLine3\x00\x01\x7F",
expected: "Line1 Line2 Line3 ",
},
{
name: "string with tabs (0x09 is control char)",
input: "Hello\tWorld",
expected: "Hello World",
},
{
name: "string with only control chars",
input: "\x00\x01\x02\x1F\x7F",
expected: " ",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := SanitizeForLog(tt.input)
if result != tt.expected {
t.Errorf("SanitizeForLog(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}