Skip to content

Commit ff843ae

Browse files
authored
fix(gateway): mask Claude API keys in logs and request files (#7)
Align ClaudeGatewayService with OpenAI gateway paths and ApiRequestLogService contract by redacting client API keys before info logs and persisted request logs.
1 parent 01b0ea8 commit ff843ae

3 files changed

Lines changed: 54 additions & 17 deletions

File tree

src/main/java/ai/nubase/ai/gateway/service/ClaudeGatewayService.java

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import ai.nubase.ai.gateway.dto.ApiUsageRecord;
44
import ai.nubase.ai.gateway.dto.TokenUsage;
55
import ai.nubase.common.config.AnthropicConfig;
6+
import ai.nubase.common.util.ApiKeyLogMask;
67
import com.fasterxml.jackson.databind.JsonNode;
78
import com.fasterxml.jackson.databind.ObjectMapper;
89
import jakarta.servlet.http.HttpServletResponse;
@@ -148,7 +149,7 @@ public String forwardGetRequest(String path, Map<String, String> headers, String
148149
long startTime = System.currentTimeMillis();
149150
String requestId = UUID.randomUUID().toString();
150151

151-
log.info("📤 [{}] GET {} - API Key: {}", requestId, path, (clientApiKey));
152+
log.info("📤 [{}] GET {} - API Key: {}", requestId, path, ApiKeyLogMask.mask(clientApiKey));
152153

153154
Request.Builder requestBuilder = new Request.Builder()
154155
.url(url)
@@ -199,7 +200,7 @@ public String forwardGetRequest(String path, Map<String, String> headers, String
199200
trackApiUsage(clientApiKey, requestId, null, path, "GET",
200201
response.code(), responseBody, duration, headers, null);
201202

202-
requestLogService.logRequest(requestId, (clientApiKey), "GET", path,
203+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "GET", path,
203204
null, headers, null, response.code(), responseBody, duration,
204205
TokenUsage.empty(), null);
205206

@@ -211,7 +212,7 @@ public String forwardGetRequest(String path, Map<String, String> headers, String
211212
trackApiUsage(clientApiKey, requestId, null, path, "GET",
212213
response.code(), responseBody, duration, headers, null);
213214

214-
requestLogService.logRequest(requestId, (clientApiKey), "GET", path,
215+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "GET", path,
215216
null, headers, null, response.code(), responseBody, duration,
216217
TokenUsage.empty(), null);
217218

@@ -237,7 +238,7 @@ public String forwardGetRequest(String path, Map<String, String> headers, String
237238
trackApiUsage(clientApiKey, requestId, null, path, "GET",
238239
500, null, duration, headers, e.getMessage());
239240

240-
requestLogService.logRequest(requestId, (clientApiKey), "GET", path,
241+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "GET", path,
241242
null, headers, null, 500, null, duration,
242243
TokenUsage.empty(), e.getMessage());
243244

@@ -357,7 +358,7 @@ private String executeNonStreamingRequest(String path, String requestBody, Map<S
357358
String requestId = UUID.randomUUID().toString();
358359

359360
log.info("agent_log [{}] POST {} - 模型: {}, API Key: {}, 上游: {}",
360-
requestId, path, model, (clientApiKey), upstream.name);
361+
requestId, path, model, ApiKeyLogMask.mask(clientApiKey), upstream.name);
361362
log.info("上游配置: baseUrl={}, timeout={}ms, maxInputTokens={}",
362363
upstream.baseUrl, upstream.timeout, upstream.maxInputTokens);
363364
String requestBodyLog = requestBody.length() > 200 ? requestBody.substring(0, 200) + "..." : requestBody;
@@ -429,7 +430,7 @@ private String executeNonStreamingRequest(String path, String requestBody, Map<S
429430
trackApiUsage(clientApiKey, requestId, model, path, "POST",
430431
response.code(), responseBody, duration, headers, null);
431432

432-
requestLogService.logRequest(requestId, (clientApiKey), "POST", path,
433+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "POST", path,
433434
model, headers, requestBody, response.code(), responseBody, duration,
434435
tokenUsage, null);
435436

@@ -441,7 +442,7 @@ private String executeNonStreamingRequest(String path, String requestBody, Map<S
441442
trackApiUsage(clientApiKey, requestId, model, path, "POST",
442443
response.code(), responseBody, duration, headers, null);
443444

444-
requestLogService.logRequest(requestId, (clientApiKey), "POST", path,
445+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "POST", path,
445446
model, headers, requestBody, response.code(), responseBody, duration,
446447
tokenUsage, null);
447448

@@ -467,7 +468,7 @@ private String executeNonStreamingRequest(String path, String requestBody, Map<S
467468
trackApiUsage(clientApiKey, requestId, model, path, "POST",
468469
500, null, duration, headers, e.getMessage());
469470

470-
requestLogService.logRequest(requestId, (clientApiKey), "POST", path,
471+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "POST", path,
471472
model, headers, requestBody, 500, null, duration,
472473
TokenUsage.empty(), e.getMessage());
473474

@@ -500,7 +501,7 @@ public String forwardCountTokensRequest(String requestBody, Map<String, String>
500501
log.info("🔢 转发 Count Tokens 请求到 Claude API");
501502
log.info("请求ID: {}", requestId);
502503
log.info("URL: {}", url);
503-
log.info("客户端 API Key: {}", (clientApiKey));
504+
log.info("客户端 API Key: {}", ApiKeyLogMask.mask(clientApiKey));
504505
if (headers != null && !headers.isEmpty()) {
505506
log.info("请求头: {}", headers);
506507
}
@@ -652,7 +653,7 @@ public String forwardFileUpload(String path, MultipartFile file, Map<String, Str
652653
response.code(), null, duration, headers,
653654
response.isSuccessful() ? null : responseBody);
654655

655-
requestLogService.logRequest(requestId, clientApiKey, "POST", path,
656+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "POST", path,
656657
null, headers,
657658
"{\"_multipart\":true,\"filename\":\"" + filename.replace("\"", "\\\"")
658659
+ "\",\"size\":" + file.getSize() + "}",
@@ -712,7 +713,7 @@ public void forwardFileDownload(String path, Map<String, String> headers, String
712713
downstream.setContentType("application/json");
713714
trackApiUsage(clientApiKey, requestId, null, path, "GET",
714715
status, null, duration, headers, errorBody);
715-
requestLogService.logRequest(requestId, clientApiKey, "GET", path,
716+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "GET", path,
716717
null, headers, null, status, errorBody, duration,
717718
TokenUsage.empty(), errorBody);
718719
downstream.getOutputStream().write(errorBody.getBytes(java.nio.charset.StandardCharsets.UTF_8));
@@ -746,7 +747,7 @@ public void forwardFileDownload(String path, Map<String, String> headers, String
746747

747748
trackApiUsage(clientApiKey, requestId, null, path, "GET",
748749
status, null, duration, headers, null);
749-
requestLogService.logRequest(requestId, clientApiKey, "GET", path,
750+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "GET", path,
750751
null, headers, null, status, "{\"_binary\":true}", duration,
751752
TokenUsage.empty(), null);
752753
} finally {
@@ -830,7 +831,7 @@ public String forwardGenericRequest(String method, String path, String requestBo
830831
response.code(), null, duration, headers,
831832
response.isSuccessful() ? null : responseBody);
832833

833-
requestLogService.logRequest(requestId, clientApiKey, upperMethod, path,
834+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), upperMethod, path,
834835
null, headers, expectsBody ? requestBody : null,
835836
response.code(), responseBody, duration,
836837
TokenUsage.empty(),
@@ -937,7 +938,7 @@ public void forwardStreamingRequest(String path, String requestBody, Map<String,
937938

938939
log.info("========================================");
939940
log.info("agent_log [{}] POST {} (stream) - 模型: {}, API Key: {}, 上游: {}",
940-
requestId, path, model, clientApiKey, primaryUpstream.name);
941+
requestId, path, model, ApiKeyLogMask.mask(clientApiKey), primaryUpstream.name);
941942
log.info("上游配置: baseUrl={}, timeout={}ms, maxInputTokens={}",
942943
primaryUpstream.baseUrl, primaryUpstream.timeout, primaryUpstream.maxInputTokens);
943944
String requestBodyLog = requestBody.length() > 200 ? requestBody.substring(0, 200) + "..." : requestBody;
@@ -1202,7 +1203,7 @@ private void completeStream(EventSource eventSource) {
12021203
trackApiUsageWithTokens(clientApiKey, requestId, model, path, "POST",
12031204
200, finalUsage, duration, ttft, headers);
12041205

1205-
requestLogService.logRequest(requestId, (clientApiKey), "POST", path,
1206+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "POST", path,
12061207
model, headers, originalRequestBody, 200,
12071208
"{\"type\":\"message_stream\",\"status\":\"completed\"}",
12081209
duration, finalUsage, null);
@@ -1216,7 +1217,7 @@ private void recordFailedAttempt(int statusCode, String errorBody, String errorM
12161217
trackApiUsage(clientApiKey, requestId, model, path, "POST",
12171218
statusCode, null, duration, headers, errorMsg);
12181219

1219-
requestLogService.logRequest(requestId, (clientApiKey), "POST", path,
1220+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "POST", path,
12201221
model, headers, originalRequestBody, statusCode, errorBody, duration,
12211222
TokenUsage.empty(), errorMsg);
12221223
}
@@ -1305,7 +1306,7 @@ private void cancelActiveEventSource() {
13051306
trackApiUsage(clientApiKey, requestId, model, path, "POST",
13061307
408, null, duration, headers, "请求超时");
13071308

1308-
requestLogService.logRequest(requestId, (clientApiKey), "POST", path,
1309+
requestLogService.logRequest(requestId, ApiKeyLogMask.mask(clientApiKey), "POST", path,
13091310
model, headers, originalRequestBody, 408, null, duration,
13101311
TokenUsage.empty(), "请求超时");
13111312

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package ai.nubase.common.util;
2+
3+
/**
4+
* 网关与审计日志中的 API Key 脱敏,保留首尾各 4 位便于排障关联。
5+
*/
6+
public final class ApiKeyLogMask {
7+
8+
private ApiKeyLogMask() {}
9+
10+
public static String mask(String apiKey) {
11+
if (apiKey == null || apiKey.length() <= 8) {
12+
return "***";
13+
}
14+
return apiKey.substring(0, 4) + "..." + apiKey.substring(apiKey.length() - 4);
15+
}
16+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ai.nubase.common.util;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.assertj.core.api.Assertions.assertThat;
6+
7+
class ApiKeyLogMaskTest {
8+
9+
@Test
10+
void mask_longKeyShowsPrefixAndSuffix() {
11+
assertThat(ApiKeyLogMask.mask("nbk_demo_abcdefghijklmnopqrstuvwxyz"))
12+
.isEqualTo("nbk_...wxyz");
13+
}
14+
15+
@Test
16+
void mask_shortOrNullReturnsPlaceholder() {
17+
assertThat(ApiKeyLogMask.mask(null)).isEqualTo("***");
18+
assertThat(ApiKeyLogMask.mask("short")).isEqualTo("***");
19+
}
20+
}

0 commit comments

Comments
 (0)