fix(cookbook): validate adopt host (#4282)

This commit is contained in:
RaresKeY
2026-06-15 17:44:24 +03:00
committed by GitHub
parent 81e7074d93
commit ffd0aaf69b
2 changed files with 51 additions and 1 deletions
+1 -1
View File
@@ -790,7 +790,7 @@ def setup_codex_routes(
norm = dict(body or {}) norm = dict(body or {})
sess = (norm.get("tmux_session") or norm.get("session_id") or "").strip() sess = (norm.get("tmux_session") or norm.get("session_id") or "").strip()
model = (norm.get("model") or norm.get("repo_id") or "").strip() model = (norm.get("model") or norm.get("repo_id") or "").strip()
host = (norm.get("host") or norm.get("remote_host") or "").strip() host = validate_remote_host((norm.get("host") or norm.get("remote_host") or "").strip() or None) or ""
port = norm.get("port") or 8000 port = norm.get("port") or 8000
import re as _re import re as _re
if not sess or not _re.fullmatch(r"[a-zA-Z0-9_-]+", sess): if not sess or not _re.fullmatch(r"[a-zA-Z0-9_-]+", sess):
+50
View File
@@ -7,12 +7,39 @@ in ``remoteHost`` would be injected into that command.
These pin validation on the host/port before they reach the ssh string, matching These pin validation on the host/port before they reach the ssh string, matching
the validators the rest of the cookbook routes already apply. the validators the rest of the cookbook routes already apply.
""" """
import asyncio
import pytest import pytest
from fastapi import HTTPException from fastapi import HTTPException
from starlette.requests import Request
import routes.codex_routes as codex_routes import routes.codex_routes as codex_routes
def _route_endpoint(path: str, method: str):
router = codex_routes.setup_codex_routes()
for route in router.routes:
if route.path == path and method in route.methods:
return route.endpoint
raise AssertionError(f"{method} {path} route not found")
def _launch_request() -> Request:
request = Request(
{
"type": "http",
"method": "POST",
"path": "/api/codex/cookbook/adopt",
"headers": [],
"state": {},
}
)
request.state.api_token = True
request.state.api_token_owner = "alice"
request.state.api_token_scopes = ["cookbook:launch"]
return request
def test_rejects_remote_host_with_shell_metacharacters(): def test_rejects_remote_host_with_shell_metacharacters():
task = {"remoteHost": "box; rm -rf ~", "sshPort": ""} task = {"remoteHost": "box; rm -rf ~", "sshPort": ""}
with pytest.raises(HTTPException) as exc: with pytest.raises(HTTPException) as exc:
@@ -47,3 +74,26 @@ def test_default_ssh_port_omits_flag():
) )
assert host == "box" assert host == "box"
assert port_flag == "" assert port_flag == ""
def test_adopt_rejects_ssh_option_host_before_shell(monkeypatch):
calls = []
async def fail_if_shell_runs(*args, **kwargs):
calls.append((args, kwargs))
raise RuntimeError("shell should not run for invalid host")
monkeypatch.setattr(asyncio, "create_subprocess_shell", fail_if_shell_runs)
endpoint = _route_endpoint("/api/codex/cookbook/adopt", "POST")
body = {
"tmux_session": "serve_abc123",
"model": "org/model",
"host": "-oProxyCommand=sh",
}
with pytest.raises(HTTPException) as exc:
asyncio.run(endpoint(_launch_request(), body))
assert exc.value.status_code == 400
assert calls == []