agent-zero/tests/test_csrf_tunnel_origins.py
Alessandro ca4efe6e6a Fix Tailscale Remote Control CSRF origins
Normalize active Remote Control URLs to same-origin values before adding them to CSRF allowlists, so Tailscale Funnel URLs with paths or trailing slashes can bootstrap tokens correctly.

Allow WebSocket origin validation to trust only the currently active Remote Control origin, including Docker split-process tunnel service URLs, while preserving rejection for unrelated external origins.

Add focused regression coverage for active Tailscale-style origins, tunnel-service origin lookup, and negative cross-origin cases; keep run_ui decorator re-exports compatible with existing CSRF tests.
2026-06-04 14:54:39 +02:00

153 lines
4.2 KiB
Python

from __future__ import annotations
import json
from types import SimpleNamespace
import pytest
from flask import Flask
@pytest.mark.asyncio
async def test_csrf_token_allows_normalized_active_tailscale_origin(monkeypatch):
import api.csrf_token as csrf_module
import api.tunnel_proxy as tunnel_proxy
handler = csrf_module.GetCsrfToken(Flask("test_csrf_tunnel_origins"), None)
request = SimpleNamespace(
headers={"Origin": "https://agent-zero.tailabc.ts.net"},
environ={},
referrer=None,
)
monkeypatch.setattr(csrf_module.login, "is_login_required", lambda: False)
monkeypatch.setattr(
csrf_module.dotenv,
"get_dotenv_value",
lambda key: "http://localhost:32080"
if key == csrf_module.ALLOWED_ORIGINS_KEY
else "",
)
async def fake_tunnel_process(input_data):
return {
"success": True,
"tunnel_url": "https://agent-zero.tailabc.ts.net/funnel-ready/",
"is_running": True,
}
monkeypatch.setattr(tunnel_proxy, "process", fake_tunnel_process)
origin_check = await handler.check_allowed_origin(request)
assert origin_check["ok"] is True
assert "https://agent-zero.tailabc.ts.net" in origin_check["allowed_origins"]
@pytest.mark.asyncio
async def test_csrf_token_rejects_unrelated_origin_with_active_tunnel(monkeypatch):
import api.csrf_token as csrf_module
import api.tunnel_proxy as tunnel_proxy
handler = csrf_module.GetCsrfToken(Flask("test_csrf_tunnel_origins"), None)
request = SimpleNamespace(
headers={"Origin": "https://evil.example"},
environ={},
referrer=None,
)
monkeypatch.setattr(csrf_module.login, "is_login_required", lambda: False)
monkeypatch.setattr(
csrf_module.dotenv,
"get_dotenv_value",
lambda key: "http://localhost:32080"
if key == csrf_module.ALLOWED_ORIGINS_KEY
else "",
)
async def fake_tunnel_process(input_data):
return {
"success": True,
"tunnel_url": "https://agent-zero.tailabc.ts.net/funnel-ready/",
"is_running": True,
}
monkeypatch.setattr(tunnel_proxy, "process", fake_tunnel_process)
origin_check = await handler.check_allowed_origin(request)
assert origin_check["ok"] is False
def test_active_tunnel_origins_include_docker_tunnel_service_url(monkeypatch):
import helpers.tunnel_origins as tunnel_origins
monkeypatch.setattr(
tunnel_origins,
"_get_tunnel_service_url",
lambda: "https://agent-zero.tailabc.ts.net/funnel-ready/",
)
assert (
"https://agent-zero.tailabc.ts.net"
in tunnel_origins.get_active_tunnel_origins()
)
def test_tunnel_service_url_uses_short_local_get_request(monkeypatch):
from helpers import dotenv, runtime
import helpers.tunnel_origins as tunnel_origins
captured = {}
class FakeResponse:
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return None
def read(self):
return json.dumps({
"success": True,
"tunnel_url": "https://agent-zero.tailabc.ts.net/funnel-ready/",
}).encode("utf-8")
def fake_urlopen(request, timeout):
captured["url"] = request.full_url
captured["body"] = request.data
captured["method"] = request.get_method()
captured["timeout"] = timeout
return FakeResponse()
monkeypatch.setattr(
runtime,
"is_dockerized",
lambda: True,
)
monkeypatch.setattr(
runtime,
"get_arg",
lambda name: None,
)
monkeypatch.setattr(
runtime,
"get_tunnel_api_port",
lambda: 55520,
)
monkeypatch.setattr(
dotenv,
"get_dotenv_value",
lambda key: "",
)
monkeypatch.setattr(tunnel_origins.urllib.request, "urlopen", fake_urlopen)
assert (
tunnel_origins._get_tunnel_service_url()
== "https://agent-zero.tailabc.ts.net/funnel-ready/"
)
assert captured == {
"url": "http://localhost:55520/",
"body": b'{"action": "get"}',
"method": "POST",
"timeout": 0.35,
}