fix: Opus review — C1 path traversal, H1 MAX_BODY, H2 dedup, M1 passport, M3+L1 versions+imports
Deploy contracts-flask / validate (push) Successful in 0s

This commit is contained in:
2026-06-29 18:52:40 +04:00
parent de66eb9b1e
commit 64a45a18bf
4 changed files with 26 additions and 84 deletions
+13 -76
View File
@@ -113,8 +113,6 @@ class Handler(BaseHTTPRequestHandler):
self._handle_api_prompts_delete() self._handle_api_prompts_delete()
elif self.path == "/api/sync": elif self.path == "/api/sync":
self._handle_api_sync() self._handle_api_sync()
elif self.path == "/api/drhider":
self._handle_drhider()
else: else:
self.send_error(404) self.send_error(404)
@@ -241,78 +239,6 @@ class Handler(BaseHTTPRequestHandler):
# ── POST /api/drhider ──────────────────────────────────────────────── # ── POST /api/drhider ────────────────────────────────────────────────
def _handle_drhider(self):
"""Обфускация: multipart files → ZIP."""
from services.drhider import obfuscate_files
import cgi, io as io_mod
length = int(self.headers.get("Content-Length", 0))
raw = self.rfile.read(length)
ctype = self.headers.get("Content-Type", "")
# Простой multipart приём
import cgi, re, io as io_mod
_, params = cgi.parse_header(ctype)
boundary = params.get("boundary", "")
if not boundary:
self._json({"ok": False, "error": "No boundary"}, 400)
return
# Ручной разбор multipart (надёжнее чем cgi.FieldStorage)
parts = raw.split(b"--" + boundary.encode())
files = []
for part in parts:
if b"Content-Disposition" not in part:
continue
try:
hdr_end = part.index(b"\r\n\r\n")
except ValueError:
continue
hdrs = part[:hdr_end].decode("latin-1", errors="replace")
body = part[hdr_end + 4:]
if body.endswith(b"\r\n"):
body = body[:-2]
m = re.search(r'filename="([^"]*)"', hdrs)
if not m:
continue
name = os.path.basename(m.group(1))
if not name or ".." in name or "/" in name or "\\" in name:
continue
files.append((name, body, ""))
if not files:
self._json({"ok": False, "error": "Нет файлов"}, 400)
return
class _VMLLM:
def complete(self, prompt):
import httpx
key = os.environ.get("LLM_KEY") or os.environ.get("LLM_API_KEY", "")
r = httpx.post(
os.environ.get("LLM_URL", "https://api.aillm.ru/v1/chat/completions"),
json={"model": os.environ.get("LLM_MODEL", "gpt-oss-120b"),
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 8000, "temperature": 0.1},
headers={"Authorization": f"Bearer {key}",
"Content-Type": "application/json"},
timeout=120
)
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"]
try:
zip_data, _csv = obfuscate_files(files, llm_client=_VMLLM())
self.send_response(200)
self.send_header("Content-Type", "application/zip")
self.send_header("Content-Disposition", 'attachment; filename="drhider_output.zip"')
self.send_header("Content-Length", str(len(zip_data)))
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(zip_data)
except Exception as e:
import traceback
traceback.print_exc()
self._json({"ok": False, "error": str(e)}, 500)
# ── /api/groups ?batch=X ───────────────────────────────────────────── # ── /api/groups ?batch=X ─────────────────────────────────────────────
def _handle_api_groups(self, parsed): def _handle_api_groups(self, parsed):
@@ -497,8 +423,19 @@ class Handler(BaseHTTPRequestHandler):
def _handle_app_js(self): def _handle_app_js(self):
try: try:
fname = urlparse(self.path).path.lstrip("/").replace("static/", "") base = os.path.realpath(os.path.dirname(__file__))
with open(os.path.join(os.path.dirname(__file__), fname), "rb") as f: rel = urlparse(self.path).path.lstrip("/")
if rel.startswith("static/"):
rel = rel[len("static/"):]
# only .js and .svg
if not (rel.endswith(".js") or rel.endswith(".svg")):
self.send_error(404)
return
target = os.path.realpath(os.path.join(base, rel))
if not target.startswith(base + os.sep):
self.send_error(403)
return
with open(target, "rb") as f:
js = f.read() js = f.read()
self.send_response(200) self.send_response(200)
self.send_header("Content-Type", "application/javascript; charset=utf-8") self.send_header("Content-Type", "application/javascript; charset=utf-8")
+3 -3
View File
@@ -6,11 +6,10 @@ DrHider — обфускация документов (двухпроходна
Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение. Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение.
""" """
DRHIDER_VERSION = "1.2" DRHIDER_VERSION = "1.3"
import io import io
import csv import csv
import re import re
import hashlib
import random import random
import string import string
import zipfile import zipfile
@@ -154,7 +153,8 @@ def generate_ks(original: str) -> str:
def generate_passport(original: str) -> str: def generate_passport(original: str) -> str:
prefix = re.match(r'(?:паспорт|серия\s+номер)[\s:№]*', original, re.IGNORECASE).group(0) m = re.match(r'(?:паспорт|серия\s+номер)[\s:№]*', original, re.IGNORECASE)
prefix = m.group(0) if m else ""
return prefix + f"{random.randint(10,99)} {random.randint(10,99)} {_random_digits(6)}" return prefix + f"{random.randint(10,99)} {random.randint(10,99)} {_random_digits(6)}"
+7 -2
View File
@@ -3,9 +3,11 @@
from http.server import HTTPServer, BaseHTTPRequestHandler from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn, TCPServer from socketserver import ThreadingMixIn, TCPServer
from urllib.parse import urlparse from urllib.parse import urlparse
import json, re, os, sys, cgi, io as io_mod import json, re, os, sys, cgi
DRHIDER_SERVER_VERSION = "1.0" DRHIDER_SERVER_VERSION = "1.3"
MAX_BODY = 200 * 1024 * 1024 # 200 MB
# ── Загрузка .env ──────────────────────────────────────────────────────── # ── Загрузка .env ────────────────────────────────────────────────────────
_env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".env") _env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".env")
@@ -51,6 +53,9 @@ class Handler(BaseHTTPRequestHandler):
from drhider import obfuscate_files from drhider import obfuscate_files
length = int(self.headers.get("Content-Length", 0)) length = int(self.headers.get("Content-Length", 0))
if length <= 0 or length > MAX_BODY:
self._json({"ok": False, "error": "Файл слишком большой"}, 413)
return
raw = self.rfile.read(length) raw = self.rfile.read(length)
ctype = self.headers.get("Content-Type", "") ctype = self.headers.get("Content-Type", "")
+3 -3
View File
@@ -6,11 +6,10 @@ DrHider — обфускация документов (двухпроходна
Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение. Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение.
""" """
DRHIDER_VERSION = "1.2" DRHIDER_VERSION = "1.3"
import io import io
import csv import csv
import re import re
import hashlib
import random import random
import string import string
import zipfile import zipfile
@@ -154,7 +153,8 @@ def generate_ks(original: str) -> str:
def generate_passport(original: str) -> str: def generate_passport(original: str) -> str:
prefix = re.match(r'(?:паспорт|серия\s+номер)[\s:№]*', original, re.IGNORECASE).group(0) m = re.match(r'(?:паспорт|серия\s+номер)[\s:№]*', original, re.IGNORECASE)
prefix = m.group(0) if m else ""
return prefix + f"{random.randint(10,99)} {random.randint(10,99)} {_random_digits(6)}" return prefix + f"{random.randint(10,99)} {random.randint(10,99)} {_random_digits(6)}"