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
Deploy contracts-flask / validate (push) Successful in 0s
This commit is contained in:
+13
-76
@@ -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")
|
||||||
|
|||||||
@@ -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)}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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", "")
|
||||||
|
|
||||||
|
|||||||
@@ -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)}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user