Skip to content

Commit 69af5db

Browse files
committed
fix: Address review - path traversal, guardrail fallback, elif chain, retries, smoke tests
1 parent 32dfbe2 commit 69af5db

5 files changed

Lines changed: 45 additions & 16 deletions

File tree

scripts/issue_bot/bedrock_client.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ def __init__(self, cfg):
1717
config=BotoConfig(
1818
read_timeout=cfg.bedrock_timeout,
1919
connect_timeout=cfg.bedrock_timeout,
20-
retries={"max_attempts": 1, "mode": "standard"},
20+
retries={"max_attempts": 3, "mode": "adaptive"},
2121
),
2222
)
2323
self._guardrail_id = cfg.guardrail_id
24+
self._guardrail_version = cfg.guardrail_version
2425
self._failures = 0
2526
self._circuit_open = False
2627

@@ -66,7 +67,7 @@ def invoke(self, prompt, max_tokens=4096, temperature=0.3, json_schema=None):
6667
if self._guardrail_id:
6768
kwargs["guardrailConfig"] = {
6869
"guardrailIdentifier": self._guardrail_id,
69-
"guardrailVersion": "1",
70+
"guardrailVersion": self._guardrail_version,
7071
"trace": "enabled",
7172
}
7273

@@ -103,7 +104,9 @@ def _split_prompt(self, prompt):
103104
marker = "<knowledge_base>"
104105
idx = prompt.find(marker)
105106
if idx == -1:
106-
# No marker — send everything as user content (unguarded)
107+
# No marker — guard everything as untrusted
108+
if self._guardrail_id:
109+
return None, [{"guardContent": {"text": {"text": prompt}}}]
107110
return None, [{"text": prompt}]
108111

109112
system = prompt[:idx].strip()

scripts/issue_bot/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def __init__(self):
2222

2323
self.slack_webhook_url = os.getenv("SLACK_WEBHOOK_URL", "")
2424
self.guardrail_id = os.getenv("GUARDRAIL_ID", "")
25+
self.guardrail_version = os.getenv("GUARDRAIL_VERSION", "DRAFT")
2526

2627
self.dry_run = os.getenv("DRY_RUN", "false").lower() == "true"
2728
self.enable_slack = bool(self.slack_webhook_url)

scripts/issue_bot/github_client.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,11 @@ def get_codebase_map(self, src_dir="pydeequ"):
9090
return ""
9191

9292
def read_local_file(self, path):
93-
if not path.startswith("pydeequ/") and not path.startswith("tests/") and not path.startswith("tutorials/"):
94-
logger.error(f"Blocked read outside allowed dirs: {path}")
93+
full_path = os.path.realpath(os.path.join(self._repo_root, path))
94+
repo_root = os.path.realpath(self._repo_root)
95+
if not full_path.startswith(repo_root + os.sep):
96+
logger.error(f"Blocked path traversal: {path}")
9597
return ""
96-
full_path = os.path.join(self._repo_root, path)
9798
try:
9899
with open(full_path, "r", errors="replace") as f:
99100
return f.read()
@@ -185,9 +186,10 @@ def _get_valid_diff_lines(self, number):
185186
continue
186187
if line.startswith("-"):
187188
continue
188-
if line.startswith("+") or not line.startswith("\\"):
189-
lines.add(current_line)
190-
current_line += 1
189+
if line.startswith("\\"):
190+
continue
191+
lines.add(current_line)
192+
current_line += 1
191193
valid[path] = lines
192194
return valid
193195

scripts/issue_bot/main.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import os
1111
import datetime
1212
import logging
13-
from string import Template
1413

1514
from .config import Config
1615
from .bedrock_client import BedrockClient
@@ -27,10 +26,12 @@
2726

2827

2928
def _render(template_str, **kwargs):
30-
"""Render a prompt template safely. Converts {var} to $var for Template.safe_substitute
31-
so untrusted content like PR bodies containing {braces} won't crash or leak."""
32-
converted = template_str.replace("{", "${")
33-
return Template(converted).safe_substitute(**kwargs)
29+
"""Render a prompt template safely using str.replace per known variable.
30+
Unlike .format(), this won't crash on untrusted content containing {braces}."""
31+
result = template_str
32+
for key, value in kwargs.items():
33+
result = result.replace("{" + key + "}", str(value))
34+
return result
3435

3536

3637
def _load_schema(name):
@@ -275,7 +276,7 @@ def act():
275276
slack.send_escalation(number, title, html_url, labels)
276277
logger.info(f"Responded to #{number}")
277278

278-
if action == "ESCALATE":
279+
elif action == "ESCALATE":
279280
reason = result.get("reason", "")
280281
if reason == "user_dissatisfied":
281282
ack = (
@@ -309,7 +310,7 @@ def act():
309310
slack.send_escalation(number, title, html_url, labels)
310311
logger.info(f"Escalated #{number}")
311312

312-
if action == "CLOSE" and not is_pr:
313+
elif action == "CLOSE" and not is_pr:
313314
msg = (
314315
"This issue may not be related to the PyDeequ data quality library. "
315316
"The maintainer team has been notified and will review." + footer

tests/test_bot.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,25 @@ def test_strips_preamble(self):
215215
def test_preserves_normal_text(self):
216216
text = "This is a normal response."
217217
assert _clean_response(text) == text
218+
219+
220+
# ---------------------------------------------------------------------------
221+
# Smoke test — module imports without crashing
222+
# ---------------------------------------------------------------------------
223+
224+
class TestSmoke:
225+
def test_main_module_imports(self):
226+
from issue_bot import main
227+
assert hasattr(main, 'analyze')
228+
assert hasattr(main, 'act')
229+
230+
def test_sanitizer_imports(self):
231+
from issue_bot import sanitizer
232+
assert hasattr(sanitizer, 'sanitize')
233+
234+
def test_schemas_loadable(self):
235+
from issue_bot.main import ISSUE_RESPONSE_SCHEMA, PR_REVIEW_SCHEMA, FOLLOWUP_SCHEMA
236+
import json
237+
assert json.loads(ISSUE_RESPONSE_SCHEMA)["type"] == "object"
238+
assert json.loads(PR_REVIEW_SCHEMA)["type"] == "object"
239+
assert json.loads(FOLLOWUP_SCHEMA)["type"] == "object"

0 commit comments

Comments
 (0)