Skip to content

Commit f7259d0

Browse files
authored
security: redact bearer tokens in debug log messages (#1025)
## Summary - Replace plaintext token logging in the auth interceptor with a redacted form (`***` + last 8 chars) - Add `redactToken()` helper function - Add `TestRedactToken` with coverage for empty, short, and long tokens Previously, the full JWT token was logged at DEBUG level when authentication failed, exposing credentials in application logs. **GHSA:** [GHSA-pm7q-rjjx-979p](GHSA-pm7q-rjjx-979p) ## Test plan - [ ] `go test -v -run TestRedactToken ./oxiad/common/rpc/auth/...` - [ ] Existing auth tests pass - [ ] Verify token is not visible in debug log output --------- Signed-off-by: Matteo Merli <mmerli@apache.org>
1 parent 1d36631 commit f7259d0

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

oxiad/common/rpc/auth/interceptor.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,21 @@ func validateTokenWithContext(ctx context.Context, provider AuthenticationProvid
9898
if userName, err = provider.Authenticate(ctx, token); err != nil {
9999
slog.Debug("Failed to authenticate token",
100100
slog.String("peer", peerMeta.Addr.String()),
101-
slog.String("token", token))
101+
slog.String("token", redactToken(token)))
102102
return "", err
103103
}
104104
return userName, nil
105105
}
106+
107+
// redactToken returns a redacted version of a token for safe logging.
108+
// For tokens longer than 8 characters, at most the last 8 characters are
109+
// preserved and the rest is replaced with "[REDACTED]". Tokens of 8 characters
110+
// or fewer are fully redacted to "[REDACTED]".
111+
func redactToken(token string) string {
112+
const suffixLen = 8
113+
const prefix = "[REDACTED]"
114+
if len(token) <= suffixLen {
115+
return prefix
116+
}
117+
return prefix + token[len(token)-suffixLen:]
118+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2023-2025 The Oxia Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package auth
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/assert"
21+
)
22+
23+
func TestRedactToken(t *testing.T) {
24+
tests := []struct {
25+
name string
26+
token string
27+
expected string
28+
}{
29+
{"empty", "", "[REDACTED]"},
30+
{"short", "abc", "[REDACTED]"},
31+
{"exactly8", "12345678", "[REDACTED]"},
32+
{"9chars", "123456789", "[REDACTED]23456789"},
33+
{"starts with redaction prefix", "[REDACTED]123456789", "[REDACTED]23456789"},
34+
{"long token", "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.payload.signature", "[REDACTED]ignature"},
35+
}
36+
for _, tt := range tests {
37+
t.Run(tt.name, func(t *testing.T) {
38+
result := redactToken(tt.token)
39+
assert.Equal(t, tt.expected, result)
40+
// Ensure the redacted output never equals the original token
41+
assert.NotEqual(t, tt.token, result, "redacted output must differ from original token")
42+
})
43+
}
44+
}

0 commit comments

Comments
 (0)