← Files Codex ReplayARCHIVED FILE

tests/test_controller_auth.py

14.1 KB · Nov 3, 2026 · 18:31 UTC

↓ Download file

See the change to this file →

"""Browser credential lifecycle and authenticated controller compatibility."""

from __future__ import annotations

import hashlib
import hmac
import http.client
import json
import subprocess
import tempfile
import threading
import unittest
from pathlib import Path
from unittest import mock

from test_mcp_server import CONTROLLER_PATH, load_server


class ControllerAuthTests(unittest.TestCase):
    def test_legacy_controller_is_verified_but_not_reusable(self) -> None:
        server = load_server()
        token = "synthetic-control-" + "x" * 48

        def reply(method, url):
            challenge = server.urllib.parse.parse_qs(server.urllib.parse.urlsplit(url).query)[
                "challenge"
            ][0]
            return 200, json.dumps(
                {
                    "server": server.SERVER_NAME,
                    "protocol_version": 1,
                    "proof": hmac.new(
                        token.encode(), challenge.encode(), hashlib.sha256
                    ).hexdigest(),
                }
            ).encode()

        with (
            mock.patch.object(
                server, "_read_controller_runtime", return_value={"control_token": token}
            ),
            mock.patch.object(server, "_http_request", side_effect=reply),
        ):
            status, _ = server._probe_controller(43117)
        self.assertEqual(status, "legacy")

    def test_browser_fragment_refresh_invalid_link_and_api_headers(self) -> None:
        harness = r"""
const fs = require('node:fs'), vm = require('node:vm'), assert = require('node:assert/strict');
const file = process.argv[1], source = fs.readFileSync(file, 'utf8');
const script = source.split('<script>')[1].split('</script>')[0];
const saved = new Map(), requests = [];
function load(hash, blockedStorage = false) {
  const app = {innerHTML:'',addEventListener() {}};
  const location = {hash,pathname:'/',search:''};
  const context = vm.createContext({
    console, location,
    window:{addEventListener(){}},
    history:{replaceState(a,b,url){assert.equal(url,'/');location.hash='';}},
    sessionStorage:{getItem:k=>saved.get(k),setItem(k,v){if(blockedStorage)throw Error('blocked');saved.set(k,v);},removeItem:k=>saved.delete(k)},
    localStorage:{getItem:()=>null,setItem(){},removeItem(){}},
    document:{getElementById:()=>app,createElement:()=>({click(){},remove(){}}),body:{append(){}}},
    URL:{createObjectURL:()=> 'blob:fixture',revokeObjectURL(){}},
    fetch:async (url, options)=>{requests.push({url,options});return {ok:true,json:async()=>({structuredContent:{ok:true}}),blob:async()=>({})};},
  });
  vm.runInContext(fs.readFileSync(require('node:path').join(require('node:path').dirname(file),'controller-ranges.js'),'utf8'),context);
  vm.runInContext(script.replace('      initialize();', 'render = () => {}; window.test = {accessToken, callTool, heartbeatController, downloadReport, state};'), context);
  assert.equal(location.hash,'');
  return context.window.test;
}
(async()=>{
 const token='a'.repeat(64), fresh='b'.repeat(64);
 let api=load('#token='+token); assert.equal(api.accessToken,token);
 api=load(''); assert.equal(api.accessToken,token);
 api.state.runId='fixture'; await api.callTool('get_state'); await api.heartbeatController(); await api.downloadReport('json');
 assert.deepEqual(requests.map(r=>r.url),['/api/call','/api/heartbeat','/api/download']);
 for(const r of requests)assert.equal(r.options.headers['X-Codex-Replay-Access'],token);
 assert.equal(load('#token='+fresh).accessToken,fresh);
 assert.equal(load('#token=malformed').accessToken,''); assert.equal(load('').accessToken,'');
 assert.equal(load('#token='+token,true).accessToken,token);
})().catch(e=>{console.error(e);process.exitCode=1;});
"""
        subprocess.run(
            ["node", "-e", harness, str(CONTROLLER_PATH)],
            check=True,
            capture_output=True,
            text=True,
            timeout=15,
        )

    def test_loopback_controller_requires_browser_authentication(self) -> None:
        server = load_server()
        control_token = "control-" + ("x" * 48)
        httpd = server._ControllerHTTPServer(("127.0.0.1", 0), control_token)
        thread = threading.Thread(target=httpd.serve_forever, daemon=True)
        thread.start()
        port = int(httpd.server_address[1])

        def request(
            method: str,
            path: str,
            *,
            body: str | None = None,
            headers: dict[str, str] | None = None,
        ) -> tuple[int, dict[str, str], bytes]:
            connection = http.client.HTTPConnection("127.0.0.1", port, timeout=5)
            try:
                connection.request(
                    method,
                    path,
                    body=body,
                    headers={
                        server.CONTROLLER_BROWSER_HEADER: httpd.browser_token,
                        **(headers or {}),
                    },
                )
                response = connection.getresponse()
                return response.status, dict(response.getheaders()), response.read()
            finally:
                connection.close()

        try:
            with (
                mock.patch.object(server, "_call_tool") as dispatch,
                mock.patch.object(httpd, "touch_heartbeat") as heartbeat,
                mock.patch.object(server, "_safe_run_directory") as read_run,
            ):
                for route in ("/api/call", "/api/download", "/api/heartbeat"):
                    for token in ("", "wrong", control_token, "x" * 64):
                        status, _, _ = request(
                            "POST",
                            route,
                            body="{}",
                            headers={
                                "Origin": httpd.origin,
                                "Content-Type": "application/json",
                                server.CONTROLLER_BROWSER_HEADER: token,
                            },
                        )
                        self.assertEqual(status, 401, (route, token))
                dispatch.assert_not_called()
                heartbeat.assert_not_called()
                read_run.assert_not_called()
            status, _, _ = request(
                "POST",
                "/api/call",
                body="{}",
                headers={
                    "Host": "attacker.invalid",
                    "Origin": httpd.origin,
                    "Content-Type": "application/json",
                },
            )
            self.assertEqual(status, 421)
            challenge = "test-challenge"
            status, _, body = request("GET", f"/health?challenge={challenge}")
            self.assertEqual(status, 200)
            health = json.loads(body)
            self.assertEqual(health["server"], "codex-replay")
            self.assertNotIn(httpd.browser_token.encode(), body)
            self.assertNotIn(control_token.encode(), body)
            self.assertEqual(
                health["proof"],
                hmac.new(control_token.encode(), challenge.encode(), hashlib.sha256).hexdigest(),
            )

            status, _, _ = request("GET", "/auth?token=unused")
            self.assertEqual(status, 404)
            status, _, _ = request("POST", "/api/launch", body="{}")
            self.assertEqual(status, 404)

            status, _, body = request(
                "POST",
                "/api/heartbeat",
                body="{}",
                headers={
                    "Content-Type": "application/json",
                    "Origin": httpd.origin,
                },
            )
            self.assertEqual(status, 200)
            heartbeat = json.loads(body)
            self.assertTrue(heartbeat["ok"])
            self.assertEqual(heartbeat["controller_session_id"], httpd.controller_session_id)
            self.assertGreater(heartbeat["heartbeat_interval_seconds"], 0)

            status, _, _ = request(
                "POST",
                "/api/heartbeat",
                body="{}",
                headers={
                    "Content-Type": "application/json",
                    "Origin": "https://example.com",
                },
            )
            self.assertEqual(status, 403)

            status, _, body = request("GET", "/")
            self.assertEqual(status, 200)
            self.assertIn(b"codex-replay.controller-draft.v7", body)
            self.assertNotIn(httpd.browser_token.encode(), body)
            self.assertNotIn(b"window.openai", body)
            self.assertNotIn(b"controller-session", body)
            status, headers, body = request("GET", "/controller.css")
            self.assertEqual(status, 200)
            self.assertEqual(headers["Content-Type"], "text/css; charset=utf-8")
            self.assertIn(b":root", body)
            status, headers, body = request("GET", "/controller-ranges.js")
            self.assertEqual(status, 200)
            self.assertEqual(headers["Content-Type"], "text/javascript; charset=utf-8")
            self.assertEqual(body, server.APP_RANGES.read_bytes())
            self.assertIn("script-src 'self'", headers["Content-Security-Policy"])

            result = {
                "content": [{"type": "text", "text": "Ready."}],
                "structuredContent": {"state": {"models": []}},
            }
            with mock.patch.object(server, "_call_tool", return_value=result):
                status, _, body = request(
                    "POST",
                    "/api/call",
                    body=json.dumps({"name": "get_state", "arguments": {}}),
                    headers={
                        "Content-Type": "application/json",
                        "Origin": httpd.origin,
                    },
                )
                self.assertEqual(status, 200)
                self.assertEqual(json.loads(body), result)

                status, _, _ = request(
                    "POST",
                    "/api/call",
                    body=json.dumps({"name": "get_state", "arguments": {}}),
                    headers={
                        "Content-Type": "application/json",
                        "Origin": "https://example.com",
                    },
                )
                self.assertEqual(status, 403)

                status, _, _ = request(
                    "POST",
                    "/api/call",
                    body=json.dumps({"name": "get_state", "arguments": {}}),
                    headers={"Content-Type": "application/json"},
                )
                self.assertEqual(status, 403)

                status, _, _ = request(
                    "POST",
                    "/api/call",
                    body=json.dumps({"name": "open_controller", "arguments": {}}),
                    headers={
                        "Content-Type": "application/json",
                        "Origin": httpd.origin,
                    },
                )
                self.assertEqual(status, 400)
        finally:
            httpd.shutdown()
            thread.join(timeout=5)
            httpd.server_close()

    def test_authenticated_same_origin_report_downloads(self) -> None:
        server = load_server()
        with tempfile.TemporaryDirectory() as temporary:
            run_root = Path(temporary).resolve()
            run_directory = run_root / "run-1"
            run_directory.mkdir()
            artifacts = {
                "json": (
                    b'{"status":"completed","original_request":"'
                    + b"x" * (19 * 1024 * 1024)
                    + b'"}\n',
                    "application/json; charset=utf-8",
                ),
                "html": (
                    b"<!doctype html><title>Replay report</title>" + b"x" * (19 * 1024 * 1024),
                    "text/html; charset=utf-8",
                ),
            }
            for artifact_format, (body, _) in artifacts.items():
                (run_directory / f"report.{artifact_format}").write_bytes(body)

            with mock.patch.object(server, "RUN_ROOT", run_root):
                httpd = server._ControllerHTTPServer(
                    ("127.0.0.1", 0),
                    "control-" + ("x" * 48),
                )
                thread = threading.Thread(target=httpd.serve_forever, daemon=True)
                thread.start()
                port = int(httpd.server_address[1])

                def request(
                    artifact_format: str,
                    *,
                    origin: str | None = None,
                ) -> tuple[int, dict[str, str], bytes]:
                    connection = http.client.HTTPConnection("127.0.0.1", port, timeout=5)
                    headers = {
                        "Content-Type": "application/json",
                        "Origin": httpd.origin if origin is None else origin,
                        server.CONTROLLER_BROWSER_HEADER: httpd.browser_token,
                    }
                    try:
                        connection.request(
                            "POST",
                            "/api/download",
                            body=json.dumps({"run_id": "run-1", "format": artifact_format}),
                            headers=headers,
                        )
                        response = connection.getresponse()
                        return response.status, dict(response.getheaders()), response.read()
                    finally:
                        connection.close()

                try:
                    for artifact_format, (expected_body, content_type) in artifacts.items():
                        status, headers, body = request(artifact_format)
                        self.assertEqual(status, 200)
                        self.assertEqual(body, expected_body)
                        self.assertEqual(headers["Content-Type"], content_type)
                        self.assertEqual(
                            headers["Content-Disposition"],
                            f'attachment; filename="codex-replay-run-1-report.{artifact_format}"',
                        )

                    status, _, _ = request("html", origin="https://example.com")
                    self.assertEqual(status, 403)
                finally:
                    httpd.shutdown()
                    thread.join(timeout=5)
                    httpd.server_close()

SHA-256: 71b689dafd781d4bb2438a51de5c8adfe9ddca2d8a162742a2355212521895ab