Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions agent-pipe-uploader/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ RUN pip install --no-cache-dir "fastmcp==${FASTMCP_VERSION}" \

WORKDIR /app
COPY server.py /app/server.py
COPY test_server.py /tmp/test_server.py

RUN PYTHONPATH=/app python3 /tmp/test_server.py \
&& rm -f /tmp/test_server.py

USER 1000

Expand Down
3 changes: 3 additions & 0 deletions agent-pipe-uploader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ The chart mounts a non-secret profile file at
`/etc/agent-pipe/profiles.json` and an isolated artifact PVC at `/artifacts`.
The service exposes Streamable HTTP MCP at `/mcp` and health at `/healthz`.
It does not log signed URLs or artifact bytes.

`test_server.py` is run during the image build and covers artifact-root escape
rejection plus signed-URL host, prefix, and authorization-parameter validation.
77 changes: 77 additions & 0 deletions agent-pipe-uploader/test_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import importlib
import json
import os
import tempfile
import unittest
from pathlib import Path


class ServerTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temporary_directory = tempfile.TemporaryDirectory()
cls.root = Path(cls.temporary_directory.name)
cls.artifacts = cls.root / "artifacts"
cls.artifacts.mkdir()
cls.profiles = cls.root / "profiles.json"
cls.profiles.write_text(
json.dumps(
{
"profiles": {
"agent-pipe": {
"allowedHosts": ["agent-pipe.s3.us-west-2.amazonaws.com"],
"pathPrefixes": ["/deliveries/"],
"requiredQueryParameters": ["X-Amz-Algorithm", "X-Amz-Signature"],
"maxBytes": 1024,
}
}
}
)
)
os.environ["ARTIFACT_ROOT"] = str(cls.artifacts)
os.environ["PROFILE_CONFIG_PATH"] = str(cls.profiles)
os.environ["MCP_ALLOWED_HOSTS"] = "agent-pipe-uploader.opencode.svc"
cls.server = importlib.import_module("server")

@classmethod
def tearDownClass(cls):
cls.temporary_directory.cleanup()

def test_resolves_existing_artifact(self):
artifact = self.artifacts / "sample.txt"
artifact.write_text("test")
self.assertEqual(self.server.artifact_path("sample.txt", must_exist=True), artifact)

def test_rejects_artifact_path_escape(self):
with self.assertRaises(self.server.TransferError):
self.server.artifact_path("../outside", must_exist=True)

def test_validates_profile_signed_url(self):
profile, host, target = self.server.signed_target(
"agent-pipe",
"https://agent-pipe.s3.us-west-2.amazonaws.com/deliveries/test.txt?"
"X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Signature=example",
)
self.assertEqual(profile["maxBytes"], 1024)
self.assertEqual(host, "agent-pipe.s3.us-west-2.amazonaws.com")
self.assertTrue(target.startswith("/deliveries/test.txt?"))

def test_rejects_unapproved_signed_url(self):
with self.assertRaises(self.server.TransferError):
self.server.signed_target(
"agent-pipe",
"https://example.com/deliveries/test.txt?"
"X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Signature=example",
)

def test_rejects_disallowed_object_prefix(self):
with self.assertRaises(self.server.TransferError):
self.server.signed_target(
"agent-pipe",
"https://agent-pipe.s3.us-west-2.amazonaws.com/private/test.txt?"
"X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Signature=example",
)


if __name__ == "__main__":
unittest.main()
Loading