mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-13 14:15:52 +00:00
fix: injection protections and concurrency improvements
This commit is contained in:
1 parent
b144265094
commit
99484e02c8
12 files changed
+452
-84
No files matched your search
@@ -153,6 +153,35 @@ def test_renderer_keeps_injection_text_as_literal_content():
|
||||
assert prs.slides[0].shapes.title.text == payload
|
||||
|
||||
|
||||
def test_spreadsheet_renderer_neutralizes_formula_injection():
|
||||
# Spec content is model / prompt-injection controlled. A string cell starting
|
||||
# with a formula trigger must be stored as TEXT (data_type 's'), never as a
|
||||
# live formula; genuine numbers stay numeric.
|
||||
from openpyxl import load_workbook
|
||||
|
||||
spec = {
|
||||
"sheets": [
|
||||
{
|
||||
"name": "S",
|
||||
"rows": [
|
||||
['=HYPERLINK("http://evil","x")', "+1+1", "@cmd", "-danger"],
|
||||
["safe text", -5, 3.14],
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
wb = load_workbook(_render_in_process("spreadsheet", spec))
|
||||
ws = wb["S"]
|
||||
# None of the trigger cells are formulas; each is a quote-prefixed string.
|
||||
for coord in ("A1", "B1", "C1", "D1"):
|
||||
assert ws[coord].data_type == "s", coord
|
||||
assert str(ws[coord].value).startswith("'")
|
||||
# Benign text is unchanged; numbers stay numeric (no spurious quoting).
|
||||
assert ws["A2"].value == "safe text" and ws["A2"].data_type == "s"
|
||||
assert ws["B2"].value == -5 and ws["B2"].data_type == "n"
|
||||
assert ws["C2"].value == 3.14 and ws["C2"].data_type == "n"
|
||||
|
||||
|
||||
def test_pdf_renderer_escapes_markup_and_does_not_execute():
|
||||
payload = "<b>not bold</b> & \"</para>\" '''os.system('x')'''"
|
||||
spec = {"title": payload, "blocks": [{"type": "paragraph", "text": payload}]}
|
||||
|
||||
@@ -34,8 +34,12 @@ class _FakeFile:
|
||||
def __init__(self, data: bytes) -> None:
|
||||
self._data = data
|
||||
|
||||
def read(self) -> bytes:
|
||||
return self._data
|
||||
def read(self, size: int = -1) -> bytes:
|
||||
# Mirror BinaryIO.read(n): the bridge does a bounded read to cap memory.
|
||||
return self._data if size is None or size < 0 else self._data[:size]
|
||||
|
||||
def close(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class _FakeStorage:
|
||||
@@ -224,6 +228,22 @@ def test_bridge_missing_upload_path_errors(monkeypatch):
|
||||
bridge_attachment(att, user_id=USER, conversation_id=CONV)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_bridge_rejects_oversize_attachment_before_reading(monkeypatch):
|
||||
# An oversize attachment is rejected via its authoritative ``size`` column,
|
||||
# BEFORE the bytes are buffered into worker memory (a memory-DoS guard).
|
||||
from application.core.settings import settings
|
||||
|
||||
_FakeArtifactsRepo.bridged = {}
|
||||
storage, calls = _patch_bridge(monkeypatch)
|
||||
att = _attachment()
|
||||
att["size"] = int(settings.ARTIFACT_MAX_BYTES) + 1
|
||||
with pytest.raises(AttachmentBridgeError, match="size limit"):
|
||||
bridge_attachment(att, user_id=USER, conversation_id=CONV)
|
||||
assert calls == [] # never persisted
|
||||
assert storage.requested == [] # never even read the bytes
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# code_executor wiring: fallback fires, stages bytes, succeeds
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user