Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion intercept/apidump/apidump.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"cdr.dev/slog/v3"

"github.com/coder/aibridge/utils"
"github.com/coder/quartz"
"github.com/google/uuid"
"github.com/tidwall/pretty"
Expand Down Expand Up @@ -186,7 +187,7 @@ func (d *dumper) writeRedactedHeaders(w io.Writer, headers http.Header, sensitiv
}

if isSensitive {
value = redactHeaderValue(value)
value = utils.MaskSecret(value)
}
_, err := fmt.Fprintf(w, "%s: %s\r\n", key, value)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions intercept/apidump/apidump_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestBridgedMiddleware_RedactsSensitiveRequestHeaders(t *testing.T) {
// Verify sensitive headers ARE present but redacted
require.Contains(t, content, "Authorization: Bear...2345")
require.Contains(t, content, "X-Api-Key: secr...alue")
require.Contains(t, content, "Cookie: sess...c123") // "session=abc123" is 14 chars, so first 4 + last 4
require.Contains(t, content, "Cookie: se...23") // "session=abc123" is 14 chars, so first 2 + last 2

// Verify the full secret values are NOT present
require.NotContains(t, content, "sk-secret-key-12345")
Expand Down Expand Up @@ -133,8 +133,8 @@ func TestBridgedMiddleware_RedactsSensitiveResponseHeaders(t *testing.T) {
// Verify sensitive headers are present but redacted
require.Contains(t, content, "Set-Cookie: sess...cure")
// Note: Go canonicalizes WWW-Authenticate to Www-Authenticate
// "Bearer realm=\"api\"" = 18 chars, first 4 = "Bear", last 4 = "api\""
require.Contains(t, content, "Www-Authenticate: Bear...api\"")
// "Bearer realm=\"api\"" = 18 chars, first 2 = "Be", last 2 = "i\""
require.Contains(t, content, "Www-Authenticate: Be...i\"")

// Verify full secret values are NOT present
require.NotContains(t, content, "secret123")
Expand Down
14 changes: 0 additions & 14 deletions intercept/apidump/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,3 @@ var sensitiveResponseHeaders = map[string]struct{}{
"Www-Authenticate": {},
"Proxy-Authenticate": {},
}

// redactHeaderValue redacts a sensitive header value, showing only partial content.
// For values >= 8 bytes: shows first 4 and last 4 bytes with "..." in between.
// For values < 8 bytes: shows first and last byte with "..." in between.
func redactHeaderValue(value string) string {
if len(value) >= 8 {
return value[:4] + "..." + value[len(value)-4:]
}
if len(value) >= 2 {
return value[:1] + "..." + value[len(value)-1:]
}
// Single character or empty - just return as-is
return value
}
56 changes: 1 addition & 55 deletions intercept/apidump/headers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,60 +12,6 @@ import (
"github.com/stretchr/testify/require"
)

func TestRedactHeaderValue(t *testing.T) {
t.Parallel()

tests := []struct {
name string
input string
expected string
}{
{
name: "empty string",
input: "",
expected: "",
},
{
name: "single char",
input: "a",
expected: "a",
},
{
name: "two chars",
input: "ab",
expected: "a...b",
},
{
name: "seven chars",
input: "abcdefg",
expected: "a...g",
},
{
name: "eight chars - threshold",
input: "abcdefgh",
expected: "abcd...efgh",
},
{
name: "long value",
input: "Bearer sk-secret-key-12345",
expected: "Bear...2345",
},
{
name: "realistic api key",
input: "sk-proj-abc123xyz789",
expected: "sk-p...z789",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
result := redactHeaderValue(tc.input)
require.Equal(t, tc.expected, result)
})
}
}

func TestSensitiveHeaderLists(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -140,7 +86,7 @@ func TestWriteRedactedHeaders(t *testing.T) {
name: "sensitive header redacted",
headers: http.Header{"Set-Cookie": {"session=abcdefghij"}},
sensitive: sensitiveResponseHeaders,
expected: "Set-Cookie: sess...ghij\r\n",
expected: "Set-Cookie: se...ij\r\n",
},
{
name: "multi-value header",
Expand Down
2 changes: 1 addition & 1 deletion provider/anthropic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func TestAnthropic_CreateInterceptor_BYOK(t *testing.T) {
setHeaders: map[string]string{},
wantXApiKey: "test-key",
wantCredentialKind: intercept.CredentialKindCentralized,
wantCredentialHint: "***",
wantCredentialHint: "t...y",
},
{
name: "Messages_BYOK_BearerToken_And_APIKey",
Expand Down
7 changes: 4 additions & 3 deletions utils/mask.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ func MaskSecret(s string) string {
runes := []rune(s)
reveal := revealLength(len(runes))

// If there's nothing safe to reveal, mask it all.
if reveal == 0 || reveal*2 >= len(runes) {
return "***"
if len(runes) <= reveal*2 {
return "..."
}

prefix := string(runes[:reveal])
Expand All @@ -28,6 +27,8 @@ func revealLength(n int) int {
return 4
case n >= 10:
return 2
case n >= 5:
return 1
default:
return 0
}
Expand Down
7 changes: 5 additions & 2 deletions utils/mask_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ func TestMaskSecret(t *testing.T) {
expected string
}{
{"empty", "", ""},
{"short", "short", "***"},
{"short_9_chars", "veryshort", "***"},
{"single_char", "x", "..."},
{"two_chars", "ab", "..."},
{"four_chars", "abcd", "..."},
{"short", "short", "s...t"},
{"short_9_chars", "veryshort", "v...t"},
{"medium_15_chars", "thisisquitelong", "th...ng"},
{"long_api_key", "sk-ant-api03-abcdefgh", "sk-a...efgh"},
{"unicode", "hélloworld🌍!", "hé...🌍!"},
Expand Down
Loading