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
28 changes: 18 additions & 10 deletions pyrit/cli/pyrit_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from argparse import ArgumentParser, Namespace, RawDescriptionHelpFormatter
from pathlib import Path
from typing import TYPE_CHECKING, Any, get_args, get_origin
from urllib.parse import urlparse

from pyrit.cli._cli_args import (
ARG_HELP,
Expand Down Expand Up @@ -444,21 +445,30 @@ async def _resolve_server_url_async(*, parsed_args: Namespace) -> str | None:

# Auto-start if requested
if parsed_args.start_server:
# The launcher can only bind localhost:8000. If the user explicitly
# configured a different URL we can't honor it — refuse rather than
# silently start a server the user can't reach.
if base_url != DEFAULT_SERVER_URL:
parsed_url = urlparse(base_url)
if (
parsed_url.scheme != "http"
or parsed_url.hostname not in {"localhost", "127.0.0.1"}
or parsed_url.username is not None
or parsed_url.password is not None
or parsed_url.path not in {"", "/"}
or parsed_url.query
or parsed_url.fragment
):
print(
f"Error: cannot --start-server because the configured server URL ({base_url}) "
f"does not match the launcher default ({DEFAULT_SERVER_URL}). "
"Either remove --server-url / the server.url config entry, "
"or start the backend manually with `pyrit_backend --host ... --port ...`.",
"is not a plain local HTTP URL. Use localhost or 127.0.0.1, "
"or start a remote backend separately.",
file=sys.stderr,
)
return None
launcher = ServerLauncher()
try:
return await launcher.start_async(config_file=parsed_args.config_file)
return await launcher.start_async(
host=parsed_url.hostname,
port=parsed_url.port or 80,
config_file=parsed_args.config_file,
)
except RuntimeError as exc:
print(f"Error: {exc}")
return None
Expand Down Expand Up @@ -504,8 +514,6 @@ async def _handle_stop_server_async(*, parsed_args: Namespace) -> int:
Returns:
int: Exit code (always ``0``).
"""
from urllib.parse import urlparse

from pyrit.cli._server_launcher import ServerLauncher, stop_server_on_port

base_url = _resolve_configured_server_url(parsed_args=parsed_args)
Expand Down
21 changes: 18 additions & 3 deletions tests/unit/cli/test_pyrit_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -764,9 +764,7 @@ async def test_returns_none_when_start_server_raises(self, capsys):
assert await pyrit_scan._resolve_server_url_async(parsed_args=parsed) is None
assert "nope" in capsys.readouterr().out

async def test_start_server_refuses_when_url_differs_from_default(self, capsys):
# User explicitly configured a non-default URL but asks us to launch the bundled
# backend. The launcher only knows how to bind localhost:8000, so we must refuse.
async def test_start_server_refuses_remote_url(self, capsys):
parsed = Namespace(server_url="http://other:9999", start_server=True, config_file=None)
start_async_mock = AsyncMock()
with (
Expand All @@ -786,6 +784,23 @@ async def test_start_server_refuses_when_url_differs_from_default(self, capsys):
assert "cannot --start-server" in err
assert "http://other:9999" in err

async def test_start_server_uses_custom_local_port(self):
parsed = Namespace(server_url="http://127.0.0.1:8765", start_server=True, config_file=None)
start_async_mock = AsyncMock(return_value="http://127.0.0.1:8765")
with (
patch(
"pyrit.cli._server_launcher.ServerLauncher.probe_health_async",
new=AsyncMock(return_value=False),
),
patch(
"pyrit.cli._server_launcher.ServerLauncher.start_async",
new=start_async_mock,
),
):
result = await pyrit_scan._resolve_server_url_async(parsed_args=parsed)
assert result == "http://127.0.0.1:8765"
start_async_mock.assert_awaited_once_with(host="127.0.0.1", port=8765, config_file=None)

async def test_resolution_order_cli_beats_config_beats_default(self):
"""CLI flag > config-file value > built-in default."""
# 1) CLI flag wins even when config has a different value.
Expand Down