fix: M2 word boundaries + L2 sorted_keys once (Opus review)
Deploy contracts-flask / validate (push) Successful in 0s
Deploy contracts-flask / validate (push) Successful in 0s
This commit is contained in:
@@ -189,6 +189,7 @@ class TwoPassObfuscator:
|
|||||||
def __init__(self, llm_client=None):
|
def __init__(self, llm_client=None):
|
||||||
self._mapping: Dict[str, str] = {} # оригинал → замена (только для точных текстовых совпадений)
|
self._mapping: Dict[str, str] = {} # оригинал → замена (только для точных текстовых совпадений)
|
||||||
self._regex_replacements: List[Tuple[str, str, Callable]] = [] # (pattern, type, generator)
|
self._regex_replacements: List[Tuple[str, str, Callable]] = [] # (pattern, type, generator)
|
||||||
|
self._sorted_keys: List[str] = [] # ключи mapping, отсортированные по длине (убывание)
|
||||||
self._llm_client = llm_client
|
self._llm_client = llm_client
|
||||||
|
|
||||||
def obfuscate(self, files: List[Tuple[str, bytes, str]]) -> Tuple[bytes, str]:
|
def obfuscate(self, files: List[Tuple[str, bytes, str]]) -> Tuple[bytes, str]:
|
||||||
@@ -220,6 +221,9 @@ class TwoPassObfuscator:
|
|||||||
if self._llm_client:
|
if self._llm_client:
|
||||||
self._scan_llm_ner(all_texts)
|
self._scan_llm_ner(all_texts)
|
||||||
|
|
||||||
|
# Предсортировать ключи один раз для _apply_replacements
|
||||||
|
self._sorted_keys = sorted(self._mapping.keys(), key=len, reverse=True)
|
||||||
|
|
||||||
# --- Проход 2: замена ---
|
# --- Проход 2: замена ---
|
||||||
results = []
|
results = []
|
||||||
for fname, content, ctype in files:
|
for fname, content, ctype in files:
|
||||||
@@ -244,6 +248,7 @@ class TwoPassObfuscator:
|
|||||||
finally:
|
finally:
|
||||||
self._mapping.clear()
|
self._mapping.clear()
|
||||||
self._regex_replacements.clear()
|
self._regex_replacements.clear()
|
||||||
|
self._sorted_keys.clear()
|
||||||
|
|
||||||
def _expand_zips(self, files: List[Tuple[str, bytes, str]]) -> List[Tuple[str, bytes, str]]:
|
def _expand_zips(self, files: List[Tuple[str, bytes, str]]) -> List[Tuple[str, bytes, str]]:
|
||||||
"""Распаковать ZIP-файлы, заменив их содержимым. Остальные файлы — как есть."""
|
"""Распаковать ZIP-файлы, заменив их содержимым. Остальные файлы — как есть."""
|
||||||
@@ -432,13 +437,15 @@ class TwoPassObfuscator:
|
|||||||
|
|
||||||
def _apply_replacements(self, text: str) -> str:
|
def _apply_replacements(self, text: str) -> str:
|
||||||
"""Применить все замены из словаря mapping к строке. Сначала длинные, потом короткие."""
|
"""Применить все замены из словаря mapping к строке. Сначала длинные, потом короткие."""
|
||||||
# Сортируем по длине оригинала (убывание) чтобы избежать частичных замен
|
|
||||||
sorted_keys = sorted(self._mapping.keys(), key=len, reverse=True)
|
|
||||||
result = text
|
result = text
|
||||||
for original in sorted_keys:
|
for original in self._sorted_keys:
|
||||||
replacement = self._mapping[original]
|
replacement = self._mapping[original]
|
||||||
# Только точное совпадение (не подстрока)
|
# Границы слова — если сущность начинается и заканчивается на \w
|
||||||
result = re.sub(re.escape(original), replacement, result)
|
if original and original[0].isalnum() and original[-1].isalnum():
|
||||||
|
pattern = r'(?<!\w)' + re.escape(original) + r'(?!\w)'
|
||||||
|
else:
|
||||||
|
pattern = re.escape(original)
|
||||||
|
result = re.sub(pattern, replacement, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# --- Сборка выдачи ---
|
# --- Сборка выдачи ---
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ class TwoPassObfuscator:
|
|||||||
def __init__(self, llm_client=None):
|
def __init__(self, llm_client=None):
|
||||||
self._mapping: Dict[str, str] = {} # оригинал → замена (только для точных текстовых совпадений)
|
self._mapping: Dict[str, str] = {} # оригинал → замена (только для точных текстовых совпадений)
|
||||||
self._regex_replacements: List[Tuple[str, str, Callable]] = [] # (pattern, type, generator)
|
self._regex_replacements: List[Tuple[str, str, Callable]] = [] # (pattern, type, generator)
|
||||||
|
self._sorted_keys: List[str] = [] # ключи mapping, отсортированные по длине (убывание)
|
||||||
self._llm_client = llm_client
|
self._llm_client = llm_client
|
||||||
|
|
||||||
def obfuscate(self, files: List[Tuple[str, bytes, str]]) -> Tuple[bytes, str]:
|
def obfuscate(self, files: List[Tuple[str, bytes, str]]) -> Tuple[bytes, str]:
|
||||||
@@ -220,6 +221,9 @@ class TwoPassObfuscator:
|
|||||||
if self._llm_client:
|
if self._llm_client:
|
||||||
self._scan_llm_ner(all_texts)
|
self._scan_llm_ner(all_texts)
|
||||||
|
|
||||||
|
# Предсортировать ключи один раз для _apply_replacements
|
||||||
|
self._sorted_keys = sorted(self._mapping.keys(), key=len, reverse=True)
|
||||||
|
|
||||||
# --- Проход 2: замена ---
|
# --- Проход 2: замена ---
|
||||||
results = []
|
results = []
|
||||||
for fname, content, ctype in files:
|
for fname, content, ctype in files:
|
||||||
@@ -244,6 +248,7 @@ class TwoPassObfuscator:
|
|||||||
finally:
|
finally:
|
||||||
self._mapping.clear()
|
self._mapping.clear()
|
||||||
self._regex_replacements.clear()
|
self._regex_replacements.clear()
|
||||||
|
self._sorted_keys.clear()
|
||||||
|
|
||||||
def _expand_zips(self, files: List[Tuple[str, bytes, str]]) -> List[Tuple[str, bytes, str]]:
|
def _expand_zips(self, files: List[Tuple[str, bytes, str]]) -> List[Tuple[str, bytes, str]]:
|
||||||
"""Распаковать ZIP-файлы, заменив их содержимым. Остальные файлы — как есть."""
|
"""Распаковать ZIP-файлы, заменив их содержимым. Остальные файлы — как есть."""
|
||||||
@@ -432,13 +437,15 @@ class TwoPassObfuscator:
|
|||||||
|
|
||||||
def _apply_replacements(self, text: str) -> str:
|
def _apply_replacements(self, text: str) -> str:
|
||||||
"""Применить все замены из словаря mapping к строке. Сначала длинные, потом короткие."""
|
"""Применить все замены из словаря mapping к строке. Сначала длинные, потом короткие."""
|
||||||
# Сортируем по длине оригинала (убывание) чтобы избежать частичных замен
|
|
||||||
sorted_keys = sorted(self._mapping.keys(), key=len, reverse=True)
|
|
||||||
result = text
|
result = text
|
||||||
for original in sorted_keys:
|
for original in self._sorted_keys:
|
||||||
replacement = self._mapping[original]
|
replacement = self._mapping[original]
|
||||||
# Только точное совпадение (не подстрока)
|
# Границы слова — если сущность начинается и заканчивается на \w
|
||||||
result = re.sub(re.escape(original), replacement, result)
|
if original and original[0].isalnum() and original[-1].isalnum():
|
||||||
|
pattern = r'(?<!\w)' + re.escape(original) + r'(?!\w)'
|
||||||
|
else:
|
||||||
|
pattern = re.escape(original)
|
||||||
|
result = re.sub(pattern, replacement, result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# --- Сборка выдачи ---
|
# --- Сборка выдачи ---
|
||||||
|
|||||||
Reference in New Issue
Block a user