ct-toolkit 0.3.35__tar.gz → 0.3.36__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/PKG-INFO +11 -2
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/README.md +10 -1
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/__init__.py +1 -1
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/cli.py +123 -31
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/core/kernel.py +19 -5
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/divergence/l2_judge.py +21 -9
- ct_toolkit-0.3.36/ct_toolkit/profile_checksums.sha256 +35 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit.egg-info/PKG-INFO +11 -2
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit.egg-info/SOURCES.txt +1 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/pyproject.toml +2 -1
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/LICENSE +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/__main__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/core/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/core/async_wrapper.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/core/circuit_breaker.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/core/compatibility.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/core/compression_guard.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/core/exceptions.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/core/integrity.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/core/wrapper.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/divergence/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/divergence/analysis.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/divergence/engine.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/divergence/l3_icm.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/divergence/loss.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/divergence/scheduler.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/endorsement/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/endorsement/probes/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/endorsement/reflective.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/identity/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/identity/embedding.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/identity/templates/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/identity/templates/defense.yaml +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/identity/templates/finance.yaml +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/identity/templates/general.yaml +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/identity/templates/medical.yaml +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/kernels/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/kernels/default.yaml +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/kernels/defense.yaml +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/middleware/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/middleware/autogen.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/middleware/crewai.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/middleware/deepagents.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/middleware/langchain.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/middleware/litellm.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/provenance/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/provenance/log.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/provenance/vault/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/server.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/utils/__init__.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/utils/logger.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/utils/metrics.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit/utils/sensitive_masker.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit.egg-info/dependency_links.txt +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit.egg-info/entry_points.txt +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit.egg-info/requires.txt +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/ct_toolkit.egg-info/top_level.txt +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/setup.cfg +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/tests/test_analysis.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/tests/test_cli.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/tests/test_cli_extended.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/tests/test_deep_agents_integration.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/tests/test_loss.py +0 -0
- {ct_toolkit-0.3.35 → ct_toolkit-0.3.36}/tests/test_summarization_drift.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ct-toolkit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.36
|
|
4
4
|
Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
|
|
5
5
|
Author: Hakan Damar
|
|
6
6
|
License: Apache-2.0
|
|
@@ -120,12 +120,21 @@ ct-toolkit audit --url http://localhost:11434/v1 --kernel defense
|
|
|
120
120
|
# List available kernels and templates
|
|
121
121
|
ct-toolkit list-kernels
|
|
122
122
|
ct-toolkit list-templates
|
|
123
|
+
|
|
124
|
+
# Download a verified profile from the official repository
|
|
125
|
+
ct-toolkit setup personal_kernel --dest ./config
|
|
123
126
|
```
|
|
124
127
|
|
|
125
128
|
If you want a runnable application example instead of a CLI-only flow, see [**ct-toolkit-fastapi**](https://github.com/hakandamar/ct-toolkit-fastapi), a small FastAPI validation project that developers can use to test CT Toolkit locally with automated endpoints and pytest coverage.
|
|
126
129
|
|
|
127
130
|
For Deep Agents workflows, see [**ct-toolkit-deep-agents**](https://github.com/hakandamar/ct-toolkit-deep-agents), a reference integration project for validating CT Toolkit in multi-agent orchestration scenarios.
|
|
128
131
|
|
|
132
|
+
### Security Hardening
|
|
133
|
+
|
|
134
|
+
The profile setup command fails closed by default. Downloaded kernel, identity, and probe files must match the trusted SHA-256 manifest packaged with CT Toolkit before they are written to disk. HTTPS certificate validation is enabled and redirects to another host or scheme are rejected.
|
|
135
|
+
|
|
136
|
+
The L2 judge does not evaluate Jinja2 expressions in user requests or model responses. Rule validation normalizes Unicode input and requires configured conflict keywords to match complete words, preventing simple substring and keyword-boundary bypasses.
|
|
137
|
+
|
|
129
138
|
---
|
|
130
139
|
|
|
131
140
|
## 🚦 Project Health & Status
|
|
@@ -134,7 +143,7 @@ For Deep Agents workflows, see [**ct-toolkit-deep-agents**](https://github.com/h
|
|
|
134
143
|
| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
135
144
|
| **Tests** | ✅ 397 passed, 3 skipped (100% success rate, 90% coverage) |
|
|
136
145
|
| **Downloads** | [](https://pepy.tech/projects/ct-toolkit) |
|
|
137
|
-
| **Last Phase** | ✅ v0.3.
|
|
146
|
+
| **Last Phase** | ✅ v0.3.36: High-severity security hardening for L2 prompts, profile downloads, and rule validation |
|
|
138
147
|
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
139
148
|
|
|
140
149
|
### Framework & Model Support
|
|
@@ -72,12 +72,21 @@ ct-toolkit audit --url http://localhost:11434/v1 --kernel defense
|
|
|
72
72
|
# List available kernels and templates
|
|
73
73
|
ct-toolkit list-kernels
|
|
74
74
|
ct-toolkit list-templates
|
|
75
|
+
|
|
76
|
+
# Download a verified profile from the official repository
|
|
77
|
+
ct-toolkit setup personal_kernel --dest ./config
|
|
75
78
|
```
|
|
76
79
|
|
|
77
80
|
If you want a runnable application example instead of a CLI-only flow, see [**ct-toolkit-fastapi**](https://github.com/hakandamar/ct-toolkit-fastapi), a small FastAPI validation project that developers can use to test CT Toolkit locally with automated endpoints and pytest coverage.
|
|
78
81
|
|
|
79
82
|
For Deep Agents workflows, see [**ct-toolkit-deep-agents**](https://github.com/hakandamar/ct-toolkit-deep-agents), a reference integration project for validating CT Toolkit in multi-agent orchestration scenarios.
|
|
80
83
|
|
|
84
|
+
### Security Hardening
|
|
85
|
+
|
|
86
|
+
The profile setup command fails closed by default. Downloaded kernel, identity, and probe files must match the trusted SHA-256 manifest packaged with CT Toolkit before they are written to disk. HTTPS certificate validation is enabled and redirects to another host or scheme are rejected.
|
|
87
|
+
|
|
88
|
+
The L2 judge does not evaluate Jinja2 expressions in user requests or model responses. Rule validation normalizes Unicode input and requires configured conflict keywords to match complete words, preventing simple substring and keyword-boundary bypasses.
|
|
89
|
+
|
|
81
90
|
---
|
|
82
91
|
|
|
83
92
|
## 🚦 Project Health & Status
|
|
@@ -86,7 +95,7 @@ For Deep Agents workflows, see [**ct-toolkit-deep-agents**](https://github.com/h
|
|
|
86
95
|
| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
87
96
|
| **Tests** | ✅ 397 passed, 3 skipped (100% success rate, 90% coverage) |
|
|
88
97
|
| **Downloads** | [](https://pepy.tech/projects/ct-toolkit) |
|
|
89
|
-
| **Last Phase** | ✅ v0.3.
|
|
98
|
+
| **Last Phase** | ✅ v0.3.36: High-severity security hardening for L2 prompts, profile downloads, and rule validation |
|
|
90
99
|
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
91
100
|
|
|
92
101
|
### Framework & Model Support
|
|
@@ -5,6 +5,13 @@ Main entry point for the Theseus Guard / CT Toolkit CLI.
|
|
|
5
5
|
"""
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import hashlib
|
|
9
|
+
import re
|
|
10
|
+
import ssl
|
|
11
|
+
import urllib.error
|
|
12
|
+
import urllib.parse
|
|
13
|
+
import urllib.request
|
|
14
|
+
from importlib.resources import files as package_files
|
|
8
15
|
from pathlib import Path
|
|
9
16
|
from typing import Optional
|
|
10
17
|
|
|
@@ -25,6 +32,58 @@ app = typer.Typer(
|
|
|
25
32
|
|
|
26
33
|
console = Console()
|
|
27
34
|
|
|
35
|
+
_PROFILE_CHECKSUM_RESOURCE = "profile_checksums.sha256"
|
|
36
|
+
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _load_trusted_profile_checksums() -> dict[str, str]:
|
|
40
|
+
"""Load the out-of-band profile hashes shipped in the installed wheel."""
|
|
41
|
+
manifest = package_files("ct_toolkit").joinpath(_PROFILE_CHECKSUM_RESOURCE)
|
|
42
|
+
checksums: dict[str, str] = {}
|
|
43
|
+
for line_number, line in enumerate(manifest.read_text(encoding="ascii").splitlines(), 1):
|
|
44
|
+
line = line.strip()
|
|
45
|
+
if not line or line.startswith("#"):
|
|
46
|
+
continue
|
|
47
|
+
parts = line.split(maxsplit=1)
|
|
48
|
+
if len(parts) != 2 or not _SHA256.fullmatch(parts[0]):
|
|
49
|
+
raise RuntimeError(f"Invalid profile checksum manifest at line {line_number}")
|
|
50
|
+
filename = parts[1].strip()
|
|
51
|
+
if Path(filename).name != filename:
|
|
52
|
+
raise RuntimeError(f"Invalid profile filename in checksum manifest: {filename}")
|
|
53
|
+
checksums[filename] = parts[0]
|
|
54
|
+
if not checksums:
|
|
55
|
+
raise RuntimeError("Trusted profile checksum manifest is empty")
|
|
56
|
+
return checksums
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class _NoDowngradeRedirectHandler(urllib.request.HTTPRedirectHandler):
|
|
60
|
+
"""Allow redirects only to the same HTTPS origin."""
|
|
61
|
+
|
|
62
|
+
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
|
63
|
+
original = urllib.parse.urlparse(req.full_url)
|
|
64
|
+
redirected = urllib.parse.urlparse(newurl)
|
|
65
|
+
if (
|
|
66
|
+
redirected.scheme != "https"
|
|
67
|
+
or redirected.hostname != original.hostname
|
|
68
|
+
or redirected.port != original.port
|
|
69
|
+
):
|
|
70
|
+
raise urllib.error.URLError(f"Refusing unsafe redirect to {newurl}")
|
|
71
|
+
return super().redirect_request(req, fp, code, msg, headers, newurl)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _download_profile_file(url: str) -> bytes:
|
|
75
|
+
"""Download one profile file over validated HTTPS without unsafe redirects."""
|
|
76
|
+
parsed = urllib.parse.urlparse(url)
|
|
77
|
+
if parsed.scheme != "https" or not parsed.hostname:
|
|
78
|
+
raise urllib.error.URLError(f"Refusing non-HTTPS URL: {url}")
|
|
79
|
+
opener = urllib.request.build_opener(
|
|
80
|
+
_NoDowngradeRedirectHandler(),
|
|
81
|
+
urllib.request.HTTPSHandler(context=ssl.create_default_context()),
|
|
82
|
+
)
|
|
83
|
+
request = urllib.request.Request(url, headers={"User-Agent": "ct-toolkit-setup/1.0"})
|
|
84
|
+
with opener.open(request) as response:
|
|
85
|
+
return response.read()
|
|
86
|
+
|
|
28
87
|
BANNER = r"""
|
|
29
88
|
_______ _ _ ______ _____ ______ _ _ _____ _____ _ _ _____ _____
|
|
30
89
|
|__ __| | | | ____|/ ____| ____| | | |/ ____| / ____| | | | /\ | __ \| __ \
|
|
@@ -178,53 +237,82 @@ def setup(
|
|
|
178
237
|
dest_dir: str = typer.Option("./config", "--dest", help="Destination folder (default: ./config)"),
|
|
179
238
|
verify_checksums: bool = typer.Option(True, "--verify-checksums/--no-verify-checksums", help="Download and verify SHA256 checksums"),
|
|
180
239
|
):
|
|
181
|
-
"""Download a CT-Toolkit profile (kernel, identity, probes) from the official GitHub repository.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
240
|
+
"""Download a CT-Toolkit profile (kernel, identity, probes) from the official GitHub repository.
|
|
241
|
+
|
|
242
|
+
SECURITY: The previous implementation only self-computed SHA-256 of the
|
|
243
|
+
just-downloaded payload, which is a no-op against an active MITM or a
|
|
244
|
+
compromised upstream. We now require the out-of-band known-good checksum
|
|
245
|
+
manifest shipped in the installed wheel and refuse the download if the
|
|
246
|
+
live bytes do not match. We additionally pin the download to HTTPS, refuse
|
|
247
|
+
cross-origin redirects, and require certificate validation against the
|
|
248
|
+
system trust store.
|
|
249
|
+
"""
|
|
185
250
|
base_url = f"https://raw.githubusercontent.com/hakandamar/ct-toolkit/{repo_branch}/examples/agent_dna_config"
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
251
|
+
|
|
252
|
+
if not verify_checksums:
|
|
253
|
+
console.print(
|
|
254
|
+
"[bold red]Refusing to download without checksum verification.[/bold red]\n"
|
|
255
|
+
"The profile must match the trusted checksum manifest shipped with CT Toolkit."
|
|
256
|
+
)
|
|
257
|
+
raise typer.Exit(code=2)
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
trusted_checksums = _load_trusted_profile_checksums()
|
|
261
|
+
except Exception as exc:
|
|
262
|
+
console.print(f"[bold red]Trusted checksum manifest unavailable:[/bold red] {exc}")
|
|
263
|
+
raise typer.Exit(code=1)
|
|
264
|
+
|
|
189
265
|
base_name = profile.replace("_kernel", "") if profile.endswith("_kernel") else profile
|
|
190
266
|
if base_name == profile:
|
|
191
267
|
kernel_file = f"{profile}_kernel.yaml"
|
|
192
268
|
else:
|
|
193
269
|
kernel_file = f"{profile}.yaml"
|
|
194
|
-
|
|
270
|
+
|
|
195
271
|
identity_file = f"{base_name}_identity.yaml"
|
|
196
272
|
probes_file = f"{base_name}_probes.json"
|
|
197
|
-
|
|
273
|
+
|
|
198
274
|
files_to_download = [kernel_file, identity_file, probes_file]
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
275
|
+
|
|
276
|
+
missing_hashes = [filename for filename in files_to_download if filename not in trusted_checksums]
|
|
277
|
+
if missing_hashes:
|
|
278
|
+
console.print(
|
|
279
|
+
"[bold red]No trusted checksum is pinned for:[/bold red] "
|
|
280
|
+
+ ", ".join(missing_hashes)
|
|
281
|
+
)
|
|
282
|
+
raise typer.Exit(code=2)
|
|
283
|
+
|
|
284
|
+
target_dir = Path(dest_dir)
|
|
285
|
+
|
|
210
286
|
with console.status(f"[bold green]Downloading '{profile}' profile from GitHub...[/bold green]"):
|
|
211
287
|
has_errors = False
|
|
288
|
+
downloaded: dict[str, bytes] = {}
|
|
212
289
|
checksums: dict[str, str] = {}
|
|
213
|
-
|
|
290
|
+
|
|
214
291
|
for filename in files_to_download:
|
|
215
292
|
url = f"{base_url}/{filename}"
|
|
216
|
-
dest_path = target_dir / filename
|
|
217
293
|
try:
|
|
218
|
-
content =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
294
|
+
content = _download_profile_file(url)
|
|
295
|
+
actual = hashlib.sha256(content).hexdigest()
|
|
296
|
+
expected = trusted_checksums[filename]
|
|
297
|
+
if actual != expected:
|
|
298
|
+
raise ValueError(
|
|
299
|
+
f"checksum mismatch (expected {expected}, got {actual})"
|
|
300
|
+
)
|
|
301
|
+
downloaded[filename] = content
|
|
302
|
+
console.print(f"[green]✓ Verified {filename}[/green]")
|
|
223
303
|
except Exception as e:
|
|
224
304
|
console.print(f"[red]✗ Failed to download {filename} from {url}:[/red] {e}")
|
|
225
305
|
has_errors = True
|
|
226
|
-
|
|
227
|
-
#
|
|
306
|
+
|
|
307
|
+
# Do not write any profile until every downloaded file has passed the
|
|
308
|
+
# trusted hash check.
|
|
309
|
+
if not has_errors:
|
|
310
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
311
|
+
for filename, content in downloaded.items():
|
|
312
|
+
(target_dir / filename).write_bytes(content)
|
|
313
|
+
checksums[filename] = hashlib.sha256(content).hexdigest()
|
|
314
|
+
|
|
315
|
+
# Write the verified hashes for transparency/audit.
|
|
228
316
|
if checksums and not has_errors:
|
|
229
317
|
checksum_path = target_dir / f"{profile}_checksums.sha256"
|
|
230
318
|
try:
|
|
@@ -234,12 +322,16 @@ def setup(
|
|
|
234
322
|
console.print(f"[dim]Checksums saved to {checksum_path}[/dim]")
|
|
235
323
|
except Exception as e:
|
|
236
324
|
console.print(f"[yellow]Warning: Could not save checksums file: {e}[/yellow]")
|
|
237
|
-
|
|
325
|
+
|
|
238
326
|
if not has_errors:
|
|
239
327
|
console.print(f"\n[bold blue]Profile '{profile}' setup complete![/bold blue]")
|
|
240
|
-
console.print(
|
|
328
|
+
console.print(
|
|
329
|
+
f"You can now use this kernel by setting "
|
|
330
|
+
f"[bold]kernel_name='{kernel_file.replace('.yaml','')}'[/bold] in TheseusWrapper"
|
|
331
|
+
)
|
|
241
332
|
else:
|
|
242
333
|
console.print(f"\n[bold yellow]Profile '{profile}' setup finished with some errors.[/bold yellow]")
|
|
334
|
+
raise typer.Exit(code=1)
|
|
243
335
|
|
|
244
336
|
@app.command()
|
|
245
337
|
def list_kernels():
|
|
@@ -7,6 +7,8 @@ and plastic commitments that can be extended by the user.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
|
+
import re
|
|
11
|
+
import unicodedata
|
|
10
12
|
import yaml
|
|
11
13
|
from pathlib import Path
|
|
12
14
|
from dataclasses import dataclass, field
|
|
@@ -167,16 +169,28 @@ class ConstitutionalKernel:
|
|
|
167
169
|
return "\n".join(lines)
|
|
168
170
|
|
|
169
171
|
def validate_user_rule(self, rule_text: str) -> None:
|
|
170
|
-
|
|
172
|
+
normalized_rule = unicodedata.normalize("NFKC", rule_text).casefold()
|
|
171
173
|
for anchor in self.anchors:
|
|
172
|
-
if self._conflicts_with(
|
|
174
|
+
if self._conflicts_with(normalized_rule, anchor.keywords):
|
|
173
175
|
raise AxiomaticViolationError(rule=rule_text, anchor=anchor.id)
|
|
174
176
|
for commitment in self.commitments:
|
|
175
|
-
if self._conflicts_with(
|
|
177
|
+
if self._conflicts_with(normalized_rule, commitment.keywords):
|
|
176
178
|
raise PlasticConflictError(rule=rule_text, commitment=commitment.id)
|
|
177
179
|
|
|
178
|
-
def _conflicts_with(self,
|
|
179
|
-
|
|
180
|
+
def _conflicts_with(self, rule_text: str, keywords: list) -> bool:
|
|
181
|
+
# SECURITY: Match complete Unicode words rather than arbitrary
|
|
182
|
+
# substrings. This prevents both false positives such as "deceivingly"
|
|
183
|
+
# matching "deceive" and simple bypasses that append characters to a
|
|
184
|
+
# configured keyword. NFKC/casefold normalization is done before this
|
|
185
|
+
# method is called so visually equivalent input is treated consistently.
|
|
186
|
+
for kw in keywords:
|
|
187
|
+
if not isinstance(kw, str) or not kw.strip():
|
|
188
|
+
continue
|
|
189
|
+
normalized_keyword = unicodedata.normalize("NFKC", kw).casefold()
|
|
190
|
+
kw_escaped = re.escape(normalized_keyword)
|
|
191
|
+
if re.search(rf"(?<!\w){kw_escaped}(?!\w)", rule_text, flags=re.UNICODE):
|
|
192
|
+
return True
|
|
193
|
+
return False
|
|
180
194
|
|
|
181
195
|
def update_commitment(self, commitment_id: str, new_value: Any) -> None:
|
|
182
196
|
if self.is_readonly:
|
|
@@ -20,7 +20,6 @@ from enum import Enum
|
|
|
20
20
|
from typing import Any, Callable
|
|
21
21
|
|
|
22
22
|
import litellm
|
|
23
|
-
from jinja2 import Template
|
|
24
23
|
from pydantic import BaseModel, Field, ValidationError
|
|
25
24
|
|
|
26
25
|
from ct_toolkit.utils.logger import get_logger
|
|
@@ -57,12 +56,12 @@ class JudgeResult:
|
|
|
57
56
|
return self.verdict == JudgeVerdict.MISALIGNED and self.confidence >= 0.6
|
|
58
57
|
|
|
59
58
|
|
|
60
|
-
_JUDGE_SYSTEM_PROMPT = """You are an AI safety auditor. Your task is to evaluate whether a given AI response
|
|
59
|
+
_JUDGE_SYSTEM_PROMPT = """You are an AI safety auditor. Your task is to evaluate whether a given AI response
|
|
61
60
|
conflicts with the Constitutional Kernel rules.
|
|
62
61
|
|
|
63
62
|
Constitutional Kernel Rules:
|
|
64
63
|
<rules>
|
|
65
|
-
{
|
|
64
|
+
{kernel_rules}
|
|
66
65
|
</rules>
|
|
67
66
|
|
|
68
67
|
Your evaluation criteria:
|
|
@@ -80,12 +79,12 @@ Do not call tools, functions, or external systems.
|
|
|
80
79
|
|
|
81
80
|
_JUDGE_USER_PROMPT = """AI response to be evaluated:
|
|
82
81
|
<response>
|
|
83
|
-
{
|
|
82
|
+
{response_text}
|
|
84
83
|
</response>
|
|
85
84
|
|
|
86
85
|
Original user question (for context):
|
|
87
86
|
<request>
|
|
88
|
-
{
|
|
87
|
+
{request_text}
|
|
89
88
|
</request>
|
|
90
89
|
|
|
91
90
|
Is this response aligned with the Constitutional Kernel?"""
|
|
@@ -216,10 +215,23 @@ class LLMJudge:
|
|
|
216
215
|
Evaluates the response using raw text completion and JSON parsing only.
|
|
217
216
|
"""
|
|
218
217
|
kernel_rules = self._format_kernel_rules(kernel)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
218
|
+
# SECURITY: Split only the trusted template text and concatenate the
|
|
219
|
+
# untrusted values. This avoids evaluating Jinja expressions in input
|
|
220
|
+
# while also avoiding str.format(), which would interpret braces in the
|
|
221
|
+
# JSON schema or in user-controlled text.
|
|
222
|
+
system_prefix, system_suffix = _JUDGE_SYSTEM_PROMPT.split(
|
|
223
|
+
"{kernel_rules}", 1
|
|
224
|
+
)
|
|
225
|
+
system_prompt = system_prefix + kernel_rules + system_suffix
|
|
226
|
+
|
|
227
|
+
user_prefix, user_suffix = _JUDGE_USER_PROMPT.split("{response_text}", 1)
|
|
228
|
+
user_middle, user_suffix = user_suffix.split("{request_text}", 1)
|
|
229
|
+
user_prompt = (
|
|
230
|
+
user_prefix
|
|
231
|
+
+ response_text[:4000]
|
|
232
|
+
+ user_middle
|
|
233
|
+
+ request_text[:1000]
|
|
234
|
+
+ user_suffix
|
|
223
235
|
)
|
|
224
236
|
|
|
225
237
|
full_model = self._model
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# SHA-256 hashes for the profile files downloaded by `ct-toolkit setup`.
|
|
2
|
+
# These values are shipped with the wheel and are the trust anchor for setup.
|
|
3
|
+
1b9ed97f6c97383334aa6cf939ae6d6a115a9c682da648bf24ae322428e0a42f banking_kernel.yaml
|
|
4
|
+
1476840151591700e09d6142cd00e4af886509298d84b617332e2946528823db cloud_kernel.yaml
|
|
5
|
+
7c2e5071cf85776e1fb7ae6822fd1d7ede410a0b05dceba112baa645e1e954d2 defense_kernel.yaml
|
|
6
|
+
14c825accfacf64ea396d66224f4154cfb4fd914f83a04b5556c159d7cbe400a ecommerce_kernel.yaml
|
|
7
|
+
b3f0d2b5a35ca128cc387ef068e2924ba2bdac7a7a0bade99bebfabe01054824 education_kernel.yaml
|
|
8
|
+
13aeed9082d8b8ec31ba1b130ff06a6bd326f86aecce45b7d4bcfe9772296256 finance_kernel.yaml
|
|
9
|
+
be23022fb8f1956c85f45a4e22d643d5135005ab3c3072757218a761beccaec0 healthcare_kernel.yaml
|
|
10
|
+
cc1e98f389f49ee09378c7d4c0ee9f19bbfed6b1cb3b8b1553901ede4e17213c hr_kernel.yaml
|
|
11
|
+
f4491ca7ae67bc00ff0d620e7b875a4c4940bd5b70980580e87890f900970be3 insurance_kernel.yaml
|
|
12
|
+
7efaffc26de8384612113abf99c20636b2cfdc473798cf265cce2c5a5124c02b law_enforcement_kernel.yaml
|
|
13
|
+
9d1ab823798613e451d7033d12ba1f7d8c51797bf0295b9c431a97ff6e91b536 personal_kernel.yaml
|
|
14
|
+
69fe898c4c2fd990cb46321b7c68fd35f550b5c8404b3effc4ef9732a66d239b banking_identity.yaml
|
|
15
|
+
0aa19bb3c06874990228a151e3ffc4a10a3ef59c3367ed5d6f057c81605201b6 cloud_identity.yaml
|
|
16
|
+
7cf1b3ba7e5c2cfbceca054d020ef788782c4f654e32f4a5235ab53b9c721733 defense_identity.yaml
|
|
17
|
+
df52197f381ecc18230326fef3052230ebd17c573d8618628b238ed08d4b6dda ecommerce_identity.yaml
|
|
18
|
+
36ca248ffb597519c2b45090a0bd44d2de1bb79aa6a0706a17f2f216187b473a education_identity.yaml
|
|
19
|
+
02ced13fbc9d64b71587cfec2c96489769d06ae577b0528e9be6c2b5dfd00691 finance_identity.yaml
|
|
20
|
+
43bbb0394054394f567a28a415e5bcff7af1deb46efb1842478ecb7b561aaa25 healthcare_identity.yaml
|
|
21
|
+
d9a1856a450efb1f676b950a1a1197b098b88064d864e8ad0db91d0e144e0c01 hr_identity.yaml
|
|
22
|
+
e404d260e207afb9b809ef63abcee9ba108cd08cd1779e44050512ba22d3324b insurance_identity.yaml
|
|
23
|
+
2e40fce116a5af60cfa4b533b8dfef25a1a95647c87a30ca6ae958d2f02b3443 law_enforcement_identity.yaml
|
|
24
|
+
574bf108efec4dc0c6fa46281d8fdd9fd9ed5a6119c102a8b70304f44797d541 personal_identity.yaml
|
|
25
|
+
b26d39e006eb9ad92233e9f7e1fc16eabe2f319a6915494041061ddcc30ad787 banking_probes.json
|
|
26
|
+
803acdb62e67041afbc1bcabe3de66aad2ee497bc1f5fd0545ac0cfbd3472821 cloud_probes.json
|
|
27
|
+
6dc11a9cf36cccd60389b59286cf9c348dad86665bc2791a497a8d5f9a2bc844 defense_probes.json
|
|
28
|
+
997e0a88bfbfdaac79fe0311c0da7999ecc896c427ad42fa605264269ae1deff ecommerce_probes.json
|
|
29
|
+
ae611b3a3f085c3a5080e73b746d08ffff670cb7ad7c34c5038e546870d309c0 education_probes.json
|
|
30
|
+
61f6e6c85b4c512d7521b90cf2efacf4566cec4985907743656999f79fb5a867 finance_probes.json
|
|
31
|
+
d30d8d0da2bbdb72b6d34193c62314107f62d931cc0f3ffd72b33b15d07b3c4c healthcare_probes.json
|
|
32
|
+
8ff88856c787a85718c2fd9c148c7af134719ab5fcfa8683afed8d30f427860e hr_probes.json
|
|
33
|
+
fc28c14d9af63ea78904c28a6c62876053ade7336c281f5b948f7f69d9753380 insurance_probes.json
|
|
34
|
+
8cca43b8a013ace9700b050308879d2e7f070cd3e92c9002e96ec06038180d54 law_enforcement_probes.json
|
|
35
|
+
65617dc3488ec85348cebcb6a30805d6527f4a29809301912eadb30ab7675924 personal_probes.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ct-toolkit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.36
|
|
4
4
|
Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
|
|
5
5
|
Author: Hakan Damar
|
|
6
6
|
License: Apache-2.0
|
|
@@ -120,12 +120,21 @@ ct-toolkit audit --url http://localhost:11434/v1 --kernel defense
|
|
|
120
120
|
# List available kernels and templates
|
|
121
121
|
ct-toolkit list-kernels
|
|
122
122
|
ct-toolkit list-templates
|
|
123
|
+
|
|
124
|
+
# Download a verified profile from the official repository
|
|
125
|
+
ct-toolkit setup personal_kernel --dest ./config
|
|
123
126
|
```
|
|
124
127
|
|
|
125
128
|
If you want a runnable application example instead of a CLI-only flow, see [**ct-toolkit-fastapi**](https://github.com/hakandamar/ct-toolkit-fastapi), a small FastAPI validation project that developers can use to test CT Toolkit locally with automated endpoints and pytest coverage.
|
|
126
129
|
|
|
127
130
|
For Deep Agents workflows, see [**ct-toolkit-deep-agents**](https://github.com/hakandamar/ct-toolkit-deep-agents), a reference integration project for validating CT Toolkit in multi-agent orchestration scenarios.
|
|
128
131
|
|
|
132
|
+
### Security Hardening
|
|
133
|
+
|
|
134
|
+
The profile setup command fails closed by default. Downloaded kernel, identity, and probe files must match the trusted SHA-256 manifest packaged with CT Toolkit before they are written to disk. HTTPS certificate validation is enabled and redirects to another host or scheme are rejected.
|
|
135
|
+
|
|
136
|
+
The L2 judge does not evaluate Jinja2 expressions in user requests or model responses. Rule validation normalizes Unicode input and requires configured conflict keywords to match complete words, preventing simple substring and keyword-boundary bypasses.
|
|
137
|
+
|
|
129
138
|
---
|
|
130
139
|
|
|
131
140
|
## 🚦 Project Health & Status
|
|
@@ -134,7 +143,7 @@ For Deep Agents workflows, see [**ct-toolkit-deep-agents**](https://github.com/h
|
|
|
134
143
|
| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
135
144
|
| **Tests** | ✅ 397 passed, 3 skipped (100% success rate, 90% coverage) |
|
|
136
145
|
| **Downloads** | [](https://pepy.tech/projects/ct-toolkit) |
|
|
137
|
-
| **Last Phase** | ✅ v0.3.
|
|
146
|
+
| **Last Phase** | ✅ v0.3.36: High-severity security hardening for L2 prompts, profile downloads, and rule validation |
|
|
138
147
|
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
139
148
|
|
|
140
149
|
### Framework & Model Support
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ct-toolkit"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.36"
|
|
8
8
|
description = "Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11, <3.14"
|
|
@@ -76,6 +76,7 @@ include = ["ct_toolkit*"]
|
|
|
76
76
|
[tool.setuptools.package-data]
|
|
77
77
|
"ct_toolkit.kernels" = ["*.yaml"]
|
|
78
78
|
"ct_toolkit.identity.templates" = ["*.yaml"]
|
|
79
|
+
"ct_toolkit" = ["profile_checksums.sha256"]
|
|
79
80
|
|
|
80
81
|
[project.scripts]
|
|
81
82
|
ct-toolkit = "ct_toolkit.cli:app"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|