From cee181ad2d3ca6a827496f5d1e64b2f6d7f821a3 Mon Sep 17 00:00:00 2001 From: Imran Ahamed Date: Sat, 25 Jul 2026 07:16:40 -0500 Subject: [PATCH] FIX GCG: forward hf_token as None instead of an empty string GCGGenerator._to_attack_params built params with token=self._hf_token or "". Since hf_token defaults to None (correct for a public model), the or "" turned it into an empty string, which huggingface_hub sends as 'Authorization: Bearer ' with no credential. httpx rejects that as an illegal header, so GCG could not load any ungated model. None is HuggingFace's value for anonymous access, so it is now forwarded unchanged. Widens ModelWorker.__init__'s token parameter to str | None to match, and adds regression tests for both the absent-token and supplied-token cases. --- .../gcg/attack/base/attack_manager.py | 2 +- pyrit/executor/promptgen/gcg/generator.py | 5 ++++- .../executor/promptgen/gcg/test_generator.py | 21 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/pyrit/executor/promptgen/gcg/attack/base/attack_manager.py b/pyrit/executor/promptgen/gcg/attack/base/attack_manager.py index efd0fc7f30..25b497b9a1 100644 --- a/pyrit/executor/promptgen/gcg/attack/base/attack_manager.py +++ b/pyrit/executor/promptgen/gcg/attack/base/attack_manager.py @@ -1819,7 +1819,7 @@ class ModelWorker: def __init__( self, model_path: str, - token: str, + token: str | None, model_kwargs: dict[str, Any], tokenizer: Any, device: str, diff --git a/pyrit/executor/promptgen/gcg/generator.py b/pyrit/executor/promptgen/gcg/generator.py index 4c6550293e..14995110ff 100644 --- a/pyrit/executor/promptgen/gcg/generator.py +++ b/pyrit/executor/promptgen/gcg/generator.py @@ -410,7 +410,10 @@ def _to_attack_params(self, *, context: GCGContext) -> Any: all_models = self._models + self._test_models return SimpleNamespace( - token=self._hf_token or "", + # None means anonymous access. An empty string makes huggingface_hub send + # "Authorization: Bearer " with no credential, which httpx rejects as an + # illegal header value, so public models would fail to load. + token=self._hf_token, tokenizer_paths=[m.name for m in all_models], tokenizer_kwargs=[m.tokenizer_kwargs for m in all_models], model_paths=[m.name for m in all_models], diff --git a/tests/unit/executor/promptgen/gcg/test_generator.py b/tests/unit/executor/promptgen/gcg/test_generator.py index f35e90db9a..af19732a06 100644 --- a/tests/unit/executor/promptgen/gcg/test_generator.py +++ b/tests/unit/executor/promptgen/gcg/test_generator.py @@ -158,6 +158,27 @@ def test_test_length_mismatch_raises(self) -> None: ) +class TestToAttackParamsToken: + """The HuggingFace token forwarded to model loading.""" + + def test_token_is_none_when_not_supplied(self) -> None: + # An empty string makes huggingface_hub send "Authorization: Bearer " with no + # credential, which httpx rejects as an illegal header value. None means anonymous, + # which is what loading a public model needs. + gen = GCGGenerator(models=[GCGModelConfig(name=_LLAMA_2)]) + + params = gen._to_attack_params(context=GCGContext(goals=["g"], targets=["t"])) + + assert params.token is None + + def test_token_is_forwarded_when_supplied(self) -> None: + gen = GCGGenerator(models=[GCGModelConfig(name=_LLAMA_2)], hf_token="hf_example") + + params = gen._to_attack_params(context=GCGContext(goals=["g"], targets=["t"])) + + assert params.token == "hf_example" + + @pytest.fixture def patched_get_workers(): """Patch the heavy worker spawn so lifecycle tests don't try to load real models."""