mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
fix(llm): only prepend http:// to OLLAMA_HOST when no scheme is present
ollamaUrl was built as "http://" + OLLAMA_HOST, so any value carrying a scheme
- the form Ollama's own tooling accepts - was mangled:
OLLAMA_HOST=https://ollama.example.com
-> http://https://ollama.example.com
The LLM attack then could not connect, and reaching a remote Ollama over TLS
was impossible. llm.py derives its client base URL as f"{ollamaUrl}/v1", so the
malformation propagated into the OpenAI-compatible client.
Extract _normalize_ollama_url() so the logic is unit-testable - ollamaUrl is
assigned at module import, which is otherwise only reachable via reload - and
prepend the scheme only when absent. Also strip trailing slashes, since callers
append paths. The bare host:port default is unchanged.
Fixes #119
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
9459c21b14
commit
2376ac5c10
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
Dates are omitted for releases predating this file; see the git tags for exact timing.
|
Dates are omitted for releases predating this file; see the git tags for exact timing.
|
||||||
|
|
||||||
|
## [2.13.1] - 2026-07-24
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **`OLLAMA_HOST` values that include a scheme no longer produce a malformed URL**
|
||||||
|
(issue #119). The Ollama base URL was built as `"http://" + OLLAMA_HOST`, so a value in
|
||||||
|
the form Ollama's own tooling accepts — `http://box:11434` or
|
||||||
|
`https://ollama.example.com` — became `http://http://box:11434` and the LLM attack could
|
||||||
|
not connect. `http://` is now prepended only when no scheme is present, and trailing
|
||||||
|
slashes are stripped because callers append paths (`f"{ollamaUrl}/v1"`). Reaching a remote
|
||||||
|
Ollama over TLS works as a result. The bare `host:port` default is unchanged.
|
||||||
|
|
||||||
## [2.13.0] - 2026-07-24
|
## [2.13.0] - 2026-07-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
+18
-1
@@ -361,6 +361,23 @@ else:
|
|||||||
hcatPotfilePath = os.path.join(hate_path, hcatPotfilePath)
|
hcatPotfilePath = os.path.join(hate_path, hcatPotfilePath)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_ollama_url(host: str) -> str:
|
||||||
|
"""Turn an ``OLLAMA_HOST`` value into a usable base URL.
|
||||||
|
|
||||||
|
Ollama's own tooling accepts both a bare ``host:port`` and a full URL, so
|
||||||
|
accept either. Only prepend ``http://`` when no scheme is present;
|
||||||
|
unconditionally prepending it produced URLs like
|
||||||
|
``http://https://ollama.example.com``. Trailing slashes are stripped
|
||||||
|
because callers append paths (``f"{ollamaUrl}/v1"``).
|
||||||
|
"""
|
||||||
|
host = (host or "").strip()
|
||||||
|
if not host:
|
||||||
|
return "http://localhost:11434"
|
||||||
|
if "://" not in host:
|
||||||
|
host = "http://" + host
|
||||||
|
return host.rstrip("/")
|
||||||
|
|
||||||
|
|
||||||
def _maybe_append_username_flag(cmd):
|
def _maybe_append_username_flag(cmd):
|
||||||
"""Append --username if the active hash file has user:hash format and
|
"""Append --username if the active hash file has user:hash format and
|
||||||
the flag isn't already present (from hcatTuning or elsewhere)."""
|
the flag isn't already present (from hcatTuning or elsewhere)."""
|
||||||
@@ -450,7 +467,7 @@ hcatGoodMeasureBaseList = config_parser["hcatGoodMeasureBaseList"]
|
|||||||
|
|
||||||
hcatDebugLogPath = os.path.expanduser(config_parser["hcatDebugLogPath"])
|
hcatDebugLogPath = os.path.expanduser(config_parser["hcatDebugLogPath"])
|
||||||
|
|
||||||
ollamaUrl = "http://" + os.environ.get("OLLAMA_HOST", "localhost:11434")
|
ollamaUrl = _normalize_ollama_url(os.environ.get("OLLAMA_HOST", "localhost:11434"))
|
||||||
ollamaModel = config_parser.get("ollamaModel", "qwen2.5:32b")
|
ollamaModel = config_parser.get("ollamaModel", "qwen2.5:32b")
|
||||||
ollamaNumCtx = int(config_parser.get("ollamaNumCtx", 2048))
|
ollamaNumCtx = int(config_parser.get("ollamaNumCtx", 2048))
|
||||||
ollamaTimeout = float(config_parser.get("ollamaTimeout", 300))
|
ollamaTimeout = float(config_parser.get("ollamaTimeout", 300))
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
"""Tests for OLLAMA_HOST -> ollamaUrl normalization (issue #119).
|
||||||
|
|
||||||
|
``ollamaUrl`` used to be built as ``"http://" + OLLAMA_HOST``, which mangled any
|
||||||
|
value that already carried a scheme -- the form Ollama's own tooling accepts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from hate_crack import main as hc_main
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("host", "expected"),
|
||||||
|
[
|
||||||
|
# Bare host:port -- the historical default, must keep working.
|
||||||
|
("localhost:11434", "http://localhost:11434"),
|
||||||
|
("box:11434", "http://box:11434"),
|
||||||
|
("127.0.0.1:11434", "http://127.0.0.1:11434"),
|
||||||
|
# Scheme already present -- previously produced http://http://...
|
||||||
|
("http://box:11434", "http://box:11434"),
|
||||||
|
("https://ollama.example.com", "https://ollama.example.com"),
|
||||||
|
("https://ollama.example.com:443", "https://ollama.example.com:443"),
|
||||||
|
# Trailing slashes stripped: callers append f"{ollamaUrl}/v1".
|
||||||
|
("http://box:11434/", "http://box:11434"),
|
||||||
|
("https://ollama.example.com///", "https://ollama.example.com"),
|
||||||
|
("box:11434/", "http://box:11434"),
|
||||||
|
# Whitespace and empty values fall back to the default.
|
||||||
|
(" box:11434 ", "http://box:11434"),
|
||||||
|
("", "http://localhost:11434"),
|
||||||
|
(" ", "http://localhost:11434"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_normalize_ollama_url(host, expected):
|
||||||
|
assert hc_main._normalize_ollama_url(host) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_scheme_is_never_doubled():
|
||||||
|
"""The exact regression from issue #119."""
|
||||||
|
for host in ("http://box:11434", "https://ollama.example.com"):
|
||||||
|
assert "http://http" not in hc_main._normalize_ollama_url(host)
|
||||||
|
assert "http://https" not in hc_main._normalize_ollama_url(host)
|
||||||
|
|
||||||
|
|
||||||
|
def test_result_builds_a_valid_v1_endpoint():
|
||||||
|
"""llm.py derives its client base URL as f"{ollamaUrl}/v1"."""
|
||||||
|
for host in ("box:11434", "http://box:11434/", "https://x.example.com//"):
|
||||||
|
url = hc_main._normalize_ollama_url(host)
|
||||||
|
assert f"{url}/v1".count("//") == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_level_ollamaUrl_honours_a_scheme_in_the_env():
|
||||||
|
"""Guard the wiring, not just the helper: ollamaUrl is built at import."""
|
||||||
|
with mock.patch.dict(
|
||||||
|
os.environ, {"OLLAMA_HOST": "https://ollama.example.com"}, clear=False
|
||||||
|
):
|
||||||
|
reloaded = importlib.reload(hc_main)
|
||||||
|
try:
|
||||||
|
assert reloaded.ollamaUrl == "https://ollama.example.com"
|
||||||
|
finally:
|
||||||
|
# Restore the module to the ambient environment for other tests.
|
||||||
|
importlib.reload(reloaded)
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_level_ollamaUrl_defaults_to_localhost():
|
||||||
|
env = {k: v for k, v in os.environ.items() if k != "OLLAMA_HOST"}
|
||||||
|
with mock.patch.dict(os.environ, env, clear=True):
|
||||||
|
reloaded = importlib.reload(hc_main)
|
||||||
|
try:
|
||||||
|
assert reloaded.ollamaUrl == "http://localhost:11434"
|
||||||
|
finally:
|
||||||
|
importlib.reload(reloaded)
|
||||||
Reference in New Issue
Block a user