import pytest from akos.gateway import ssrf def test_ssrf_blocks_private_and_metadata_targets(): for url in ("http://169.254.169.254/latest/meta-data/", "http://127.0.0.1:8520/", "http://localhost/", "http://10.0.0.1/", "http://192.168.2.1/", "http://metadata.google.internal/", "file:///etc/passwd", "http://user:pw@example.com/", "http://[::1]/", "http://0.0.0.0/", "http://100.100.100.100/"): with pytest.raises(ssrf.SSRFError): ssrf.validate(url, allow_local=False) t = ssrf.validate("https://api.worldbank.org/v2/", allow_local=False) assert t.host == "api.worldbank.org" and t.port == 443 and t.ip async def test_redirect_to_private_is_refused(app_client, mock_api, monkeypatch): """Even with loopback allowed for the mock, the redirect target (cloud metadata) is validated and refused.""" c = app_client; h = c.auth_headers await c.post("/api/v1/projects", json={"slug": "ssrf", "name": "ssrf", "policy_mode": "discovery"}, headers=h) import akos.gateway.executor as ex real = ssrf.validate def strict(url, allow_local=None): return real(url, allow_local=("169.254" not in url)) monkeypatch.setattr(ex.ssrf, "validate", strict) r = await c.post("/api/v1/execute", json={"project_id": "ssrf", "url": mock_api["base"] + "/redirect-private"}, headers=h) assert r.status_code == 200 and r.json()["state"] == "failed" and "ssrf" in (r.json()["error"] or "") async def test_prompt_injection_in_docs_is_inert(app_client, pool, mock_api): from tests.conftest import run_job_inline c = app_client; h = c.auth_headers res = await run_job_inline(pool, "discover", {"project_id": "default", "openapi_url": mock_api["base"] + "/openapi.json", "name": "Mock Data API", "provider_slug": "mock", "license": "CC0-1.0", "created_by": "t"}) pkg = (await c.get(f"/api/v1/akps/{res['package_id']}", headers=h)).json() src = (await c.get(f"/api/v1/akps/{pkg['id']}/files/client.py", headers=h)).text assert pkg["manifest"]["static_scan"]["ok"] assert "IGNORE PREVIOUS INSTRUCTIONS" in src and "rm -rf" in src # kept verbatim as a string literal (data) import ast tree = ast.parse(src) assert not any(isinstance(n, ast.Call) and getattr(n.func, "id", "") in ("eval", "exec") for n in ast.walk(tree)) readme = (await c.get(f"/api/v1/akps/{pkg['id']}/files/README.md", headers=h)).text assert "rm -rf" not in readme or "IGNORE" in readme # description is rendered as text in docs, never executed def test_rate_limit_token_bucket(): from akos.api.deps import rate_limit, ApiError for _ in range(5): rate_limit("rl-test", limit=5, window=60) with pytest.raises(ApiError) as e: rate_limit("rl-test", limit=5, window=60) assert e.value.status_code == 429 async def test_path_traversal_and_validation(app_client): c = app_client; h = c.auth_headers pk = (await c.get("/api/v1/akps", headers=h)).json()["packages"] if pk: r = await c.get(f"/api/v1/akps/{pk[0]['id']}/files/../../etc/passwd", headers=h); assert r.status_code in (400, 404) r = await c.get(f"/api/v1/akps/{pk[0]['id']}/files/%2e%2e%2f.env", headers=h); assert r.status_code in (400, 404) assert (await c.get("/api/v1/artifacts/not-a-sha", headers=h)).status_code == 404 assert (await c.post("/api/v1/execute", json={"project_id": "default", "url": "x", "method": "TRACE"}, headers=h)).status_code == 422 assert (await c.get("/docs/md/..%2F..%2F.env", headers=h)).status_code in (400, 404) assert (await c.get("/docs/md/nonexistent-doc", headers=h)).status_code == 404 import httpx anon = httpx.AsyncClient(transport=c._transport, base_url="http://test") assert (await anon.get("/docs")).status_code == 401 and (await c.get("/docs", headers=h)).status_code == 200 r = await c.get("/api/v1/health"); assert r.status_code == 200 and "X-Content-Type-Options" in r.headers and r.headers.get("Strict-Transport-Security") async def test_mcp_endpoint_auth_and_tools(app_client): c = app_client; h = c.auth_headers import httpx anon = httpx.AsyncClient(transport=c._transport, base_url="http://test") assert (await anon.post("/mcp", json={"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}})).status_code == 401 r = await c.post("/mcp", json={"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18"}}, headers=h) assert r.status_code == 200 and r.json()["result"]["serverInfo"]["name"] == "akos" r = await c.post("/mcp", json={"jsonrpc": "2.0", "id": 2, "method": "tools/list"}, headers=h) names = {t["name"] for t in r.json()["result"]["tools"]} assert names >= {"search_knowledge", "inspect_api", "discover_api", "execute_api", "generate_client", "verify_package", "list_knowledge_debt", "compose_recipe", "export_research_dataset", "get_execution"} r = await c.post("/mcp", json={"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "akos.status", "arguments": {}}}, headers=h) assert r.status_code == 200 and "counts" in r.json()["result"]["content"][0]["text"] r = await c.post("/mcp", json={"jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": {"name": "execute_api", "arguments": {"project_id": "default", "url": "http://169.254.169.254/"}}}, headers=h) assert r.json()["result"]["isError"] is True