Skip to content

[BUG] Normal logged-in Cookie headers are rejected as "Semantic attack: suspicious (score: 0.71)" #161

Closed

Description

@HardMax71

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 the command attack type (semantic.py#L186) and as command_chain patterns (#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

  1. rennf93 commented on Oct 15, 2026

    @rennf93
    Member

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions