From 52f585fa4554016d848071e7b2657d4845d3e04f Mon Sep 17 00:00:00 2001 From: jade Date: Mon, 3 Aug 2026 17:41:02 +0900 Subject: [PATCH] Refactor: Remove SensitiveDataMasker and fix PaginationRequestValidator --- .../dap/mcg/audit/AuditLogService.java | 8 +- .../mcg/guardrail/SensitiveDataMasker.java | 92 ------------------- .../dap/mcg/redis/RedisToolTraceService.java | 8 +- .../dap/mcg/service/ExecuteService.java | 8 +- .../large/AgentResponseBudgetService.java | 8 +- .../tool/large/LargeToolResponseService.java | 16 +--- .../large/PaginationRequestValidator.java | 11 ++- .../result/ToolExecutionResultFormatter.java | 8 +- 8 files changed, 24 insertions(+), 135 deletions(-) delete mode 100644 dap-gateway/src/main/java/io/shinhanlife/dap/mcg/guardrail/SensitiveDataMasker.java diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/audit/AuditLogService.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/audit/AuditLogService.java index 5cf5007a..6df20a55 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/audit/AuditLogService.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/audit/AuditLogService.java @@ -16,7 +16,6 @@ package io.shinhanlife.dap.mcg.audit; * */ import io.shinhanlife.dap.mcg.config.McpGatewayProperties; -import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker; import io.shinhanlife.dap.mcg.security.McpRequestContext; import com.fasterxml.jackson.databind.JsonNode; import org.slf4j.Logger; @@ -32,11 +31,8 @@ import org.springframework.stereotype.Service; public class AuditLogService { private static final Logger audit = LoggerFactory.getLogger("MCP_AUDIT"); private final McpGatewayProperties properties; - private final SensitiveDataMasker masker; - - public AuditLogService(McpGatewayProperties properties, SensitiveDataMasker masker) { + public AuditLogService(McpGatewayProperties properties) { this.properties = properties; - this.masker = masker; } /** @@ -47,7 +43,7 @@ public class AuditLogService { return; } audit.info("event=tool_started requestId={} agentId={} userId={} clientAddress={} tool={} arguments={}", - context.requestId(), context.agentId(), context.userId(), context.clientAddress(), toolName, masker.mask(arguments)); + context.requestId(), context.agentId(), context.userId(), context.clientAddress(), toolName, arguments); } /** diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/guardrail/SensitiveDataMasker.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/guardrail/SensitiveDataMasker.java deleted file mode 100644 index c4f539d7..00000000 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/guardrail/SensitiveDataMasker.java +++ /dev/null @@ -1,92 +0,0 @@ -package io.shinhanlife.dap.mcg.guardrail; - - -/** - * @package io.shinhanlife.dap.mcg.guardrail - * @className SensitiveDataMasker - * @description AX HUB 시스템 처리 클래스 - * @author 0986406 - * @create 2026.09.01 - *
- * ---------- 개정이력 ----------
- * 수정일      수정자    수정내용
- * ---------- -------- ---------------------------
- * 2026.09.01  0986406    최초생성
- * 
- * 
- */ -import org.springframework.stereotype.Component; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; - -/** - * Audit Log, Redis Trace, Agent 응답 preview에 남으면 안 되는 민감정보를 마스킹합니다. - */ -@Component -public class SensitiveDataMasker { - private static final Set SENSITIVE_KEYS = Set.of( - "password", "passwd", "pwd", "token", "accessToken", "refreshToken", "secret", - "ssn", "rrn", "residentNumber", "cardNumber", "accountNumber", "accountNo", - "phone", "mobile", "email", "idempotencyKey"); - private static final Pattern EMAIL = Pattern.compile("([a-zA-Z0-9._%+-]{2})[a-zA-Z0-9._%+-]*(@[a-zA-Z0-9.-]+)"); - private static final Pattern CARD_OR_ACCOUNT = Pattern.compile("\\b(\\d{4})\\d{4,12}(\\d{2,4})\\b"); - private final ObjectMapper json; - - public SensitiveDataMasker(ObjectMapper json) { - this.json = json; - } - - /** - * JsonNode 전체를 재귀적으로 순회하며 민감 key와 민감 패턴을 마스킹합니다. - */ - public JsonNode mask(JsonNode input) { - if (input == null || input.isMissingNode() || input.isNull()) { - return json.createObjectNode(); - } - if (input.isArray()) { - ArrayNode masked = json.createArrayNode(); - for (JsonNode item : input) { - masked.add(mask(item)); - } - return masked; - } - if (input.isObject()) { - ObjectNode masked = json.createObjectNode(); - Iterator> fields = input.fields(); - while (fields.hasNext()) { - Map.Entry entry = fields.next(); - String key = entry.getKey(); - JsonNode value = entry.getValue(); - if (isSensitiveKey(key)) { - masked.put(key, "***"); - } else { - masked.set(key, mask(value)); - } - } - return masked; - } - if (input.isTextual()) { - return json.valueToTree(maskText(input.asText())); - } - return input; - } - - private boolean isSensitiveKey(String key) { - return key != null && SENSITIVE_KEYS.stream().anyMatch(sensitive -> sensitive.equalsIgnoreCase(key)); - } - - private String maskText(String value) { - if (value == null || value.isBlank()) { - return value; - } - String masked = EMAIL.matcher(value).replaceAll("$1***$2"); - return CARD_OR_ACCOUNT.matcher(masked).replaceAll("$1********$2"); - } -} diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/redis/RedisToolTraceService.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/redis/RedisToolTraceService.java index a1d85332..7beab07e 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/redis/RedisToolTraceService.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/redis/RedisToolTraceService.java @@ -16,7 +16,6 @@ package io.shinhanlife.dap.mcg.redis; * */ import io.shinhanlife.dap.mcg.config.McpGatewayProperties; -import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker; import io.shinhanlife.dap.mcg.security.McpRequestContext; import io.shinhanlife.dap.mcg.dto.ToolMetadata; import org.slf4j.Logger; @@ -49,19 +48,16 @@ public class RedisToolTraceService { private final ObjectProvider redisProvider; private final ObjectMapper json; private final McpMonitorEventService monitorEvents; - private final SensitiveDataMasker masker; private final Map attemptStates = new ConcurrentHashMap<>(); public RedisToolTraceService(McpGatewayProperties properties, ObjectProvider redisProvider, ObjectMapper json, - McpMonitorEventService monitorEvents, - SensitiveDataMasker masker) { + McpMonitorEventService monitorEvents) { this.properties = properties; this.redisProvider = redisProvider; this.json = json; this.monitorEvents = monitorEvents; - this.masker = masker; } /** @@ -206,7 +202,7 @@ public class RedisToolTraceService { arguments.fieldNames().forEachRemaining(argNames::add); trace.put("argumentNames", argNames); - trace.put("arguments", masker.mask(arguments).toString()); + trace.put("arguments", arguments.toString()); trace.put("responseSummary", responseSummary(responseText)); trace.put("timestamp", Instant.now().toString()); return json.writeValueAsString(trace); diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java index e4c51559..8e71d2b1 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/service/ExecuteService.java @@ -23,7 +23,6 @@ import io.shinhanlife.dap.mcg.resilience.RetryPolicy; import io.shinhanlife.dap.mcg.resilience.ToolExecutionException; import io.shinhanlife.dap.mcg.dto.OperationType; import io.shinhanlife.dap.mcg.config.McpGatewayProperties; -import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker; import io.shinhanlife.dap.mcg.guardrail.GuardrailService; import io.shinhanlife.dap.mcg.security.McpRequestContext; import io.shinhanlife.dap.mcg.security.McpRequestContextResolver; @@ -58,7 +57,6 @@ public class ExecuteService { private final ToolPlanner planner; private final KillSwitchService killSwitchService; private final ObjectMapper objectMapper; - private final SensitiveDataMasker dataMasker; private final GuardrailService guardrailService; private final McpRequestContextResolver contextResolver; private final AuditLogService auditLogService; @@ -76,7 +74,6 @@ public class ExecuteService { public ExecuteService(ToolPlanner planner, KillSwitchService killSwitchService, ObjectMapper objectMapper, - SensitiveDataMasker dataMasker, GuardrailService guardrailService, McpRequestContextResolver contextResolver, AuditLogService auditLogService, @@ -92,7 +89,6 @@ public class ExecuteService { this.planner = planner; this.killSwitchService = killSwitchService; this.objectMapper = objectMapper; - this.dataMasker = dataMasker; this.guardrailService = guardrailService; this.contextResolver = contextResolver; this.auditLogService = auditLogService; @@ -266,14 +262,14 @@ public class ExecuteService { headers.put("trace-id", context.requestId()); headers.put("request-id", java.util.UUID.randomUUID().toString()); - ObjectNode pageArguments = paginationValidator.normalize(arguments); + ObjectNode pageArguments = paginationValidator.normalize(metadata, arguments); LargeToolResponseService.Collector collector = largeResponses.newCollector(metadata.getName(), context.requestId()); while (true) { Map pagePayload = objectMapper.convertValue(pageArguments, Map.class); try { - log.info(" [ExecuteService] 요청 페이로드(마스킹 적용): {}", objectMapper.writeValueAsString(dataMasker.mask(objectMapper.valueToTree(pagePayload)))); + log.info(" [ExecuteService] 요청 페이로드: {}", objectMapper.writeValueAsString(pagePayload)); } catch (Exception ignore) {} JsonNode data = null; diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/AgentResponseBudgetService.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/AgentResponseBudgetService.java index 357cfce9..5cee6a51 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/AgentResponseBudgetService.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/AgentResponseBudgetService.java @@ -19,7 +19,6 @@ import java.util.Iterator; import java.util.Map; import io.shinhanlife.dap.mcg.config.AgentResponseBudgetProperties; -import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker; import org.springframework.stereotype.Service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -30,12 +29,9 @@ import com.fasterxml.jackson.databind.node.ObjectNode; public class AgentResponseBudgetService { private final AgentResponseBudgetProperties properties; private final ObjectMapper json; - private final SensitiveDataMasker masker; - - public AgentResponseBudgetService(AgentResponseBudgetProperties properties, ObjectMapper json, SensitiveDataMasker masker) { + public AgentResponseBudgetService(AgentResponseBudgetProperties properties, ObjectMapper json) { this.properties = properties; this.json = json; - this.masker = masker; } public ObjectNode apply(ObjectNode response) { @@ -109,7 +105,7 @@ public class AgentResponseBudgetService { } private JsonNode budgetItem(JsonNode item, BudgetStats stats) { - JsonNode masked = masker.mask(item); + JsonNode masked = item; if (!masked.isObject()) { return truncateByBytes(masked, stats); } diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/LargeToolResponseService.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/LargeToolResponseService.java index df2be0af..3cba3c8f 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/LargeToolResponseService.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/LargeToolResponseService.java @@ -18,7 +18,6 @@ package io.shinhanlife.dap.mcg.tool.large; import java.util.Iterator; import io.shinhanlife.dap.mcg.config.McpGatewayProperties; -import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker; import io.shinhanlife.dap.mcg.resilience.FailureType; import io.shinhanlife.dap.mcg.resilience.ToolExecutionException; import org.springframework.stereotype.Service; @@ -34,16 +33,13 @@ import java.time.Instant; public class LargeToolResponseService { private final McpGatewayProperties properties; private final ObjectMapper json; - private final SensitiveDataMasker masker; private final AgentResponseBudgetService agentBudget; public LargeToolResponseService(McpGatewayProperties properties, ObjectMapper json, - SensitiveDataMasker masker, AgentResponseBudgetService agentBudget) { this.properties = properties; this.json = json; - this.masker = masker; this.agentBudget = agentBudget; } @@ -157,8 +153,7 @@ public class LargeToolResponseService { } Page page = pageFrom(data); if (!page.paginated() && pageCount == 0 && count(page.items()) <= pageSize()) { - JsonNode masked = masker.mask(data); - normalData = masked; + normalData = data; pageCount = 1; returnedCount = count(page.items()); totalCount = returnedCount; @@ -271,19 +266,18 @@ public class LargeToolResponseService { } private JsonNode previewItem(JsonNode item) { - JsonNode masked = masker.mask(item); - long itemBytes = jsonBytes(masked); + long itemBytes = jsonBytes(item); if (itemBytes <= properties.largeResponseMaxItemBytes()) { - return masked; + return item; } truncated = true; ObjectNode preview = json.createObjectNode(); preview.put("truncated", true); preview.put("originalBytes", itemBytes); preview.put("maxItemBytes", properties.largeResponseMaxItemBytes()); - if (masked.isObject()) { + if (item.isObject()) { ArrayNode fieldNames = json.createArrayNode(); - Iterator fieldNamesIter = masked.fieldNames(); + Iterator fieldNamesIter = item.fieldNames(); while (fieldNamesIter.hasNext()) { fieldNames.add(fieldNamesIter.next()); } diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/PaginationRequestValidator.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/PaginationRequestValidator.java index cf75fb9a..522a2f64 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/PaginationRequestValidator.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/large/PaginationRequestValidator.java @@ -15,6 +15,7 @@ package io.shinhanlife.dap.mcg.tool.large; * * */ +import io.shinhanlife.dap.mcg.dto.ToolMetadata; import io.shinhanlife.dap.mcg.config.McpGatewayProperties; import io.shinhanlife.dap.mcg.resilience.FailureType; import io.shinhanlife.dap.mcg.resilience.ToolExecutionException; @@ -40,12 +41,18 @@ public class PaginationRequestValidator { /** * pageSize/cursor를 검증한 뒤 Tool 서버에 넘길 안전한 arguments 복사본을 만듭니다. */ - public ObjectNode normalize(ObjectNode arguments) { + public ObjectNode normalize(ToolMetadata metadata, ObjectNode arguments) { try { ObjectNode normalized = arguments == null ? json.createObjectNode() : (ObjectNode) json.readTree(json.writeValueAsString(arguments)); - normalizePageSize(normalized); + + if (metadata != null && metadata.allowedArguments().contains("pageSize")) { + normalizePageSize(normalized); + } else if (normalized.has("pageSize")) { + normalizePageSize(normalized); + } + validateCursor(normalized); return normalized; } catch (ToolExecutionException error) { diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/result/ToolExecutionResultFormatter.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/result/ToolExecutionResultFormatter.java index b1259d6d..1edf772c 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/result/ToolExecutionResultFormatter.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/tool/result/ToolExecutionResultFormatter.java @@ -15,7 +15,6 @@ package io.shinhanlife.dap.mcg.tool.result; * * */ -import io.shinhanlife.dap.mcg.guardrail.SensitiveDataMasker; import org.springframework.stereotype.Service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -34,11 +33,8 @@ public class ToolExecutionResultFormatter { private static final int TEXT_PREVIEW_LIMIT = 2_000; private final ObjectMapper json; - private final SensitiveDataMasker masker; - - public ToolExecutionResultFormatter(ObjectMapper json, SensitiveDataMasker masker) { + public ToolExecutionResultFormatter(ObjectMapper json) { this.json = json; - this.masker = masker; } /** @@ -71,7 +67,7 @@ public class ToolExecutionResultFormatter { } public ToolExecutionResult fromJson(String toolName, JsonNode parsed, long sizeBytes) { - JsonNode masked = masker.mask(parsed); + JsonNode masked = parsed; if (masked.isObject()) { ObjectNode object = (ObjectNode) masked; if (object.path("isError").asBoolean(false) || object.has("error") || object.has("failureType")) {