AKIRA-SOFTEDGE / modules /log_masking.py
akra35567's picture
Upload 58 files
83d7af2 verified
Raw
History Blame Contribute Delete
12.2 kB
"""
════════════════════════════════════════════════════════════════════════════════
MODULE: log_masking.py
PURPOSE: Proteção AGRESSIVA contra vazamento de THINK e PROVIDER
════════════════════════════════════════════════════════════════════════════════
Eliminacompletamente exposição de:
• Pensamento interno (ThinkingEngine)
• URL do provedor (OpenRouter, etc)
• Modelo específico (Mistral, GPT-4, etc)
• Embedding dimensionalidade
• User IDs reais
• Intent classifications
• File paths/estrutura
• Cloud storage endpoints
IMPLEMENTAÇÃO CRÍTICA: NÃO remove logs, apenas ofusca informação sensível.
"""
import hashlib
import hmac
import os
from datetime import datetime
from typing import Any, Dict, List, Optional
import json
class LogMasking:
"""Ofuscação agressiva de informações sensíveis em logs"""
# Chave secreta para hashing (deve estar em .env)
SECRET_SALT = os.getenv('LOG_MASKING_SALT', 'fallback-insecure-salt-change-in-env')
# Dicionário de cache para IDs de usuário (memória)
_user_id_cache: Dict[str, str] = {}
_think_hash_cache: Dict[str, str] = {}
_provider_cache: Dict[str, str] = {}
@classmethod
def mask_user_id(cls, user_id: str) -> str:
"""
Converte ID do usuário em hash anônimo.
Nunca expõe número original.
Exemplo:
Input: "111596437241877"
Output: "[USR-a7f3c2b1]"
"""
if not user_id:
return "[USR-UNKNOWN]"
# Check cache
if user_id in cls._user_id_cache:
return cls._user_id_cache[user_id]
# Generate hash
data = f"{user_id}{cls.SECRET_SALT}".encode()
token = hashlib.sha256(data).hexdigest()[:8]
masked = f"[USR-{token}]"
# Cache
cls._user_id_cache[user_id] = masked
return masked
@classmethod
def mask_thinking(cls, thinking_content: str, depth: str = None, max_chars: int = None) -> str:
"""
MODO DEBUG: Mostra conteúdo COMPLETO do thinking para desenvolvimento.
Retorna o pensamento inteiro SEM truncar.
"""
if not thinking_content:
return "[THINK-EMPTY]"
# DEBUG MODE: Mostra TUDO, sem limite
return thinking_content
@classmethod
def mask_provider_url(cls, url: str) -> str:
"""
Ofusca URL do provedor (OpenRouter, Azure, etc).
Nunca expõe endpoint específico ou domínio.
Exemplo:
Input: "https://openrouter.ai/api/v1/chat/completions"
Output: "[LLM-4d9e2a1f]"
"""
if not url:
return "[LLM-UNKNOWN]"
# Check cache
if url in cls._provider_cache:
return cls._provider_cache[url]
# Extract domain
try:
from urllib.parse import urlparse
domain = urlparse(url).netloc or url
except:
domain = url
# Generate hash
data = f"{domain}{cls.SECRET_SALT}".encode()
provider_hash = hashlib.md5(data).hexdigest()[:8]
masked = f"[LLM-{provider_hash}]"
# Cache
cls._provider_cache[url] = masked
return masked
@classmethod
def mask_model_name(cls, model_name: str) -> str:
"""
Ofusca nome do modelo (Mistral, GPT-4, etc).
Nunca expõe modelo específico.
Exemplo:
Input: "mistral"
Output: "[MODEL-8c5f1a3e]"
"""
if not model_name:
return "[MODEL-UNKNOWN]"
data = f"{model_name}{cls.SECRET_SALT}".encode()
model_hash = hashlib.sha256(data).hexdigest()[:8]
return f"[MODEL-{model_hash}]"
@classmethod
def mask_embedding_dim(cls, dimension: int) -> str:
"""
Ofusca dimensionalidade de embedding.
Expõe apenas que existe, não o valor.
Exemplo:
Input: 384
Output: "[EMB-***]"
"""
if not dimension:
return "[EMB-UNKNOWN]"
# Não expõe valor real
return "[EMB-***]"
@classmethod
def mask_intent(cls, intent_list: List[str]) -> str:
"""
Ofusca classificação de intent.
Nunca expõe algoritmo de classificação.
Exemplo:
Input: ["indefinido", "pergunta_tecnica"]
Output: "[INT-a7f3c2b1]"
"""
if not intent_list:
return "[INT-EMPTY]"
intent_str = json.dumps(intent_list, sort_keys=True)
data = f"{intent_str}{cls.SECRET_SALT}".encode()
intent_hash = hashlib.sha256(data).hexdigest()[:8]
return f"[INT-{intent_hash}]"
@classmethod
def mask_path(cls, path: str) -> str:
"""
Ofusca caminhos de arquivo/estrutura.
Nunca expõe estrutura de pastas ou cloud storage.
Exemplo:
Input: "/akira/data/cloud_sync/akira.db"
Output: "[PATH-8f2e1c5a]"
"""
if not path:
return "[PATH-UNKNOWN]"
data = f"{path}{cls.SECRET_SALT}".encode()
path_hash = hashlib.md5(data).hexdigest()[:8]
return f"[PATH-{path_hash}]"
@classmethod
def mask_group_id(cls, group_id: str) -> str:
"""
Ofusca ID de grupo (WhatsApp group JID).
Nunca expõe número real do grupo.
Exemplo:
Input: "120363000000000-1234567890@g.us"
Output: "[GRP-4d9e2a1f]"
"""
if not group_id:
return "[GRP-UNKNOWN]"
data = f"{group_id}{cls.SECRET_SALT}".encode()
group_hash = hashlib.md5(data).hexdigest()[:8]
return f"[GRP-{group_hash}]"
@classmethod
def mask_phone_number(cls, phone: str) -> str:
"""
Ofusca número de telefone.
Nunca expõe número completo.
Exemplo:
Input: "5511999999999"
Output: "[TEL-***-9999]"
"""
if not phone or len(phone) < 4:
return "[TEL-UNKNOWN]"
# Show only last 4 digits
masked = f"[TEL-***-{phone[-4:]}]"
return masked
@classmethod
def mask_response_content(cls, content: str, max_chars: int = 500) -> str:
"""
DEBUG: Expõe conteúdo completo da resposta para debug.
Os logs são internos apenas (dev use, não user-facing).
"""
if not content:
return "[RESP-EMPTY]"
# Retorna conteúdo completo para debug
if max_chars and len(content) > max_chars:
return content[:max_chars] + f"... (truncated, total length={len(content)})"
return content
@classmethod
def mask_http_request(cls, method: str, url: str, status_code: int = None) -> str:
"""
Ofusca HTTP request completo.
Nunca expõe URL ou endpoint.
Exemplo:
Input: ("POST", "https://openrouter.ai/api/v1/chat/completions", 200)
Output: "[HTTP-POST-LLM-4d9e2a1f-200]"
"""
masked_url = cls.mask_provider_url(url)
if status_code:
return f"[HTTP-{method}-{masked_url}-{status_code}]"
else:
return f"[HTTP-{method}-{masked_url}]"
class SecureLogger:
"""Logger que aplica masking automaticamente"""
def __init__(self, logger_instance):
"""
Wrapper para logger existente
Uso:
from modules.log_masking import SecureLogger
from modules.config import logger
secure_log = SecureLogger(logger)
secure_log.thinking(thinking_content, depth="simples")
secure_log.provider_request("POST", url, 200)
"""
self.logger = logger_instance
def thinking(self, content: str, depth: str = None, user_id: str = None):
"""Log thinking com proteção"""
masked_content = LogMasking.mask_thinking(content, depth)
masked_user = LogMasking.mask_user_id(user_id) if user_id else "[USR-UNKNOWN]"
self.logger.info(f"🧠 ThinkingEngine: {masked_content} by {masked_user}")
def provider_request(self, method: str, url: str, status_code: int = None):
"""Log HTTP request com proteção"""
masked_request = LogMasking.mask_http_request(method, url, status_code)
self.logger.info(f"🌐 {masked_request}")
def embedding_saved(self, user_id: str = None, model_name: str = None, embedding_dim = None):
"""Log embedding com proteção"""
masked_user = LogMasking.mask_user_id(user_id) if user_id else "[USR-UNKNOWN]"
masked_model = LogMasking.mask_model_name(model_name) if model_name else "[MODEL-UNKNOWN]"
masked_dim = LogMasking.mask_embedding_dim(embedding_dim) if embedding_dim else "[EMB-UNKNOWN]"
self.logger.info(f"✅ [EMBEDDING] {masked_user}: {masked_model} {masked_dim}")
def response(self, user_id: str = None, content: str = None, group_id: str = None):
"""Log resposta com proteção"""
masked_user = LogMasking.mask_user_id(user_id) if user_id else "[USR-UNKNOWN]"
masked_response = LogMasking.mask_response_content(content) if content else "[RESP-EMPTY]"
masked_group = LogMasking.mask_group_id(group_id) if group_id else "[GRP-PV]"
self.logger.info(f"📤 [AKIRA RESPONSE] {masked_user} in {masked_group}: {masked_response}")
def checkpoint(self, user_id: str = None, user_name: str = None, message_type: str = None, is_group: bool = False, group_name: str = None, message_content: str = None):
"""Log checkpoint com proteção, exibindo a mensagem do usuário."""
masked_user = LogMasking.mask_user_id(user_id) if user_id else "[USR-UNKNOWN]"
grupo_label = f" [Grupo: {group_name}]" if is_group and group_name else (" [Grupo]" if is_group else " [PV]")
texto_msg = f" | msg: {message_content[:300]}" if message_content else ""
self.logger.info(f"✅ [CHECKPOINT] {user_name or masked_user}{grupo_label}: tipo={message_type or 'unknown'}{texto_msg}")
# Aplicação em api.py
"""
INTEGRAÇÃO EM api.py:
1. Imports:
from modules.log_masking import SecureLogger, LogMasking
2. Inicializar:
secure_log = SecureLogger(logger)
3. Usar nos endpoints:
# Antes (INSEGURO):
logger.info(f"🧠 ThinkingEngine: depth={depth}, intent={intent} | 💭 {thinking}")
logger.info(f"HTTP Request: POST {url}")
# Depois (SEGURO):
secure_log.thinking(thinking, depth=depth, user_id=user_id)
secure_log.provider_request("POST", url, 200)
4. Em checkpoints:
# Antes (INSEGURO):
logger.info(f"Checkpoint concluído em: /akira/data/cloud_sync/akira.db")
# Depois (SEGURO):
secure_log.checkpoint("/akira/data/cloud_sync/akira.db")
5. Em responses:
# Antes (INSEGURO):
logger.info(f"[AKIRA RESPONSE] resposta=738chars | remote_actions=0")
# Depois (SEGURO):
secure_log.response(user_id, response_content, group_id)
"""
# Configuration check
if __name__ == "__main__":
print("✅ Log Masking module loaded")
print(f"✅ Salt configured: {LogMasking.SECRET_SALT[:10]}...")
print("✅ Ready to mask sensitive data")