feat(mcp): harden remote assets and job execution

Gate source-file downloads and verify registry paths and content hashes. Stream multipart uploads with bounded limits, supervise OpenCode jobs with explicit result markers and timeouts, and preserve managed endpoint security settings. Document the trusted-client boundary and add focused regression coverage.

Validation: PYTHONPATH=<staged-tree> python -m unittest discover -s tests (182 tests)
This commit is contained in:
Codex
2026-07-09 21:44:48 +00:00
parent d47bb2c549
commit 7eb9b987ab
16 changed files with 3709 additions and 500 deletions
+154
View File
@@ -1,4 +1,5 @@
import base64
import io
import json
import os
import subprocess
@@ -35,6 +36,7 @@ class McpIngestTests(unittest.TestCase):
)
def tearDown(self):
self.manager.close()
self.tempdir.cleanup()
def test_parse_bool_accepts_enabled_values_only(self):
@@ -45,6 +47,18 @@ class McpIngestTests(unittest.TestCase):
self.assertFalse(parse_bool("0"))
self.assertFalse(parse_bool("false"))
def test_close_still_closes_supervisor_when_queued_job_write_fails(self):
queued = {"state": "queued"}
with (
patch.object(self.manager, "_iter_jobs", return_value=[queued]),
patch.object(self.manager, "_write_job", side_effect=OSError("disk full")),
patch.object(self.manager._supervisor, "close") as supervisor_close,
):
with self.assertRaises(OSError):
self.manager.close()
supervisor_close.assert_called_once_with()
def test_uploads_are_staged_in_runtime_not_vault(self):
upload = self.manager.create_upload(file_name="drawing.pdf", size_bytes=0, sha256=self._sha256(b""))
upload_path = self._upload_payload_path(upload["upload_id"]).resolve()
@@ -193,6 +207,56 @@ class McpIngestTests(unittest.TestCase):
self.assertNotIn("path", upload)
self.assertEqual(self._upload_payload_path(upload["upload_id"]).read_bytes(), payload)
def test_create_completed_upload_from_stream_reads_bounded_chunks(self):
payload = b"streamed upload payload" * 100_000
class BoundedReadStream(io.BytesIO):
def __init__(self, data):
super().__init__(data)
self.read_sizes = []
def read(self, size=-1):
if size < 0:
raise AssertionError("stream upload must not use an unbounded read")
self.read_sizes.append(size)
return super().read(size)
manager = IngestManager(
vault_path=self.vault,
project_root=self.project,
runtime_root=self.root / "stream-runtime",
max_file_bytes=len(payload) + 1,
)
stream = BoundedReadStream(payload)
upload = manager.create_completed_upload_from_stream(
file_name="stream.bin",
stream=stream,
size_bytes=len(payload),
)
self.assertEqual(upload["state"], "complete")
self.assertEqual(upload["sha256"], "sha256:" + self._sha256(payload))
self.assertGreater(len(stream.read_sizes), 2)
self.assertTrue(all(size == 1024 * 1024 for size in stream.read_sizes))
def test_create_completed_upload_from_stream_cleans_partial_file_when_oversized(self):
manager = IngestManager(
vault_path=self.vault,
project_root=self.project,
runtime_root=self.root / "small-stream-runtime",
max_file_bytes=5,
)
with self.assertRaises(IngestError):
manager.create_completed_upload_from_stream(
file_name="too-large.bin",
stream=io.BytesIO(b"123456"),
)
self.assertEqual(list(manager.upload_root.rglob("upload.json")), [])
self.assertEqual([path for path in manager.upload_root.rglob("*") if path.is_file()], [])
def test_upload_ticket_is_single_use_and_preserves_defaults(self):
ticket = self.manager.create_upload_ticket(
expires_seconds=60,
@@ -341,6 +405,8 @@ class McpIngestTests(unittest.TestCase):
self.assertNotIn("../secret", prompt)
self.assertNotIn("path", job["files"][0])
log_path = Path(self.manager._load_job(job["job_id"])["log_path"])
log_path.write_text(self._json_text_event("MCP_INGEST_OK\nIngested source.pdf"), encoding="utf-8")
popen.returncode = 0
self.manager.refresh_jobs()
status = self.manager.get_job_status(job["job_id"])
@@ -348,6 +414,61 @@ class McpIngestTests(unittest.TestCase):
self.assertEqual(status["exit_code"], 0)
self.assertNotIn("path", status["files"][0])
def test_ingest_result_rejects_skill_failure_with_zero_exit_code(self):
upload = self._complete_upload("source.pdf", b"source data")
popen = FakePopen(returncode=None)
with (
patch("obsidian_wiki.mcp_ingest.subprocess.run", side_effect=self._fake_static_readiness_run),
patch("obsidian_wiki.mcp_ingest.subprocess.Popen", return_value=popen),
):
job = self.manager.start_ingest_job([upload["upload_id"]])
log_path = Path(self.manager._load_job(job["job_id"])["log_path"])
log_path.write_text(self._json_text_event("MCP_INGEST_FAILED\nSKILL_LOAD_FAILED: missing"), encoding="utf-8")
popen.returncode = 0
result = self.manager.get_ingest_job_result(job["job_id"])
self.assertEqual(result["state"], "failed")
self.assertIn("SKILL_LOAD_FAILED", result["error"])
def test_ingest_public_status_hides_raw_tool_logs_paths_and_pid(self):
upload = self._complete_upload("source.pdf", b"source data")
popen = FakePopen(returncode=None)
with (
patch("obsidian_wiki.mcp_ingest.subprocess.run", side_effect=self._fake_static_readiness_run),
patch("obsidian_wiki.mcp_ingest.subprocess.Popen", return_value=popen),
):
job = self.manager.start_ingest_job([upload["upload_id"]])
log_path = Path(self.manager._load_job(job["job_id"])["log_path"])
log_path.write_text(
'{"type":"tool_use","part":{"state":{"output":"/home/private/secret"}}}\n'
+ self._json_text_event("MCP_INGEST_OK\nIngest complete"),
encoding="utf-8",
)
popen.returncode = 0
result = self.manager.get_ingest_job_result(job["job_id"])
status = self.manager.get_ingest_job_status(job["job_id"])
self.assertEqual(result["state"], "succeeded")
self.assertEqual(result["opencode_output_tail"], "Ingest complete")
self.assertNotIn("/home/private", result["opencode_output_tail"])
self.assertNotIn("pid", status)
self.assertNotIn("checked_paths", status["preflight"])
internal = self.manager._load_job(job["job_id"])
internal["error"] = f"failed while reading {self.project / 'secret.txt'}"
internal["preflight"]["warnings"] = [f"check {self.runtime / 'private.log'}"]
self.manager._write_job(internal)
redacted = self.manager.get_ingest_job_status(job["job_id"])
serialized = json.dumps(redacted)
self.assertNotIn(str(self.project), serialized)
self.assertNotIn(str(self.runtime), serialized)
self.assertIn("<project>", serialized)
self.assertIn("<runtime>", serialized)
def test_jobs_queue_one_at_a_time_and_cancel_queued_job(self):
first = self._complete_upload("first.pdf", b"first")
second = self._complete_upload("second.pdf", b"second")
@@ -367,6 +488,27 @@ class McpIngestTests(unittest.TestCase):
self.assertEqual(cancelled["state"], "cancelled")
def test_start_ingest_job_is_idempotent_for_claimed_uploads(self):
upload = self._complete_upload("source.pdf", b"source")
popen = FakePopen(returncode=None)
with (
patch("obsidian_wiki.mcp_ingest.subprocess.run", side_effect=self._fake_static_readiness_run),
patch("obsidian_wiki.mcp_ingest.subprocess.Popen", return_value=popen) as popen_mock,
):
first = self.manager.start_ingest_job([upload["upload_id"]])
repeated = self.manager.start_ingest_job([upload["upload_id"]])
self.assertEqual(repeated["job_id"], first["job_id"])
self.assertEqual(repeated["state"], "running")
popen_mock.assert_called_once()
def test_start_ingest_job_rejects_duplicate_upload_ids(self):
upload = self._complete_upload("source.pdf", b"source")
with self.assertRaisesRegex(IngestError, "upload_ids must be unique"):
self.manager.start_ingest_job([upload["upload_id"], upload["upload_id"]])
def test_cleanup_runtime_prunes_expired_tickets_and_old_completed_artifacts(self):
upload = self._complete_upload("old.pdf", b"old payload")
ticket = self.manager.create_upload_ticket(expires_seconds=60, max_files=1)
@@ -476,6 +618,18 @@ class McpIngestTests(unittest.TestCase):
raise subprocess.TimeoutExpired(args, kwargs.get("timeout"))
raise AssertionError(f"unexpected subprocess.run args: {args}")
def _json_text_event(self, text):
return json.dumps(
{
"type": "text",
"part": {
"type": "text",
"text": text,
"metadata": {"openai": {"phase": "final_answer"}},
},
}
) + "\n"
class FakePopen:
def __init__(self, returncode=None):