From 06b948eefe574ebd48a41c71c6a221c4f15579dd Mon Sep 17 00:00:00 2001 From: John Menke Date: Fri, 11 Sep 2026 11:18:15 -0400 Subject: [PATCH] Refuse thin secret filenames and token shapes before they reach Ollama. Co-authored-by: Cursor --- src/local_coding_slm/payload.py | 35 +++++++++++++++++++++++++-------- tests/test_payload.py | 30 ++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/local_coding_slm/payload.py b/src/local_coding_slm/payload.py index 68fa8c9..7fe6678 100644 --- a/src/local_coding_slm/payload.py +++ b/src/local_coding_slm/payload.py @@ -8,6 +8,7 @@ from __future__ import annotations +import re from collections.abc import Sequence MAX_FILES = 12 @@ -20,7 +21,20 @@ "-----BEGIN EC PRIVATE KEY-----", "-----BEGIN PRIVATE KEY-----", ) -_TOKEN_PREFIXES = ("ghp_", "github_pat_", "sk-proj-", "sk-ant-") +_SECRET_BASENAMES = frozenset( + { + "credentials.json", + "id_rsa", + "id_rsa.pub", + "id_ed25519", + "id_ed25519.pub", + "kubeconfig", + } +) +_TOKEN_RE = re.compile(r"(? str: def _secret_path(path: str) -> str | None: - name = path.replace("\\", "/").rsplit("/", 1)[-1].lower() + normalized = path.replace("\\", "/").lower() + name = normalized.rsplit("/", 1)[-1] if name == ".env.example": return None if name == ".env" or name.startswith(".env."): return "secrets_file" - if name in {"credentials.json", "id_rsa", "id_rsa.pub"}: + if name in _SECRET_BASENAMES: + return "secrets_file" + if normalized == ".aws/credentials" or normalized.endswith("/.aws/credentials"): return "secrets_file" return None @@ -73,10 +90,12 @@ def _secret_path(path: str) -> str | None: def _secret_content(content: str) -> str | None: if any(marker in content for marker in _PRIVATE_KEY_MARKERS): return "secret_content" - lowered = content.lower() - if "aws_secret_access_key=" in lowered: + if _AWS_SECRET_ASSIGN_RE.search(content.lower()): + return "secret_content" + if _TOKEN_RE.search(content): + return "secret_content" + if _AWS_ACCESS_KEY_ID_RE.search(content): + return "secret_content" + if _JWT_RE.search(content): return "secret_content" - for prefix in _TOKEN_PREFIXES: - if prefix in content: - return "secret_content" return None diff --git a/tests/test_payload.py b/tests/test_payload.py index 307bf2f..92d630e 100644 --- a/tests/test_payload.py +++ b/tests/test_payload.py @@ -8,7 +8,37 @@ from local_coding_slm.payload import MAX_FILES, inspect_payload, refusal_message +# Leftover #6 shapes that origin/main allowed through. Fixtures only. +_THIN_SECRET_CASES: tuple[tuple[str, list[dict[str, str]]], ...] = ( + ("id_ed25519", [{"path": "id_ed25519", "content": "not-a-real-key"}]), + (".aws/credentials", [{"path": ".aws/credentials", "content": "[default]\n"}]), + ("kubeconfig", [{"path": "kubeconfig", "content": "apiVersion: v1\n"}]), + ("plain sk-", [{"path": "notes.py", "content": "sk-"}]), + ("AKIA", [{"path": "notes.py", "content": "AKIA"}]), + ( + "JWT", + [ + { + "path": "notes.py", + "content": "eyJhbGciOiJub25lIn0.eyJzdWIiOiJmaXh0dXJlIn0.e30", + } + ], + ), + ( + "spaced aws_secret_access_key =", + [{"path": "notes.py", "content": "aws_secret_access_key ="}], + ), +) + + class InspectPayloadTests(unittest.TestCase): + def test_inspect_payload_refuses_thin_secret_shapes(self) -> None: + for label, files in _THIN_SECRET_CASES: + with self.subTest(label=label): + reason = inspect_payload(files) + self.assertIsNotNone(reason, msg=label) + self.assertTrue(reason) + def test_clean_snippet_ok(self) -> None: self.assertIsNone( inspect_payload([{"path": "add.py", "content": "def add(a, b): return a + b\n"}])