Repository navigation
[BUG] Normal logged-in Cookie headers are rejected as "Semantic attack: suspicious (score: 0.71)" #161
Description
Value this delivers
Logged-in users are not blocked because of the normal cookies their browser sends.
What happened
Penetration detection scans the whole Cookie header as one string (body_content_scan.py#L306). For the semantic analyzer, an ordinary header with a JWT session cookie gets close to the 0.7 threshold from things almost every such header has:
- the
;between cookies counts as a command separator for thecommandattack type (semantic.py#L186) and ascommand_chainpatterns (#L98); - the JWT counts as obfuscated because of its entropy (#L219) and as two encoding layers, base64 and hex (#L151);
- the decoded JWT claims add
{...}and the@of an email to the scanned text, which count as code-injection risk (#L261).
Ordinary values then add the rest. In the repro below these are theme=system (system is a command keyword) and the name Jane Doe (Acme) in the token (function_call), so the header scores 0.711 and the request is rejected. The same three cookies scored one by one get 0.55, 0.12 and 0.20.
We hit this in production on 3.15.0: within a day, 9 requests from 3 client IPs were rejected with exactly this message. 6 of them were token refresh calls; the refresh cookie doesn't change between retries, so every retry was rejected too.
Expected: the repro prints False.
Steps to reproduce
import asyncio
import base64
import json
import types
from guard_core.models import SecurityConfig
from guard_core.utils import detect_penetration_attempt
def b64url(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b"=").decode()
claims = {
"sub": "3f2c1b9e-8a47-4d2e-9c1f-5b6a7d8e9f01",
"name": "Jane Doe (Acme)",
"email": "jane.doe@example.com",
"roles": ["admin"],
"iat": 1792000000,
"exp": 1792000900,
}
jwt = ".".join([
b64url(json.dumps({"alg": "ES256", "typ": "JWT"}).encode()),
b64url(json.dumps(claims).encode()),
b64url(bytes(range(64))),
])
cookie = f"session={jwt}; theme=system; _ga=GA1.1.123456789.1792000000"
class Request:
headers = {"cookie": cookie}
query_params: dict[str, str] = {}
url_path = "/api/me"
url_scheme = "https"
url_full = "https://example.com/api/me"
method = "GET"
client_host = "203.0.113.7"
scope: dict = {}
state = types.SimpleNamespace()
async def body(self) -> bytes:
return b""
result = asyncio.run(detect_penetration_attempt(Request(), SecurityConfig()))
print(result.is_threat, result.trigger_info)Output on 4.3.3, 3.15.0 and master 25cbcb2:
True Header 'cookie': Semantic attack: suspicious (score: 0.71)
Versions
guard-core 4.3.3, 3.15.0 and master 25cbcb2, Python 3.12, macOS. In production: guard-core 3.15.0 with fastapi-guard 7.8.2 on Linux.
Configuration
SecurityConfig() defaults: penetration detection on, detection_semantic_threshold=0.7, cookie not in excluded_detection_headers.
Additional context
Workaround: add cookie to excluded_detection_headers, or raise detection_semantic_threshold. Both scan cookies less.
Possible fix: scan the Cookie header one name=value pair at a time, like query parameters are scanned one by one. Each cookie still gets every regex and semantic check, so an attack inside one cookie is still found. I have a PR with this and tests ready.
Activity
rennf93 commentedon Oct 15, 2026
Fixed by #162 (37bfb34): the Cookie header is now scanned one name=value pair at a time. Thanks for the report and the fix. It ships in the next engine release, together with the same change in the other engine ports.