Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/local_coding_slm/payload.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Refuse unsafe or oversized snippets before they reach Ollama.

Used by the MCP server (defense in depth) and by eval routing. This is
not a classifier: it only looks at file names, size, and a few secret
shapes. Code that merely mentions ``password`` is allowed.
not a classifier: it only looks at file names, the task string, size,
and a few secret shapes. Code that merely mentions ``password`` is
allowed.
"""

from __future__ import annotations
Expand All @@ -26,10 +27,15 @@ def inspect_payload(
files: Sequence[dict[str, str]] | None,
*,
max_tokens: int | None = None,
task: str | None = None,
) -> str | None:
"""Return a stable reason string, or None if the payload may be sent."""
if max_tokens is not None and max_tokens > MAX_TOKENS:
return "max_tokens_too_large"
if task:
task_reason = _secret_content(task)
if task_reason:
return task_reason
items = list(files or ())
if len(items) > MAX_FILES:
return "too_many_files"
Expand Down
2 changes: 1 addition & 1 deletion src/local_coding_slm/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def _run_tool(
) -> str:
if not task or not task.strip():
return "ERROR: task is required"
blocked = inspect_payload(files, max_tokens=max_tokens)
blocked = inspect_payload(files, max_tokens=max_tokens, task=task)
if blocked:
return refusal_message(blocked)
user = format_user_task(task, files=files, language=language, style=style)
Expand Down
9 changes: 9 additions & 0 deletions tests/test_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ def test_private_key_content(self) -> None:
"secret_content",
)

def test_private_key_in_task(self) -> None:
self.assertEqual(
inspect_payload(
[{"path": "app.py", "content": "def add(a, b): return a + b\n"}],
task="-----BEGIN OPENSSH PRIVATE KEY-----\nNOT-A-REAL-KEY\n",
),
"secret_content",
)

def test_password_in_code_is_not_a_secret_blob(self) -> None:
self.assertIsNone(
inspect_payload(
Expand Down
15 changes: 15 additions & 0 deletions tests/test_server_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ def test_private_key_never_calls_ollama(self, chat: object) -> None:
self.assertIn("secret_content", text)
chat.assert_not_called() # type: ignore[attr-defined]

@patch("local_coding_slm.server.chat")
def test_private_key_in_task_never_calls_ollama(self, chat: object) -> None:
text = _run_tool(
"local_code",
"-----BEGIN OPENSSH PRIVATE KEY-----\nNOT-A-REAL-KEY\n",
[{"path": "app.py", "content": "def add(a, b): return a + b\n"}],
None,
None,
"fast",
None,
)
self.assertTrue(text.startswith("ERROR:"))
self.assertIn("secret_content", text)
chat.assert_not_called() # type: ignore[attr-defined]

@patch("local_coding_slm.server.chat", return_value="ok")
def test_clean_payload_reaches_chat(self, chat: object) -> None:
text = _run_tool(
Expand Down
Loading