Spaces:
Runtime error
Runtime error
| """ | |
| 安全工具函数 | |
| 提供敏感信息掩码和安全日志功能 | |
| """ | |
| import json | |
| import re | |
| from typing import Any, Dict, Union | |
| from copy import deepcopy | |
| def mask_api_key(api_key: str, prefix_length: int = 4) -> str: | |
| """ | |
| 安全地掩码API key,只显示前几个字符,其余用星号替代 | |
| Args: | |
| api_key: 原始API key | |
| prefix_length: 显示的前缀长度,默认4个字符 | |
| Returns: | |
| 掩码后的API key,格式如: sk-1234**** | |
| """ | |
| if not api_key: | |
| return "***empty***" | |
| if len(api_key) <= prefix_length: | |
| return "*" * len(api_key) | |
| # 显示前几位和足够的星号 | |
| masked_length = max(8, len(api_key) - prefix_length) # 至少8个星号 | |
| return f"{api_key[:prefix_length]}{'*' * masked_length}" | |
| def mask_sensitive_data(data: Union[Dict[str, Any], str], sensitive_keys: list = None) -> Union[Dict[str, Any], str]: | |
| """ | |
| 递归地掩码数据中的敏感信息 | |
| Args: | |
| data: 要处理的数据(字典或字符串) | |
| sensitive_keys: 敏感字段名列表 | |
| Returns: | |
| 掩码后的数据副本 | |
| """ | |
| if sensitive_keys is None: | |
| sensitive_keys = [ | |
| 'api_key', 'apikey', 'key', 'token', 'password', 'secret', | |
| 'authorization', 'auth', 'x-api-key', 'x-goog-api-key', | |
| 'bearer', 'access_token', 'refresh_token' | |
| ] | |
| if isinstance(data, str): | |
| # 如果是字符串,尝试解析为JSON | |
| try: | |
| parsed = json.loads(data) | |
| masked = mask_sensitive_data(parsed, sensitive_keys) | |
| return json.dumps(masked, ensure_ascii=False, indent=2) | |
| except (json.JSONDecodeError, TypeError): | |
| # 如果不是JSON,检查是否包含API key模式 | |
| return _mask_string_patterns(data) | |
| elif isinstance(data, dict): | |
| # 深拷贝避免修改原始数据 | |
| masked_data = deepcopy(data) | |
| for key, value in masked_data.items(): | |
| key_lower = key.lower() | |
| # 检查是否是敏感字段 | |
| if any(sensitive_key in key_lower for sensitive_key in sensitive_keys): | |
| if isinstance(value, str): | |
| masked_data[key] = mask_api_key(value) | |
| else: | |
| masked_data[key] = "***masked***" | |
| elif isinstance(value, (dict, list)): | |
| # 递归处理嵌套结构 | |
| masked_data[key] = mask_sensitive_data(value, sensitive_keys) | |
| return masked_data | |
| elif isinstance(data, list): | |
| # 处理列表 | |
| return [mask_sensitive_data(item, sensitive_keys) for item in data] | |
| else: | |
| # 其他类型直接返回 | |
| return data | |
| def _mask_string_patterns(text: str) -> str: | |
| """ | |
| 掩码字符串中的API key模式 | |
| """ | |
| # 常见的API key模式 | |
| patterns = [ | |
| # OpenAI格式: sk-开头 | |
| (r'sk-[a-zA-Z0-9]{48}', lambda m: mask_api_key(m.group())), | |
| # Anthropic格式: sk-ant-开头 | |
| (r'sk-ant-[a-zA-Z0-9\-_]{95}', lambda m: mask_api_key(m.group())), | |
| # Google格式: 39字符的字母数字 | |
| (r'AIza[a-zA-Z0-9_\-]{35}', lambda m: mask_api_key(m.group())), | |
| # Bearer token | |
| (r'Bearer\s+[a-zA-Z0-9\-_\.]{20,}', lambda m: f"Bearer {mask_api_key(m.group()[7:])}"), | |
| # 通用长字符串(可能是key) | |
| (r'[a-zA-Z0-9\-_]{32,}', lambda m: mask_api_key(m.group()) if len(m.group()) > 20 else m.group()), | |
| ] | |
| result = text | |
| for pattern, replacer in patterns: | |
| result = re.sub(pattern, replacer, result) | |
| return result | |
| def safe_log_data(data: Any, max_length: int = 1000) -> str: | |
| """ | |
| 安全地格式化数据用于日志记录 | |
| Args: | |
| data: 要记录的数据 | |
| max_length: 最大长度限制 | |
| Returns: | |
| 安全的日志字符串 | |
| """ | |
| try: | |
| # 先掩码敏感信息 | |
| masked_data = mask_sensitive_data(data) | |
| # 转换为字符串 | |
| if isinstance(masked_data, (dict, list)): | |
| log_str = json.dumps(masked_data, ensure_ascii=False, indent=2) | |
| else: | |
| log_str = str(masked_data) | |
| # 限制长度 | |
| if len(log_str) > max_length: | |
| log_str = log_str[:max_length] + "...[truncated]" | |
| return log_str | |
| except Exception as e: | |
| return f"***log_error: {str(e)}***" | |
| def safe_log_request(request_data: Dict[str, Any]) -> str: | |
| """ | |
| 安全地记录请求数据 | |
| """ | |
| return safe_log_data(request_data, max_length=2000) | |
| def safe_log_response(response_data: Dict[str, Any]) -> str: | |
| """ | |
| 安全地记录响应数据 | |
| """ | |
| return safe_log_data(response_data, max_length=1500) |