diff --git a/backend/app/services/c2/mythic.py b/backend/app/services/c2/mythic.py index c430afa..cb83221 100644 --- a/backend/app/services/c2/mythic.py +++ b/backend/app/services/c2/mythic.py @@ -1,7 +1,8 @@ # Contract pinned from MythicMeta/Mythic_Scripting master @ 2026-06-10 (raw.githubusercontent.com/MythicMeta/Mythic_Scripting/master/mythic/mythic.py) +# Sprint 10 correction: /graphql/ MUST have trailing slash (matches mythic_utilities.get_http_transport). """Mythic 3.x C2 adapter. -Transport: POST https://:7443/graphql +Transport: POST https://:7443/graphql/ Header: apitoken: Backend: Hasura-proxied Postgres behind nginx. @@ -116,7 +117,7 @@ class MythicAdapter(C2Adapter): """Real Mythic 3.x adapter using GraphQL over HTTP.""" def __init__(self, url: str, api_token: str, verify_tls: bool = False) -> None: - self._url = url.rstrip("/") + "/graphql" + self._url = url.rstrip("/") + "/graphql/" self._token = api_token self._verify = verify_tls if not verify_tls: diff --git a/backend/tests/test_c2_adapter_mythic.py b/backend/tests/test_c2_adapter_mythic.py index de32055..3ec77c2 100644 --- a/backend/tests/test_c2_adapter_mythic.py +++ b/backend/tests/test_c2_adapter_mythic.py @@ -9,7 +9,7 @@ from backend.app.services.c2.adapter import C2Error from backend.app.services.c2.mythic import MythicAdapter _BASE_URL = "https://mythic.lab:7443" -_GQL_URL = _BASE_URL + "/graphql" +_GQL_URL = _BASE_URL + "/graphql/" _TOKEN = "fake-api-token" @@ -143,9 +143,19 @@ class TestMythicAdapterNoRedirects: """Adapter must not follow HTTP redirects (allow_redirects=False).""" with rm_module.Mocker() as m: # Simulate a redirect response; requests-mock won't auto-follow it. - m.post(_GQL_URL, status_code=301, headers={"Location": "https://evil.example/graphql"}) + m.post(_GQL_URL, status_code=301, headers={"Location": "https://evil.example/graphql/"}) # With allow_redirects=False the 301 is treated as a non-2xx → raise_for_status raises. with pytest.raises(C2Error): adapter.list_callbacks() # Exactly one request was made — no follow-up to Location. assert len(m.request_history) == 1 + + +class TestMythicAdapterUrlConstruction: + def test_mythic_adapter_url_has_trailing_slash(self): + a1 = MythicAdapter(url="https://x:7443", api_token="t") + assert a1._url == "https://x:7443/graphql/" + a2 = MythicAdapter(url="https://x:7443/", api_token="t") + assert a2._url == "https://x:7443/graphql/" + a3 = MythicAdapter(url="https://x:7443///", api_token="t") + assert a3._url == "https://x:7443/graphql/" diff --git a/backend/tests/test_c2_adapter_mythic_m3.py b/backend/tests/test_c2_adapter_mythic_m3.py index e6d5963..a749616 100644 --- a/backend/tests/test_c2_adapter_mythic_m3.py +++ b/backend/tests/test_c2_adapter_mythic_m3.py @@ -9,7 +9,7 @@ from backend.app.services.c2.adapter import C2Error from backend.app.services.c2.mythic import MythicAdapter _BASE_URL = "https://mythic.lab:7443" -_GQL_URL = _BASE_URL + "/graphql" +_GQL_URL = _BASE_URL + "/graphql/" _TOKEN = "fake-api-token" diff --git a/backend/tests/test_c2_adapter_mythic_m4.py b/backend/tests/test_c2_adapter_mythic_m4.py index 5103185..206eb76 100644 --- a/backend/tests/test_c2_adapter_mythic_m4.py +++ b/backend/tests/test_c2_adapter_mythic_m4.py @@ -9,7 +9,7 @@ from backend.app.services.c2.adapter import C2Error, C2HistoricalTask from backend.app.services.c2.mythic import MythicAdapter _BASE_URL = "https://mythic.lab:7443" -_GQL_URL = _BASE_URL + "/graphql" +_GQL_URL = _BASE_URL + "/graphql/" _TOKEN = "fake-api-token"