This commit is contained in:
Shalom Jamil 2026-04-29 13:16:25 +03:00
parent 65d2e40202
commit 57343cc281
2 changed files with 23 additions and 7 deletions

View file

@ -178,11 +178,15 @@ class OvalixGuardrail(CustomGuardrail):
return metadata["user_api_key_user_id"]
return "unknown"
def _get_tracker_actor_id(self, data: dict) -> str:
"""Opaque actor id for Tracker API payloads (hash of _get_actor; avoids sending PII)."""
return hashlib.sha256(self._get_actor(data).encode()).hexdigest()[:8]
def _get_session_id(self, data: dict) -> str:
"""Return a unique identifier for the chat/session (actor + date + application_id)."""
actor = hashlib.sha256(self._get_actor(data).encode()).hexdigest()[:8]
actor_hash = self._get_tracker_actor_id(data)
today = datetime.datetime.now().strftime("%Y-%m-%d")
return f"{actor}_{today}_{self._application_id}"
return f"{actor_hash}_{today}_{self._application_id}"
async def _call_checkpoint(
self,
@ -236,7 +240,7 @@ class OvalixGuardrail(CustomGuardrail):
if not self._pre_checkpoint_id and not self._post_checkpoint_id:
return inputs
actor = self._get_actor(request_data)
tracker_actor_id = self._get_tracker_actor_id(request_data)
session_id = self._get_session_id(request_data)
texts = inputs.get("texts") or []
if not texts or not isinstance(texts, list):
@ -246,13 +250,13 @@ class OvalixGuardrail(CustomGuardrail):
if not self._post_checkpoint_id:
return inputs
corrected_llm_responses = await self._generate_post_guardrail_llm_texts(
texts, actor, session_id, self._post_checkpoint_id
texts, tracker_actor_id, session_id, self._post_checkpoint_id
)
return {**inputs, "texts": corrected_llm_responses}
if self._pre_checkpoint_id:
post_guardrail_texts = await self._generate_post_guardrail_llm_texts(
texts, actor, session_id, self._pre_checkpoint_id
texts, tracker_actor_id, session_id, self._pre_checkpoint_id
)
return {**inputs, "texts": post_guardrail_texts}
return inputs

View file

@ -2,6 +2,7 @@
Unit tests for Ovalix guardrail: config resolution and apply_guardrail behavior
with mocked Tracker service responses (allow, anonymize, block).
"""
import os
from typing import Any, List
from unittest.mock import AsyncMock, MagicMock, patch
@ -167,7 +168,7 @@ class TestOvalixGuardrail:
result = await guardrail._call_checkpoint(
content="hello",
checkpoint_id="pre-1",
actor="user@test.com",
actor="a1b2c3d4",
session_id="session-1",
)
@ -180,7 +181,7 @@ class TestOvalixGuardrail:
body = call_args.kwargs["json"]
assert body["application_id"] == "app-1"
assert body["checkpoint_id"] == "pre-1"
assert body["actor"] == "user@test.com"
assert body["actor"] == "a1b2c3d4"
assert body["session_id"] == "session-1"
assert body["data_type"] == "TEXT"
assert body["data"] == {"content": "hello"}
@ -579,6 +580,17 @@ class TestOvalixGuardrail:
}
assert guardrail._get_actor(data) == "primary@test.com"
def test_get_tracker_actor_id_is_hash_not_raw_pii(self, guardrail_with_env):
"""Tracker API actor field uses a short hash of _get_actor, not email/user id."""
guardrail = guardrail_with_env
data = {"metadata": {"user_api_key_user_email": "user@example.com"}}
raw = guardrail._get_actor(data)
hashed = guardrail._get_tracker_actor_id(data)
assert raw == "user@example.com"
assert hashed != raw
assert len(hashed) == 8
assert all(c in "0123456789abcdef" for c in hashed)
def test_get_session_id_deterministic_and_includes_app_id(self, guardrail_with_env):
"""Session ID is stable for same actor/day and includes application_id."""
guardrail = guardrail_with_env