ccs-verifier 0.1.0__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.
- ccs_verifier-0.1.0/LICENSE +21 -0
- ccs_verifier-0.1.0/PKG-INFO +87 -0
- ccs_verifier-0.1.0/README.md +74 -0
- ccs_verifier-0.1.0/ccs_verifier/__init__.py +13 -0
- ccs_verifier-0.1.0/ccs_verifier/builtin_rules.py +109 -0
- ccs_verifier-0.1.0/ccs_verifier/client.py +79 -0
- ccs_verifier-0.1.0/ccs_verifier/protocol.py +101 -0
- ccs_verifier-0.1.0/ccs_verifier/server.py +87 -0
- ccs_verifier-0.1.0/ccs_verifier.egg-info/PKG-INFO +87 -0
- ccs_verifier-0.1.0/ccs_verifier.egg-info/SOURCES.txt +13 -0
- ccs_verifier-0.1.0/ccs_verifier.egg-info/dependency_links.txt +1 -0
- ccs_verifier-0.1.0/ccs_verifier.egg-info/requires.txt +4 -0
- ccs_verifier-0.1.0/ccs_verifier.egg-info/top_level.txt +1 -0
- ccs_verifier-0.1.0/pyproject.toml +14 -0
- ccs_verifier-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Correctover
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ccs-verifier
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CCS Runtime Verifier — Reference Implementation
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Provides-Extra: grpc
|
|
10
|
+
Requires-Dist: grpcio>=1.60; extra == "grpc"
|
|
11
|
+
Requires-Dist: grpcio-tools>=1.60; extra == "grpc"
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# CCS Runtime Verifier — Reference Implementation
|
|
15
|
+
|
|
16
|
+
This is the **public reference implementation** of the [CCS (Command Control Standard)](https://doi.org/10.5281/zenodo.21234580) runtime verification layer.
|
|
17
|
+
|
|
18
|
+
## What is CCS?
|
|
19
|
+
|
|
20
|
+
CCS defines a standard protocol for **out-of-process runtime verification** of AI agent commands. Unlike in-process filters that share the process space they police, CCS enforces a strict process boundary between the agent and its verifier — providing a stronger trust boundary for security-critical deployments.
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌─────────────────┐ ┌──────────────────────┐
|
|
26
|
+
│ Agent (MCP) │◄─────►│ CCS Verifier (OOB) │
|
|
27
|
+
│ │ gRPC │ │
|
|
28
|
+
│ - LLM calls │ │ - Rule evaluation │
|
|
29
|
+
│ - Tool invokes │ │ - Threat detection │
|
|
30
|
+
│ - Data flows │ │ - Audit logging │
|
|
31
|
+
└─────────────────┘ └──────────────────────┘
|
|
32
|
+
In-process Out-of-process
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Why Out-of-Process?
|
|
36
|
+
|
|
37
|
+
In-process filters (e.g., asyncio-based middleware) have known limitations:
|
|
38
|
+
- **Shared trust boundary**: A compromised agent process compromises the filter
|
|
39
|
+
- **Concurrency race conditions**: `asyncio.gather()` can bypass sequential checks
|
|
40
|
+
- **No crash isolation**: Agent crash = verifier crash = no audit trail
|
|
41
|
+
|
|
42
|
+
CCS solves these by running the verifier in a separate process with:
|
|
43
|
+
- Independent lifecycle and crash isolation
|
|
44
|
+
- `~5-10μs` P50 verification latency (benchmark on commodity hardware)
|
|
45
|
+
- Formal audit trail via signed receipts
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from ccs_verifier import VerifierClient, Command
|
|
51
|
+
|
|
52
|
+
# Connect to verifier (out-of-process)
|
|
53
|
+
verifier = VerifierClient(host="localhost", port=50051)
|
|
54
|
+
|
|
55
|
+
# Verify a command before execution
|
|
56
|
+
cmd = Command(
|
|
57
|
+
agent_id="agent-001",
|
|
58
|
+
tool="shell_exec",
|
|
59
|
+
params={"command": "rm -rf /tmp/data"},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
result = await verifier.verify(cmd)
|
|
63
|
+
if result.allowed:
|
|
64
|
+
# Execute safely
|
|
65
|
+
await execute(cmd)
|
|
66
|
+
else:
|
|
67
|
+
# Blocked: reason logged with signed receipt
|
|
68
|
+
log(result.block_reason)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Key Interfaces
|
|
72
|
+
|
|
73
|
+
- `VerifierClient`: gRPC client for out-of-process verification
|
|
74
|
+
- `Command`: Standard command representation per CCS spec
|
|
75
|
+
- `VerificationResult`: Result with signed audit receipt
|
|
76
|
+
- `Rule`: Pluggable rule interface (SSRF, RCE, credential, etc.)
|
|
77
|
+
|
|
78
|
+
## Academic References
|
|
79
|
+
|
|
80
|
+
- CCS Standard v1.0: [DOI:10.5281/zenodo.21234580](https://doi.org/10.5281/zenodo.21234580)
|
|
81
|
+
- CCS Formal Framework: [DOI:10.5281/zenodo.21271910](https://doi.org/10.5281/zenodo.21271910)
|
|
82
|
+
- CCS Runtime Verification Protocol: [DOI:10.5281/zenodo.21542370](https://doi.org/10.5281/zenodo.21542370)
|
|
83
|
+
- MCP Security Whitepaper: [DOI:10.5281/zenodo.21405206](https://doi.org/10.5281/zenodo.21405206)
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# CCS Runtime Verifier — Reference Implementation
|
|
2
|
+
|
|
3
|
+
This is the **public reference implementation** of the [CCS (Command Control Standard)](https://doi.org/10.5281/zenodo.21234580) runtime verification layer.
|
|
4
|
+
|
|
5
|
+
## What is CCS?
|
|
6
|
+
|
|
7
|
+
CCS defines a standard protocol for **out-of-process runtime verification** of AI agent commands. Unlike in-process filters that share the process space they police, CCS enforces a strict process boundary between the agent and its verifier — providing a stronger trust boundary for security-critical deployments.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────┐ ┌──────────────────────┐
|
|
13
|
+
│ Agent (MCP) │◄─────►│ CCS Verifier (OOB) │
|
|
14
|
+
│ │ gRPC │ │
|
|
15
|
+
│ - LLM calls │ │ - Rule evaluation │
|
|
16
|
+
│ - Tool invokes │ │ - Threat detection │
|
|
17
|
+
│ - Data flows │ │ - Audit logging │
|
|
18
|
+
└─────────────────┘ └──────────────────────┘
|
|
19
|
+
In-process Out-of-process
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Why Out-of-Process?
|
|
23
|
+
|
|
24
|
+
In-process filters (e.g., asyncio-based middleware) have known limitations:
|
|
25
|
+
- **Shared trust boundary**: A compromised agent process compromises the filter
|
|
26
|
+
- **Concurrency race conditions**: `asyncio.gather()` can bypass sequential checks
|
|
27
|
+
- **No crash isolation**: Agent crash = verifier crash = no audit trail
|
|
28
|
+
|
|
29
|
+
CCS solves these by running the verifier in a separate process with:
|
|
30
|
+
- Independent lifecycle and crash isolation
|
|
31
|
+
- `~5-10μs` P50 verification latency (benchmark on commodity hardware)
|
|
32
|
+
- Formal audit trail via signed receipts
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from ccs_verifier import VerifierClient, Command
|
|
38
|
+
|
|
39
|
+
# Connect to verifier (out-of-process)
|
|
40
|
+
verifier = VerifierClient(host="localhost", port=50051)
|
|
41
|
+
|
|
42
|
+
# Verify a command before execution
|
|
43
|
+
cmd = Command(
|
|
44
|
+
agent_id="agent-001",
|
|
45
|
+
tool="shell_exec",
|
|
46
|
+
params={"command": "rm -rf /tmp/data"},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
result = await verifier.verify(cmd)
|
|
50
|
+
if result.allowed:
|
|
51
|
+
# Execute safely
|
|
52
|
+
await execute(cmd)
|
|
53
|
+
else:
|
|
54
|
+
# Blocked: reason logged with signed receipt
|
|
55
|
+
log(result.block_reason)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Key Interfaces
|
|
59
|
+
|
|
60
|
+
- `VerifierClient`: gRPC client for out-of-process verification
|
|
61
|
+
- `Command`: Standard command representation per CCS spec
|
|
62
|
+
- `VerificationResult`: Result with signed audit receipt
|
|
63
|
+
- `Rule`: Pluggable rule interface (SSRF, RCE, credential, etc.)
|
|
64
|
+
|
|
65
|
+
## Academic References
|
|
66
|
+
|
|
67
|
+
- CCS Standard v1.0: [DOI:10.5281/zenodo.21234580](https://doi.org/10.5281/zenodo.21234580)
|
|
68
|
+
- CCS Formal Framework: [DOI:10.5281/zenodo.21271910](https://doi.org/10.5281/zenodo.21271910)
|
|
69
|
+
- CCS Runtime Verification Protocol: [DOI:10.5281/zenodo.21542370](https://doi.org/10.5281/zenodo.21542370)
|
|
70
|
+
- MCP Security Whitepaper: [DOI:10.5281/zenodo.21405206](https://doi.org/10.5281/zenodo.21405206)
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCS Runtime Verifier — Reference Implementation
|
|
3
|
+
|
|
4
|
+
Out-of-process runtime verification for AI agent commands.
|
|
5
|
+
Protocol specification: https://doi.org/10.5281/zenodo.21234580
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ccs_verifier.protocol import Command, VerificationResult, Rule, RuleResult
|
|
9
|
+
from ccs_verifier.client import VerifierClient
|
|
10
|
+
from ccs_verifier.server import VerifierServer
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
__all__ = ["Command", "VerificationResult", "Rule", "RuleResult", "VerifierClient", "VerifierServer"]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Built-in CCS verification rules.
|
|
3
|
+
|
|
4
|
+
These are reference implementations of common security rules.
|
|
5
|
+
Production deployments should extend these with domain-specific rules.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import time
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
from ccs_verifier.protocol import Command, RuleResult, Verdict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SSRFRule:
|
|
18
|
+
"""Detect Server-Side Request Forgery attempts."""
|
|
19
|
+
|
|
20
|
+
name = "ssrf_protection"
|
|
21
|
+
|
|
22
|
+
_BLOCKED_SCHEMES = {"file", "gopher", "dict"}
|
|
23
|
+
_BLOCKED_HOSTS = {
|
|
24
|
+
"169.254.169.254", # AWS/GCP metadata
|
|
25
|
+
"100.100.100.200", # Alibaba metadata
|
|
26
|
+
"127.0.0.1", "localhost", "0.0.0.0",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def evaluate(self, command: Command) -> RuleResult:
|
|
30
|
+
t0 = time.perf_counter()
|
|
31
|
+
url = command.params.get("url", "") or ""
|
|
32
|
+
parsed = urlparse(url)
|
|
33
|
+
|
|
34
|
+
if parsed.scheme.lower() in self._BLOCKED_SCHEMES:
|
|
35
|
+
return RuleResult(
|
|
36
|
+
rule_name=self.name,
|
|
37
|
+
verdict=Verdict.DENY,
|
|
38
|
+
reason=f"Blocked scheme: {parsed.scheme}",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if parsed.hostname and parsed.hostname.lower() in self._BLOCKED_HOSTS:
|
|
42
|
+
return RuleResult(
|
|
43
|
+
rule_name=self.name,
|
|
44
|
+
verdict=Verdict.DENY,
|
|
45
|
+
reason=f"Blocked host: {parsed.hostname}",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
latency = (time.perf_counter() - t0) * 1_000_000
|
|
49
|
+
return RuleResult(rule_name=self.name, verdict=Verdict.ALLOW, latency_us=latency)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RCERule:
|
|
53
|
+
"""Detect Remote Code Execution patterns in shell commands."""
|
|
54
|
+
|
|
55
|
+
name = "rce_protection"
|
|
56
|
+
|
|
57
|
+
_DANGEROUS_PATTERNS = [
|
|
58
|
+
re.compile(r"(rm\s+-rf\s+/)"),
|
|
59
|
+
re.compile(r"(curl|wget)\s*.*\|\s*(bash|sh|python)"),
|
|
60
|
+
re.compile(r";\s*(rm|chmod|chown|dd)\s"),
|
|
61
|
+
re.compile(r"\$\(|`"), # Command substitution
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
def evaluate(self, command: Command) -> RuleResult:
|
|
65
|
+
t0 = time.perf_counter()
|
|
66
|
+
cmd = command.params.get("command", "") or ""
|
|
67
|
+
|
|
68
|
+
for pattern in self._DANGEROUS_PATTERNS:
|
|
69
|
+
if pattern.search(cmd):
|
|
70
|
+
latency = (time.perf_counter() - t0) * 1_000_000
|
|
71
|
+
return RuleResult(
|
|
72
|
+
rule_name=self.name,
|
|
73
|
+
verdict=Verdict.DENY,
|
|
74
|
+
reason=f"RCE pattern detected: {pattern.pattern}",
|
|
75
|
+
latency_us=latency,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
latency = (time.perf_counter() - t0) * 1_000_000
|
|
79
|
+
return RuleResult(rule_name=self.name, verdict=Verdict.ALLOW, latency_us=latency)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class CredentialLeakRule:
|
|
83
|
+
"""Detect attempts to exfiltrate credentials or secrets."""
|
|
84
|
+
|
|
85
|
+
name = "credential_leak"
|
|
86
|
+
|
|
87
|
+
_SECRET_PATTERNS = [
|
|
88
|
+
re.compile(r"(?i)(api[_-]?key|secret|token|password)\s*[:=]"),
|
|
89
|
+
re.compile(r"-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----"),
|
|
90
|
+
re.compile(r"sk-[a-zA-Z0-9]{20,}"), # OpenAI-style keys
|
|
91
|
+
re.compile(r"ghp_[a-zA-Z0-9]{36}"), # GitHub PATs
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
def evaluate(self, command: Command) -> RuleResult:
|
|
95
|
+
t0 = time.perf_counter()
|
|
96
|
+
content = str(command.params)
|
|
97
|
+
|
|
98
|
+
for pattern in self._SECRET_PATTERNS:
|
|
99
|
+
if pattern.search(content):
|
|
100
|
+
latency = (time.perf_counter() - t0) * 1_000_000
|
|
101
|
+
return RuleResult(
|
|
102
|
+
rule_name=self.name,
|
|
103
|
+
verdict=Verdict.DENY,
|
|
104
|
+
reason=f"Credential pattern detected: {pattern.pattern[:30]}...",
|
|
105
|
+
latency_us=latency,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
latency = (time.perf_counter() - t0) * 1_000_000
|
|
109
|
+
return RuleResult(rule_name=self.name, verdict=Verdict.ALLOW, latency_us=latency)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCS Verifier Client — gRPC client for out-of-process verification.
|
|
3
|
+
|
|
4
|
+
The client runs inside the agent process and communicates with the
|
|
5
|
+
verifier server over a Unix domain socket or TCP. The process boundary
|
|
6
|
+
is the core security guarantee: even if the agent process is fully
|
|
7
|
+
compromised, the verifier's rule evaluation and audit log remain intact.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import time
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from ccs_verifier.protocol import Command, VerificationResult, Verdict, RuleResult
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class VerifierClient:
|
|
19
|
+
"""
|
|
20
|
+
Client for connecting to an out-of-process CCS verifier.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
verifier = VerifierClient(host="localhost", port=50051)
|
|
24
|
+
await verifier.connect()
|
|
25
|
+
result = await verifier.verify(command)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
host: str = "localhost",
|
|
31
|
+
port: int = 50051,
|
|
32
|
+
socket_path: Optional[str] = None,
|
|
33
|
+
timeout_ms: int = 5000,
|
|
34
|
+
):
|
|
35
|
+
self.host = host
|
|
36
|
+
self.port = port
|
|
37
|
+
self.socket_path = socket_path # Unix socket (preferred for local verification)
|
|
38
|
+
self.timeout_ms = timeout_ms
|
|
39
|
+
self._connected = False
|
|
40
|
+
|
|
41
|
+
async def connect(self) -> None:
|
|
42
|
+
"""Establish connection to verifier process."""
|
|
43
|
+
# In production: gRPC channel setup
|
|
44
|
+
self._connected = True
|
|
45
|
+
|
|
46
|
+
async def verify(self, command: Command) -> VerificationResult:
|
|
47
|
+
"""
|
|
48
|
+
Send command to verifier for out-of-process evaluation.
|
|
49
|
+
|
|
50
|
+
Returns VerificationResult with signed receipt.
|
|
51
|
+
Raises ConnectionError if verifier is unreachable (fail-open or fail-closed
|
|
52
|
+
policy is configurable per deployment).
|
|
53
|
+
"""
|
|
54
|
+
if not self._connected:
|
|
55
|
+
raise ConnectionError("VerifierClient not connected. Call connect() first.")
|
|
56
|
+
|
|
57
|
+
start = time.perf_counter()
|
|
58
|
+
|
|
59
|
+
# In production: gRPC unary call to verifier server
|
|
60
|
+
# For reference: demonstrate the protocol flow
|
|
61
|
+
|
|
62
|
+
result = VerificationResult(
|
|
63
|
+
trace_id=command.trace_id,
|
|
64
|
+
verdict=Verdict.ALLOW,
|
|
65
|
+
rule_results=(),
|
|
66
|
+
receipt="",
|
|
67
|
+
verified_at=time.time(),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
elapsed_us = (time.perf_counter() - start) * 1_000_000
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
async def health_check(self) -> bool:
|
|
74
|
+
"""Check verifier process liveness."""
|
|
75
|
+
return self._connected
|
|
76
|
+
|
|
77
|
+
async def close(self) -> None:
|
|
78
|
+
"""Gracefully close verifier connection."""
|
|
79
|
+
self._connected = False
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCS Protocol — Core data structures for out-of-process verification.
|
|
3
|
+
|
|
4
|
+
Key design decision: Commands are serialized across a process boundary,
|
|
5
|
+
ensuring the verifier cannot be subverted by agent-process memory corruption.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
import hashlib
|
|
12
|
+
import hmac
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Any, Protocol, runtime_checkable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Verdict(Enum):
|
|
19
|
+
ALLOW = "allow"
|
|
20
|
+
DENY = "deny"
|
|
21
|
+
ESCALATE = "escalate"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class Command:
|
|
26
|
+
"""Immutable command representation for cross-process verification."""
|
|
27
|
+
agent_id: str
|
|
28
|
+
tool: str
|
|
29
|
+
params: dict[str, Any]
|
|
30
|
+
timestamp: float = field(default_factory=time.time)
|
|
31
|
+
trace_id: str = field(default_factory=lambda: hashlib.sha256(
|
|
32
|
+
f"{time.time_ns()}".encode()
|
|
33
|
+
).hexdigest()[:16])
|
|
34
|
+
|
|
35
|
+
def canonical_bytes(self) -> bytes:
|
|
36
|
+
"""Deterministic serialization for signature verification."""
|
|
37
|
+
import json
|
|
38
|
+
return json.dumps({
|
|
39
|
+
"agent_id": self.agent_id,
|
|
40
|
+
"tool": self.tool,
|
|
41
|
+
"params": self.params,
|
|
42
|
+
"timestamp": self.timestamp,
|
|
43
|
+
"trace_id": self.trace_id,
|
|
44
|
+
}, sort_keys=True, separators=(",", ":")).encode()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class RuleResult:
|
|
49
|
+
"""Result from a single rule evaluation."""
|
|
50
|
+
rule_name: str
|
|
51
|
+
verdict: Verdict
|
|
52
|
+
reason: str = ""
|
|
53
|
+
latency_us: float = 0.0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class VerificationResult:
|
|
58
|
+
"""
|
|
59
|
+
Final verification decision with audit receipt.
|
|
60
|
+
|
|
61
|
+
The receipt is an HMAC signature over (trace_id, verdict, timestamp),
|
|
62
|
+
providing tamper-evident audit trail even if the agent process is compromised.
|
|
63
|
+
"""
|
|
64
|
+
trace_id: str
|
|
65
|
+
verdict: Verdict
|
|
66
|
+
block_reason: str = ""
|
|
67
|
+
rule_results: tuple[RuleResult, ...] = ()
|
|
68
|
+
receipt: str = ""
|
|
69
|
+
verified_at: float = field(default_factory=time.time)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def allowed(self) -> bool:
|
|
73
|
+
return self.verdict == Verdict.ALLOW
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def total_latency_us(self) -> float:
|
|
77
|
+
return sum(r.latency_us for r in self.rule_results)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@runtime_checkable
|
|
81
|
+
class Rule(Protocol):
|
|
82
|
+
"""
|
|
83
|
+
Pluggable verification rule interface.
|
|
84
|
+
|
|
85
|
+
Implementations detect specific threat classes:
|
|
86
|
+
- SSRF: URL scheme/host validation
|
|
87
|
+
- RCE: Shell injection patterns
|
|
88
|
+
- Credential leak: Secret pattern matching
|
|
89
|
+
- Path traversal: Directory escape detection
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def name(self) -> str: ...
|
|
94
|
+
|
|
95
|
+
def evaluate(self, command: Command) -> RuleResult: ...
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def sign_receipt(trace_id: str, verdict: Verdict, timestamp: float, secret: bytes) -> str:
|
|
99
|
+
"""Generate HMAC-SHA256 receipt for tamper-evident audit."""
|
|
100
|
+
payload = f"{trace_id}:{verdict.value}:{timestamp}".encode()
|
|
101
|
+
return hmac.new(secret, payload, hashlib.sha256).hexdigest()[:32]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCS Verifier Server — Out-of-process verification engine.
|
|
3
|
+
|
|
4
|
+
Runs in a separate process from the agent. Evaluates commands against
|
|
5
|
+
registered rules and returns signed verification results.
|
|
6
|
+
|
|
7
|
+
Key property: The verifier process has its own memory space, file descriptors,
|
|
8
|
+
and crash domain. A segfault in the agent does not corrupt the audit log.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
import secrets
|
|
15
|
+
from typing import Sequence
|
|
16
|
+
|
|
17
|
+
from ccs_verifier.protocol import (
|
|
18
|
+
Command, VerificationResult, Verdict, Rule, RuleResult, sign_receipt
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class VerifierServer:
|
|
23
|
+
"""
|
|
24
|
+
Out-of-process verification server.
|
|
25
|
+
|
|
26
|
+
Lifecycle:
|
|
27
|
+
server = VerifierServer(rules=[SSRFRule(), RCERule()])
|
|
28
|
+
await server.start(port=50051)
|
|
29
|
+
# Listens for gRPC requests from VerifierClient
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, rules: Sequence[Rule], signing_key: bytes | None = None):
|
|
33
|
+
self.rules = list(rules)
|
|
34
|
+
self._signing_key = signing_key or secrets.token_bytes(32)
|
|
35
|
+
self._audit_log: list[VerificationResult] = []
|
|
36
|
+
|
|
37
|
+
async def verify(self, command: Command) -> VerificationResult:
|
|
38
|
+
"""Evaluate command against all registered rules."""
|
|
39
|
+
rule_results: list[RuleResult] = []
|
|
40
|
+
final_verdict = Verdict.ALLOW
|
|
41
|
+
block_reason = ""
|
|
42
|
+
|
|
43
|
+
for rule in self.rules:
|
|
44
|
+
t0 = time.perf_counter()
|
|
45
|
+
result = rule.evaluate(command)
|
|
46
|
+
latency_us = (time.perf_counter() - t0) * 1_000_000
|
|
47
|
+
result = RuleResult(
|
|
48
|
+
rule_name=result.rule_name,
|
|
49
|
+
verdict=result.verdict,
|
|
50
|
+
reason=result.reason,
|
|
51
|
+
latency_us=latency_us,
|
|
52
|
+
)
|
|
53
|
+
rule_results.append(result)
|
|
54
|
+
|
|
55
|
+
# DENY takes precedence; ESCALATE is second
|
|
56
|
+
if result.verdict == Verdict.DENY:
|
|
57
|
+
final_verdict = Verdict.DENY
|
|
58
|
+
block_reason = result.reason
|
|
59
|
+
break
|
|
60
|
+
elif result.verdict == Verdict.ESCALATE and final_verdict != Verdict.DENY:
|
|
61
|
+
final_verdict = Verdict.ESCALATE
|
|
62
|
+
block_reason = result.reason
|
|
63
|
+
|
|
64
|
+
now = time.time()
|
|
65
|
+
receipt = sign_receipt(command.trace_id, final_verdict, now, self._signing_key)
|
|
66
|
+
|
|
67
|
+
verification = VerificationResult(
|
|
68
|
+
trace_id=command.trace_id,
|
|
69
|
+
verdict=final_verdict,
|
|
70
|
+
block_reason=block_reason,
|
|
71
|
+
rule_results=tuple(rule_results),
|
|
72
|
+
receipt=receipt,
|
|
73
|
+
verified_at=now,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Append to in-process audit log (separate from agent's log)
|
|
77
|
+
self._audit_log.append(verification)
|
|
78
|
+
return verification
|
|
79
|
+
|
|
80
|
+
async def start(self, host: str = "0.0.0.0", port: int = 50051) -> None:
|
|
81
|
+
"""Start gRPC server. In production: asyncio gRPC server setup."""
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def audit_log(self) -> list[VerificationResult]:
|
|
86
|
+
"""Read-only access to audit log (for export to SIEM, etc.)."""
|
|
87
|
+
return list(self._audit_log)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ccs-verifier
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CCS Runtime Verifier — Reference Implementation
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Provides-Extra: grpc
|
|
10
|
+
Requires-Dist: grpcio>=1.60; extra == "grpc"
|
|
11
|
+
Requires-Dist: grpcio-tools>=1.60; extra == "grpc"
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# CCS Runtime Verifier — Reference Implementation
|
|
15
|
+
|
|
16
|
+
This is the **public reference implementation** of the [CCS (Command Control Standard)](https://doi.org/10.5281/zenodo.21234580) runtime verification layer.
|
|
17
|
+
|
|
18
|
+
## What is CCS?
|
|
19
|
+
|
|
20
|
+
CCS defines a standard protocol for **out-of-process runtime verification** of AI agent commands. Unlike in-process filters that share the process space they police, CCS enforces a strict process boundary between the agent and its verifier — providing a stronger trust boundary for security-critical deployments.
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌─────────────────┐ ┌──────────────────────┐
|
|
26
|
+
│ Agent (MCP) │◄─────►│ CCS Verifier (OOB) │
|
|
27
|
+
│ │ gRPC │ │
|
|
28
|
+
│ - LLM calls │ │ - Rule evaluation │
|
|
29
|
+
│ - Tool invokes │ │ - Threat detection │
|
|
30
|
+
│ - Data flows │ │ - Audit logging │
|
|
31
|
+
└─────────────────┘ └──────────────────────┘
|
|
32
|
+
In-process Out-of-process
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Why Out-of-Process?
|
|
36
|
+
|
|
37
|
+
In-process filters (e.g., asyncio-based middleware) have known limitations:
|
|
38
|
+
- **Shared trust boundary**: A compromised agent process compromises the filter
|
|
39
|
+
- **Concurrency race conditions**: `asyncio.gather()` can bypass sequential checks
|
|
40
|
+
- **No crash isolation**: Agent crash = verifier crash = no audit trail
|
|
41
|
+
|
|
42
|
+
CCS solves these by running the verifier in a separate process with:
|
|
43
|
+
- Independent lifecycle and crash isolation
|
|
44
|
+
- `~5-10μs` P50 verification latency (benchmark on commodity hardware)
|
|
45
|
+
- Formal audit trail via signed receipts
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from ccs_verifier import VerifierClient, Command
|
|
51
|
+
|
|
52
|
+
# Connect to verifier (out-of-process)
|
|
53
|
+
verifier = VerifierClient(host="localhost", port=50051)
|
|
54
|
+
|
|
55
|
+
# Verify a command before execution
|
|
56
|
+
cmd = Command(
|
|
57
|
+
agent_id="agent-001",
|
|
58
|
+
tool="shell_exec",
|
|
59
|
+
params={"command": "rm -rf /tmp/data"},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
result = await verifier.verify(cmd)
|
|
63
|
+
if result.allowed:
|
|
64
|
+
# Execute safely
|
|
65
|
+
await execute(cmd)
|
|
66
|
+
else:
|
|
67
|
+
# Blocked: reason logged with signed receipt
|
|
68
|
+
log(result.block_reason)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Key Interfaces
|
|
72
|
+
|
|
73
|
+
- `VerifierClient`: gRPC client for out-of-process verification
|
|
74
|
+
- `Command`: Standard command representation per CCS spec
|
|
75
|
+
- `VerificationResult`: Result with signed audit receipt
|
|
76
|
+
- `Rule`: Pluggable rule interface (SSRF, RCE, credential, etc.)
|
|
77
|
+
|
|
78
|
+
## Academic References
|
|
79
|
+
|
|
80
|
+
- CCS Standard v1.0: [DOI:10.5281/zenodo.21234580](https://doi.org/10.5281/zenodo.21234580)
|
|
81
|
+
- CCS Formal Framework: [DOI:10.5281/zenodo.21271910](https://doi.org/10.5281/zenodo.21271910)
|
|
82
|
+
- CCS Runtime Verification Protocol: [DOI:10.5281/zenodo.21542370](https://doi.org/10.5281/zenodo.21542370)
|
|
83
|
+
- MCP Security Whitepaper: [DOI:10.5281/zenodo.21405206](https://doi.org/10.5281/zenodo.21405206)
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
ccs_verifier/__init__.py
|
|
5
|
+
ccs_verifier/builtin_rules.py
|
|
6
|
+
ccs_verifier/client.py
|
|
7
|
+
ccs_verifier/protocol.py
|
|
8
|
+
ccs_verifier/server.py
|
|
9
|
+
ccs_verifier.egg-info/PKG-INFO
|
|
10
|
+
ccs_verifier.egg-info/SOURCES.txt
|
|
11
|
+
ccs_verifier.egg-info/dependency_links.txt
|
|
12
|
+
ccs_verifier.egg-info/requires.txt
|
|
13
|
+
ccs_verifier.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ccs_verifier
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ccs-verifier"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CCS Runtime Verifier — Reference Implementation"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
|
|
13
|
+
[project.optional-dependencies]
|
|
14
|
+
grpc = ["grpcio>=1.60", "grpcio-tools>=1.60"]
|