Skip to content

Commit b91fa46

Browse files
fix: use simultaneous regex substitution to prevent cross-placeholder injection
Co-Authored-By: jbailey@launchdarkly.com <accounts@sidewaysgravity.com>
1 parent 1ad110e commit b91fa46

2 files changed

Lines changed: 17 additions & 4 deletions

File tree

packages/sdk/server-ai/src/ldai/judge/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Judge implementation for AI evaluation."""
22

33
import random
4+
import re
45
from typing import Any, Dict, Optional
56

67
from ldai import log
@@ -167,10 +168,11 @@ def _interpolate_message(self, content: str, variables: Dict[str, str]) -> str:
167168
:param variables: Variables to interpolate
168169
:return: Interpolated message content
169170
"""
170-
result = content
171-
for key, value in variables.items():
172-
result = result.replace('{{' + key + '}}', value)
173-
return result
171+
return re.sub(
172+
r'\{\{(\w+)\}\}',
173+
lambda match: variables.get(match.group(1), match.group(0)),
174+
content,
175+
)
174176

175177
def _parse_evaluation_response(self, data: Dict[str, Any]) -> Dict[str, EvalScore]:
176178
"""

packages/sdk/server-ai/tests/test_judge.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,17 @@ def test_multiple_placeholder_occurrences(self, tracker: LDAIConfigTracker, mock
688688
assert len(messages) == 1
689689
assert messages[0].content == 'HISTORY | HISTORY'
690690

691+
def test_cross_placeholder_injection(self, tracker: LDAIConfigTracker, mock_runner):
692+
"""A message_history value containing {{response_to_evaluate}} must not be expanded."""
693+
template = 'History: {{message_history}}\nResponse: {{response_to_evaluate}}'
694+
695+
judge = self._make_judge(template, tracker, mock_runner)
696+
messages = judge._construct_evaluation_messages('{{response_to_evaluate}}', 'REAL OUTPUT')
697+
698+
assert len(messages) == 1
699+
assert messages[0].content == 'History: {{response_to_evaluate}}\nResponse: REAL OUTPUT', \
700+
'literal {{response_to_evaluate}} in history value must not be expanded'
701+
691702
def test_mustache_syntax_in_content(self, tracker: LDAIConfigTracker, mock_runner):
692703
"""Mustache-like syntax inside history or response values is preserved verbatim."""
693704
template = 'History: {{message_history}}\nResponse: {{response_to_evaluate}}'

0 commit comments

Comments
 (0)