capiscio-sdk 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- capiscio_sdk/__init__.py +2 -0
- capiscio_sdk/config.py +1 -1
- capiscio_sdk/errors.py +11 -1
- capiscio_sdk/executor.py +17 -0
- capiscio_sdk/integrations/fastapi.py +73 -0
- capiscio_sdk/simple_guard.py +354 -0
- capiscio_sdk/types.py +1 -1
- capiscio_sdk-0.3.0.dist-info/METADATA +126 -0
- {capiscio_sdk-0.2.0.dist-info → capiscio_sdk-0.3.0.dist-info}/RECORD +11 -9
- capiscio_sdk-0.2.0.dist-info/METADATA +0 -221
- {capiscio_sdk-0.2.0.dist-info → capiscio_sdk-0.3.0.dist-info}/WHEEL +0 -0
- {capiscio_sdk-0.2.0.dist-info → capiscio_sdk-0.3.0.dist-info}/licenses/LICENSE +0 -0
capiscio_sdk/__init__.py
CHANGED
|
@@ -12,6 +12,7 @@ __version__ = "0.2.0"
|
|
|
12
12
|
|
|
13
13
|
# Core exports
|
|
14
14
|
from .executor import CapiscioSecurityExecutor, secure, secure_agent
|
|
15
|
+
from .simple_guard import SimpleGuard
|
|
15
16
|
from .config import SecurityConfig, DownstreamConfig, UpstreamConfig
|
|
16
17
|
from .errors import (
|
|
17
18
|
CapiscioSecurityError,
|
|
@@ -25,6 +26,7 @@ from .types import ValidationResult, ValidationIssue, ValidationSeverity
|
|
|
25
26
|
__all__ = [
|
|
26
27
|
"__version__",
|
|
27
28
|
"CapiscioSecurityExecutor",
|
|
29
|
+
"SimpleGuard",
|
|
28
30
|
"secure",
|
|
29
31
|
"secure_agent",
|
|
30
32
|
"SecurityConfig",
|
capiscio_sdk/config.py
CHANGED
capiscio_sdk/errors.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Error types for Capiscio
|
|
1
|
+
"""Error types for Capiscio Python SDK."""
|
|
2
2
|
from typing import Optional, List, Dict, Any
|
|
3
3
|
from .types import ValidationResult
|
|
4
4
|
|
|
@@ -67,3 +67,13 @@ class CapiscioTimeoutError(CapiscioSecurityError):
|
|
|
67
67
|
"""Operation timed out."""
|
|
68
68
|
|
|
69
69
|
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ConfigurationError(CapiscioSecurityError):
|
|
73
|
+
"""Missing keys or invalid paths (SimpleGuard)."""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class VerificationError(CapiscioSecurityError):
|
|
78
|
+
"""Invalid signature, expired token, or untrusted key (SimpleGuard)."""
|
|
79
|
+
pass
|
capiscio_sdk/executor.py
CHANGED
|
@@ -138,6 +138,23 @@ class CapiscioSecurityExecutor:
|
|
|
138
138
|
# Cancellation just passes through - no security checks needed
|
|
139
139
|
await self.delegate.cancel(context, event_queue)
|
|
140
140
|
|
|
141
|
+
async def validate_agent_card(self, url: str) -> ValidationResult:
|
|
142
|
+
"""
|
|
143
|
+
Validate an agent card from a URL.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
url: URL to the agent card or agent root
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
ValidationResult with scores
|
|
150
|
+
"""
|
|
151
|
+
from .validators.agent_card import AgentCardValidator
|
|
152
|
+
validator = AgentCardValidator()
|
|
153
|
+
try:
|
|
154
|
+
return await validator.fetch_and_validate(url)
|
|
155
|
+
finally:
|
|
156
|
+
await validator.http_client.aclose()
|
|
157
|
+
|
|
141
158
|
def _validate_message(self, message: Dict[str, Any]) -> ValidationResult:
|
|
142
159
|
"""Validate message with caching."""
|
|
143
160
|
# Try cache first
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""FastAPI integration for Capiscio SimpleGuard."""
|
|
2
|
+
from typing import Callable, Awaitable, Any, Dict
|
|
3
|
+
try:
|
|
4
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
5
|
+
from starlette.requests import Request
|
|
6
|
+
from starlette.responses import JSONResponse, Response
|
|
7
|
+
from starlette.types import ASGIApp
|
|
8
|
+
except ImportError:
|
|
9
|
+
raise ImportError("FastAPI/Starlette is required for this integration. Install with 'pip install fastapi'.")
|
|
10
|
+
|
|
11
|
+
from ..simple_guard import SimpleGuard
|
|
12
|
+
from ..errors import VerificationError
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
class CapiscioMiddleware(BaseHTTPMiddleware):
|
|
16
|
+
"""
|
|
17
|
+
Middleware to enforce A2A identity verification on incoming requests.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, app: ASGIApp, guard: SimpleGuard) -> None:
|
|
20
|
+
super().__init__(app)
|
|
21
|
+
self.guard = guard
|
|
22
|
+
|
|
23
|
+
async def dispatch(
|
|
24
|
+
self,
|
|
25
|
+
request: Request,
|
|
26
|
+
call_next: Callable[[Request], Awaitable[Response]]
|
|
27
|
+
) -> Response:
|
|
28
|
+
# Allow health checks or public endpoints if needed
|
|
29
|
+
# For now, we assume everything under /agent/ needs protection
|
|
30
|
+
# But let's just check for the header.
|
|
31
|
+
|
|
32
|
+
if request.method == "OPTIONS":
|
|
33
|
+
return await call_next(request)
|
|
34
|
+
|
|
35
|
+
auth_header = request.headers.get("X-Capiscio-JWS")
|
|
36
|
+
|
|
37
|
+
# If no header, we might let it pass but mark as unverified?
|
|
38
|
+
# The mandate says: "Returns 401 (missing) or 403 (invalid)."
|
|
39
|
+
if not auth_header:
|
|
40
|
+
return JSONResponse(
|
|
41
|
+
{"error": "Missing X-Capiscio-JWS header. This endpoint is protected by CapiscIO."},
|
|
42
|
+
status_code=401
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
start_time = time.perf_counter()
|
|
46
|
+
try:
|
|
47
|
+
# Read the body for integrity check
|
|
48
|
+
body_bytes = await request.body()
|
|
49
|
+
|
|
50
|
+
# Verify the JWS with body
|
|
51
|
+
payload = self.guard.verify_inbound(auth_header, body=body_bytes)
|
|
52
|
+
|
|
53
|
+
# Reset the receive channel so downstream can read the body
|
|
54
|
+
async def receive() -> Dict[str, Any]:
|
|
55
|
+
return {"type": "http.request", "body": body_bytes, "more_body": False}
|
|
56
|
+
request._receive = receive
|
|
57
|
+
|
|
58
|
+
# Inject claims into request.state
|
|
59
|
+
request.state.agent = payload
|
|
60
|
+
request.state.agent_id = payload.get("iss")
|
|
61
|
+
|
|
62
|
+
except VerificationError as e:
|
|
63
|
+
return JSONResponse({"error": f"Access Denied: {str(e)}"}, status_code=403)
|
|
64
|
+
|
|
65
|
+
verification_duration = (time.perf_counter() - start_time) * 1000
|
|
66
|
+
|
|
67
|
+
response = await call_next(request)
|
|
68
|
+
|
|
69
|
+
# Add Server-Timing header (standard for performance metrics)
|
|
70
|
+
# Syntax: metric_name;dur=123.4;desc="Description"
|
|
71
|
+
response.headers["Server-Timing"] = f"capiscio-auth;dur={verification_duration:.3f};desc=\"CapiscIO Verification\""
|
|
72
|
+
|
|
73
|
+
return response
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""SimpleGuard: Local, zero-config security for A2A agents."""
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import base64
|
|
6
|
+
import hashlib
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional, Dict, Any, Union, cast
|
|
10
|
+
|
|
11
|
+
import jwt
|
|
12
|
+
from cryptography.hazmat.primitives import serialization
|
|
13
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
14
|
+
|
|
15
|
+
from .errors import ConfigurationError, VerificationError
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
MAX_TOKEN_AGE = 60
|
|
20
|
+
CLOCK_SKEW_LEEWAY = 5
|
|
21
|
+
|
|
22
|
+
class SimpleGuard:
|
|
23
|
+
"""
|
|
24
|
+
The "Customs Officer" for your Agent.
|
|
25
|
+
|
|
26
|
+
Enforces Identity (JWS) and Protocol (A2A) validation locally.
|
|
27
|
+
Prioritizes local utility and zero-configuration.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
base_dir: Optional[Union[str, Path]] = None,
|
|
33
|
+
dev_mode: bool = False
|
|
34
|
+
) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Initialize SimpleGuard.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
base_dir: Starting directory to search for config (defaults to cwd).
|
|
40
|
+
dev_mode: If True, auto-generates keys and agent-card.json.
|
|
41
|
+
"""
|
|
42
|
+
self.dev_mode = dev_mode
|
|
43
|
+
|
|
44
|
+
# 1. Safety Check
|
|
45
|
+
if self.dev_mode and os.getenv("CAPISCIO_ENV") == "prod":
|
|
46
|
+
logger.critical(
|
|
47
|
+
"CRITICAL: SimpleGuard initialized in dev_mode=True but CAPISCIO_ENV=prod. "
|
|
48
|
+
"This is insecure! Disable dev_mode in production."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# 2. Resolve base_dir (Walk up logic)
|
|
52
|
+
self.project_root = self._resolve_project_root(base_dir)
|
|
53
|
+
self.keys_dir = self.project_root / "capiscio_keys"
|
|
54
|
+
self.trusted_dir = self.keys_dir / "trusted"
|
|
55
|
+
self.agent_card_path = self.project_root / "agent-card.json"
|
|
56
|
+
self.private_key_path = self.keys_dir / "private.pem"
|
|
57
|
+
self.public_key_path = self.keys_dir / "public.pem"
|
|
58
|
+
|
|
59
|
+
# 3. Load or Generate agent-card.json
|
|
60
|
+
self.agent_id: str
|
|
61
|
+
self.signing_kid: str
|
|
62
|
+
self._load_or_generate_card()
|
|
63
|
+
|
|
64
|
+
# 4. Load or Generate Keys
|
|
65
|
+
self._private_key: ed25519.Ed25519PrivateKey
|
|
66
|
+
self._load_or_generate_keys()
|
|
67
|
+
|
|
68
|
+
# 5. Load Trust Store (and self-trust in dev mode)
|
|
69
|
+
self._setup_trust_store()
|
|
70
|
+
|
|
71
|
+
def sign_outbound(self, payload: Dict[str, Any], body: Optional[bytes] = None) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Sign a payload for outbound transmission.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
payload: The JSON payload to sign.
|
|
77
|
+
body: Optional HTTP body bytes to bind to the signature.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Compact JWS string.
|
|
81
|
+
"""
|
|
82
|
+
# Inject issuer if missing
|
|
83
|
+
if "iss" not in payload:
|
|
84
|
+
payload["iss"] = self.agent_id
|
|
85
|
+
|
|
86
|
+
# Replay Protection: Inject timestamps
|
|
87
|
+
now = int(time.time())
|
|
88
|
+
payload["iat"] = now
|
|
89
|
+
payload["exp"] = now + MAX_TOKEN_AGE
|
|
90
|
+
|
|
91
|
+
# Integrity: Calculate Body Hash if body is provided
|
|
92
|
+
if body is not None:
|
|
93
|
+
# SHA-256 hash
|
|
94
|
+
sha256_hash = hashlib.sha256(body).digest()
|
|
95
|
+
# Base64Url encode (no padding)
|
|
96
|
+
bh = base64.urlsafe_b64encode(sha256_hash).decode('utf-8').rstrip('=')
|
|
97
|
+
payload["bh"] = bh
|
|
98
|
+
|
|
99
|
+
# Prepare headers
|
|
100
|
+
headers = {
|
|
101
|
+
"kid": self.signing_kid,
|
|
102
|
+
"typ": "JWT",
|
|
103
|
+
"alg": "EdDSA"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Sign
|
|
107
|
+
try:
|
|
108
|
+
token = jwt.encode(
|
|
109
|
+
payload,
|
|
110
|
+
self._private_key,
|
|
111
|
+
algorithm="EdDSA",
|
|
112
|
+
headers=headers
|
|
113
|
+
)
|
|
114
|
+
return token
|
|
115
|
+
except Exception as e:
|
|
116
|
+
raise ConfigurationError(f"Failed to sign payload: {e}")
|
|
117
|
+
|
|
118
|
+
def verify_inbound(self, jws: str, body: Optional[bytes] = None) -> Dict[str, Any]:
|
|
119
|
+
"""
|
|
120
|
+
Verify an inbound JWS.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
jws: The compact JWS string.
|
|
124
|
+
body: Optional HTTP body bytes to verify against 'bh' claim.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
The verified payload.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
VerificationError: If signature is invalid, key is untrusted, or integrity check fails.
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
# 1. Parse Header to get kid (without verifying yet)
|
|
134
|
+
header = jwt.get_unverified_header(jws)
|
|
135
|
+
kid = header.get("kid")
|
|
136
|
+
|
|
137
|
+
if not kid:
|
|
138
|
+
raise VerificationError("Missing 'kid' in JWS header.")
|
|
139
|
+
|
|
140
|
+
# 2. Resolution: Look for trusted key
|
|
141
|
+
trusted_key_path = self.trusted_dir / f"{kid}.pem"
|
|
142
|
+
if not trusted_key_path.exists():
|
|
143
|
+
logger.warning(f'{{"event": "agent_call_denied", "kid": "{kid}", "reason": "untrusted_key"}}')
|
|
144
|
+
raise VerificationError(f"Untrusted key ID: {kid}")
|
|
145
|
+
|
|
146
|
+
# Load the trusted public key
|
|
147
|
+
with open(trusted_key_path, "rb") as f:
|
|
148
|
+
public_key = serialization.load_pem_public_key(f.read())
|
|
149
|
+
# Ensure it is a key type compatible with jwt.decode (Ed25519PublicKey is supported)
|
|
150
|
+
# We cast to Any to satisfy mypy's strict check against the specific union
|
|
151
|
+
public_key = cast(Any, public_key)
|
|
152
|
+
|
|
153
|
+
# 3. Verify Signature
|
|
154
|
+
payload = jwt.decode(
|
|
155
|
+
jws,
|
|
156
|
+
public_key,
|
|
157
|
+
algorithms=["EdDSA"],
|
|
158
|
+
options={"verify_aud": False} # Audience verification depends on context, skipping for generic guard
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Cast payload to Dict[str, Any]
|
|
162
|
+
payload = cast(Dict[str, Any], payload)
|
|
163
|
+
|
|
164
|
+
# 4. Integrity Check (Body Hash)
|
|
165
|
+
if "bh" in payload:
|
|
166
|
+
if body is None:
|
|
167
|
+
raise VerificationError("JWS contains 'bh' claim but no body provided for verification.")
|
|
168
|
+
|
|
169
|
+
# Calculate hash of received body
|
|
170
|
+
sha256_hash = hashlib.sha256(body).digest()
|
|
171
|
+
calculated_bh = base64.urlsafe_b64encode(sha256_hash).decode('utf-8').rstrip('=')
|
|
172
|
+
|
|
173
|
+
if calculated_bh != payload["bh"]:
|
|
174
|
+
logger.warning(f'{{"event": "agent_call_denied", "kid": "{kid}", "reason": "integrity_check_failed"}}')
|
|
175
|
+
raise VerificationError("Integrity Check Failed: Body modified")
|
|
176
|
+
|
|
177
|
+
# 5. Replay Protection (Timestamp Enforcement)
|
|
178
|
+
now = int(time.time())
|
|
179
|
+
exp = payload.get("exp")
|
|
180
|
+
iat = payload.get("iat")
|
|
181
|
+
|
|
182
|
+
if exp is None or iat is None:
|
|
183
|
+
raise VerificationError("Missing timestamp claims (exp, iat).")
|
|
184
|
+
|
|
185
|
+
if now > (exp + CLOCK_SKEW_LEEWAY):
|
|
186
|
+
logger.warning(f'{{"event": "agent_call_denied", "kid": "{kid}", "reason": "token_expired"}}')
|
|
187
|
+
raise VerificationError("Token expired.")
|
|
188
|
+
|
|
189
|
+
if now < (iat - CLOCK_SKEW_LEEWAY):
|
|
190
|
+
logger.warning(f'{{"event": "agent_call_denied", "kid": "{kid}", "reason": "clock_skew"}}')
|
|
191
|
+
raise VerificationError("Token not yet valid (Clock skew).")
|
|
192
|
+
|
|
193
|
+
# 6. Observability
|
|
194
|
+
iss = payload.get("iss", "unknown")
|
|
195
|
+
logger.info(f'{{"event": "agent_call_allowed", "iss": "{iss}", "kid": "{kid}"}}')
|
|
196
|
+
|
|
197
|
+
return payload
|
|
198
|
+
|
|
199
|
+
except jwt.InvalidSignatureError:
|
|
200
|
+
logger.warning(f'{{"event": "agent_call_denied", "kid": "{kid}", "reason": "invalid_signature"}}')
|
|
201
|
+
raise VerificationError("Invalid signature.")
|
|
202
|
+
except jwt.ExpiredSignatureError:
|
|
203
|
+
logger.warning(f'{{"event": "agent_call_denied", "kid": "{kid}", "reason": "token_expired"}}')
|
|
204
|
+
raise VerificationError("Token expired.")
|
|
205
|
+
except jwt.DecodeError:
|
|
206
|
+
raise VerificationError("Invalid JWS format.")
|
|
207
|
+
except Exception as e:
|
|
208
|
+
if isinstance(e, VerificationError):
|
|
209
|
+
raise
|
|
210
|
+
raise VerificationError(f"Verification failed: {e}")
|
|
211
|
+
|
|
212
|
+
def make_headers(self, payload: Dict[str, Any], body: Optional[bytes] = None) -> Dict[str, str]:
|
|
213
|
+
"""Helper to generate the headers containing the JWS."""
|
|
214
|
+
token = self.sign_outbound(payload, body=body)
|
|
215
|
+
return {"X-Capiscio-JWS": token}
|
|
216
|
+
|
|
217
|
+
def _resolve_project_root(self, base_dir: Optional[Union[str, Path]]) -> Path:
|
|
218
|
+
"""Walk up the directory tree to find agent-card.json or stop at root."""
|
|
219
|
+
current = Path(base_dir or os.getcwd()).resolve()
|
|
220
|
+
|
|
221
|
+
# If we are in dev mode and nothing exists, we might just use cwd
|
|
222
|
+
# But let's try to find an existing project structure first
|
|
223
|
+
search_path = current
|
|
224
|
+
while search_path != search_path.parent:
|
|
225
|
+
if (search_path / "agent-card.json").exists():
|
|
226
|
+
return search_path
|
|
227
|
+
search_path = search_path.parent
|
|
228
|
+
|
|
229
|
+
# If not found, default to cwd
|
|
230
|
+
return current
|
|
231
|
+
|
|
232
|
+
def _load_or_generate_card(self) -> None:
|
|
233
|
+
"""Load agent-card.json or generate a minimal one in dev_mode."""
|
|
234
|
+
if self.agent_card_path.exists():
|
|
235
|
+
try:
|
|
236
|
+
with open(self.agent_card_path, "r") as f:
|
|
237
|
+
data = json.load(f)
|
|
238
|
+
self.agent_id = data.get("agent_id")
|
|
239
|
+
# Assuming the first key is the signing key for now, or looking for a specific structure
|
|
240
|
+
# The mandate says: "Cache self.agent_id and self.signing_kid"
|
|
241
|
+
# We need to find the kid from the keys array.
|
|
242
|
+
keys = data.get("public_keys", [])
|
|
243
|
+
if not keys:
|
|
244
|
+
raise ConfigurationError("agent-card.json missing 'public_keys'.")
|
|
245
|
+
self.signing_kid = keys[0].get("kid")
|
|
246
|
+
|
|
247
|
+
if not self.agent_id or not self.signing_kid:
|
|
248
|
+
raise ConfigurationError("agent-card.json missing 'agent_id' or 'public_keys[0].kid'.")
|
|
249
|
+
except Exception as e:
|
|
250
|
+
raise ConfigurationError(f"Failed to load agent-card.json: {e}")
|
|
251
|
+
elif self.dev_mode:
|
|
252
|
+
# Generate minimal card
|
|
253
|
+
logger.info("Dev Mode: Generating minimal agent-card.json")
|
|
254
|
+
self.agent_id = "local-dev-agent"
|
|
255
|
+
self.signing_kid = "local-dev-key"
|
|
256
|
+
|
|
257
|
+
# We will populate the JWK part after generating the key in the next step
|
|
258
|
+
# For now, just set the basics, we'll write the file after key gen
|
|
259
|
+
else:
|
|
260
|
+
raise ConfigurationError(f"agent-card.json not found at {self.project_root}")
|
|
261
|
+
|
|
262
|
+
def _load_or_generate_keys(self) -> None:
|
|
263
|
+
"""Load private.pem or generate it in dev_mode."""
|
|
264
|
+
if not self.keys_dir.exists():
|
|
265
|
+
if self.dev_mode:
|
|
266
|
+
self.keys_dir.mkdir(parents=True, exist_ok=True)
|
|
267
|
+
self.trusted_dir.mkdir(parents=True, exist_ok=True)
|
|
268
|
+
else:
|
|
269
|
+
raise ConfigurationError(f"capiscio_keys directory not found at {self.keys_dir}")
|
|
270
|
+
|
|
271
|
+
if self.private_key_path.exists():
|
|
272
|
+
try:
|
|
273
|
+
with open(self.private_key_path, "rb") as f:
|
|
274
|
+
loaded_key = serialization.load_pem_private_key(
|
|
275
|
+
f.read(), password=None
|
|
276
|
+
)
|
|
277
|
+
if not isinstance(loaded_key, ed25519.Ed25519PrivateKey):
|
|
278
|
+
raise ConfigurationError("Private key is not an Ed25519 key.")
|
|
279
|
+
self._private_key = loaded_key
|
|
280
|
+
except Exception as e:
|
|
281
|
+
raise ConfigurationError(f"Failed to load private.pem: {e}")
|
|
282
|
+
elif self.dev_mode:
|
|
283
|
+
logger.info("Dev Mode: Generating Ed25519 keypair")
|
|
284
|
+
self._private_key = ed25519.Ed25519PrivateKey.generate()
|
|
285
|
+
|
|
286
|
+
# Save Private Key
|
|
287
|
+
with open(self.private_key_path, "wb") as f:
|
|
288
|
+
f.write(self._private_key.private_bytes(
|
|
289
|
+
encoding=serialization.Encoding.PEM,
|
|
290
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
291
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
292
|
+
))
|
|
293
|
+
|
|
294
|
+
# Save Public Key
|
|
295
|
+
public_key = self._private_key.public_key()
|
|
296
|
+
pem_public = public_key.public_bytes(
|
|
297
|
+
encoding=serialization.Encoding.PEM,
|
|
298
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
|
299
|
+
)
|
|
300
|
+
with open(self.public_key_path, "wb") as f:
|
|
301
|
+
f.write(pem_public)
|
|
302
|
+
|
|
303
|
+
# Now update agent-card.json with the JWK
|
|
304
|
+
self._update_agent_card_with_jwk(public_key)
|
|
305
|
+
else:
|
|
306
|
+
raise ConfigurationError(f"private.pem not found at {self.private_key_path}")
|
|
307
|
+
|
|
308
|
+
def _update_agent_card_with_jwk(self, public_key: ed25519.Ed25519PublicKey) -> None:
|
|
309
|
+
"""Helper to write the agent-card.json with the generated key."""
|
|
310
|
+
# Convert Ed25519 public key to JWK parameters
|
|
311
|
+
# Ed25519 keys are simple: x is the raw bytes
|
|
312
|
+
raw_bytes = public_key.public_bytes(
|
|
313
|
+
encoding=serialization.Encoding.Raw,
|
|
314
|
+
format=serialization.PublicFormat.Raw
|
|
315
|
+
)
|
|
316
|
+
x_b64 = base64.urlsafe_b64encode(raw_bytes).decode('utf-8').rstrip('=')
|
|
317
|
+
|
|
318
|
+
jwk = {
|
|
319
|
+
"kty": "OKP",
|
|
320
|
+
"crv": "Ed25519",
|
|
321
|
+
"x": x_b64,
|
|
322
|
+
"kid": self.signing_kid,
|
|
323
|
+
"use": "sig"
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
card_data = {
|
|
327
|
+
"agent_id": self.agent_id,
|
|
328
|
+
"public_keys": [jwk],
|
|
329
|
+
"protocolVersion": "0.3.0",
|
|
330
|
+
"name": "Local Dev Agent",
|
|
331
|
+
"description": "Auto-generated by SimpleGuard",
|
|
332
|
+
"url": "http://localhost:8000",
|
|
333
|
+
"version": "0.1.0",
|
|
334
|
+
"provider": {
|
|
335
|
+
"organization": "Local Dev"
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
with open(self.agent_card_path, "w") as f:
|
|
340
|
+
json.dump(card_data, f, indent=2)
|
|
341
|
+
logger.info(f"Created agent-card.json at {self.agent_card_path}")
|
|
342
|
+
|
|
343
|
+
def _setup_trust_store(self) -> None:
|
|
344
|
+
"""Ensure trust store exists and add self-trust in dev_mode."""
|
|
345
|
+
if not self.trusted_dir.exists() and self.dev_mode:
|
|
346
|
+
self.trusted_dir.mkdir(parents=True, exist_ok=True)
|
|
347
|
+
|
|
348
|
+
if self.dev_mode:
|
|
349
|
+
# Self-Trust: Copy public.pem to trusted/{kid}.pem
|
|
350
|
+
self_trust_path = self.trusted_dir / f"{self.signing_kid}.pem"
|
|
351
|
+
if not self_trust_path.exists() and self.public_key_path.exists():
|
|
352
|
+
import shutil
|
|
353
|
+
shutil.copy(self.public_key_path, self_trust_path)
|
|
354
|
+
logger.info(f"Dev Mode: Added self-trust for kid {self.signing_kid}")
|
capiscio_sdk/types.py
CHANGED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: capiscio-sdk
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Runtime security middleware for A2A agents
|
|
5
|
+
Project-URL: Homepage, https://capisc.io
|
|
6
|
+
Project-URL: Documentation, https://docs.capisc.io/sdk-python
|
|
7
|
+
Project-URL: Repository, https://github.com/capiscio/capiscio-sdk-python
|
|
8
|
+
Project-URL: Issues, https://github.com/capiscio/capiscio-sdk-python/issues
|
|
9
|
+
Author-email: Capiscio Team <team@capisc.io>
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: a2a,agent,agent-to-agent,middleware,security,validation
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: a2a-sdk>=0.1.0
|
|
25
|
+
Requires-Dist: cachetools>=5.3.0
|
|
26
|
+
Requires-Dist: cryptography>=42.0.0
|
|
27
|
+
Requires-Dist: httpx>=0.27.0
|
|
28
|
+
Requires-Dist: pydantic>=2.0.0
|
|
29
|
+
Requires-Dist: pyjwt[crypto]>=2.8.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: black>=24.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: fastapi>=0.100.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: mypy>=1.9.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: starlette>=0.27.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: types-cachetools>=5.3.0; extra == 'dev'
|
|
40
|
+
Provides-Extra: web
|
|
41
|
+
Requires-Dist: fastapi>=0.100.0; extra == 'web'
|
|
42
|
+
Requires-Dist: starlette>=0.27.0; extra == 'web'
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
# CapiscIO SDK (Python)
|
|
46
|
+
|
|
47
|
+
**Enforcement-First Security for A2A Agents.**
|
|
48
|
+
|
|
49
|
+
[](https://badge.fury.io/py/capiscio-sdk)
|
|
50
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
51
|
+
[](https://www.python.org/downloads/)
|
|
52
|
+
|
|
53
|
+
**CapiscIO** is the "Customs Officer" for your AI Agent. It provides military-grade Identity and Integrity enforcement for the [Agent-to-Agent (A2A) Protocol](https://github.com/google/A2A) with **zero configuration**.
|
|
54
|
+
|
|
55
|
+
## 🚀 The 60-Second Upgrade
|
|
56
|
+
|
|
57
|
+
Turn any FastAPI application into a Verified A2A Agent in 3 lines of code.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from fastapi import FastAPI
|
|
61
|
+
from capiscio_sdk.simple_guard import SimpleGuard
|
|
62
|
+
from capiscio_sdk.integrations.fastapi import CapiscioMiddleware
|
|
63
|
+
|
|
64
|
+
# 1. Initialize Guard (Auto-generates keys in dev_mode)
|
|
65
|
+
guard = SimpleGuard(dev_mode=True)
|
|
66
|
+
|
|
67
|
+
app = FastAPI()
|
|
68
|
+
|
|
69
|
+
# 2. Add Enforcement Middleware
|
|
70
|
+
app.add_middleware(CapiscioMiddleware, guard=guard)
|
|
71
|
+
|
|
72
|
+
@app.post("/agent/task")
|
|
73
|
+
async def handle_task(request: Request):
|
|
74
|
+
# 🔒 Only reachable if Identity + Integrity are verified
|
|
75
|
+
caller = request.state.agent_id
|
|
76
|
+
return {"status": "accepted", "verified_caller": caller}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 🛡️ What You Get (Out of the Box)
|
|
80
|
+
|
|
81
|
+
1. **Zero-Config Identity**:
|
|
82
|
+
* Auto-generates **Ed25519** keys and `agent-card.json` on first run.
|
|
83
|
+
* No manual key management required for development.
|
|
84
|
+
|
|
85
|
+
2. **Payload Integrity**:
|
|
86
|
+
* Enforces **SHA-256 Body Hash (`bh`)** verification.
|
|
87
|
+
* Blocks tampered payloads instantly (returns `403 Forbidden`).
|
|
88
|
+
|
|
89
|
+
3. **Replay Protection**:
|
|
90
|
+
* Enforces strict **60-second** token expiration (`exp`).
|
|
91
|
+
* Prevents replay attacks and ensures freshness.
|
|
92
|
+
|
|
93
|
+
4. **Performance Telemetry**:
|
|
94
|
+
* Adds `<1ms` overhead.
|
|
95
|
+
* Includes `Server-Timing` headers for transparent monitoring.
|
|
96
|
+
|
|
97
|
+
## Installation
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pip install capiscio-sdk
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## How It Works
|
|
104
|
+
|
|
105
|
+
### 1. The Handshake
|
|
106
|
+
CapiscIO enforces the **A2A Trust Protocol**:
|
|
107
|
+
* **Sender**: Signs the request body (JWS + Body Hash).
|
|
108
|
+
* **Receiver**: Verifies the signature and re-hashes the body to ensure integrity.
|
|
109
|
+
|
|
110
|
+
### 2. The "Customs Officer"
|
|
111
|
+
The `SimpleGuard` acts as a local authority. It manages your agent's "Passport" (Agent Card) and verifies the "Visas" (Tokens) of incoming requests.
|
|
112
|
+
|
|
113
|
+
### 3. Telemetry
|
|
114
|
+
Every response includes a `Server-Timing` header showing exactly how fast the verification was:
|
|
115
|
+
```http
|
|
116
|
+
Server-Timing: capiscio-auth;dur=0.618;desc="CapiscIO Verification"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Documentation
|
|
120
|
+
|
|
121
|
+
- [Official Documentation](https://docs.capisc.io)
|
|
122
|
+
- [A2A Protocol Spec](https://github.com/google/A2A)
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
Apache License 2.0 - see [LICENSE](LICENSE) for details.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
capiscio_sdk/__init__.py,sha256=
|
|
2
|
-
capiscio_sdk/config.py,sha256
|
|
3
|
-
capiscio_sdk/errors.py,sha256=
|
|
4
|
-
capiscio_sdk/executor.py,sha256=
|
|
1
|
+
capiscio_sdk/__init__.py,sha256=d1tXC6Mh3yogY-KLPLSJPzAe5lx-GsnR1EPfAalBv00,1165
|
|
2
|
+
capiscio_sdk/config.py,sha256=-1KNubnRE06SENRXcnGvrj-y1EfKz2uAMdz7s0n6ark,3972
|
|
3
|
+
capiscio_sdk/errors.py,sha256=vGk7fhf07MM4-p7tvMXZlIz0yHQw9J03yIGGqRAEWDY,2107
|
|
4
|
+
capiscio_sdk/executor.py,sha256=jalft0M0PvyfN-5sxuPlD5hExC8EW_Af92pR2KwEtyc,7753
|
|
5
5
|
capiscio_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
capiscio_sdk/
|
|
6
|
+
capiscio_sdk/simple_guard.py,sha256=0s85UBDvNw8zG0y-gqZSibWEV_4LaykMATs-tIPCD5M,14516
|
|
7
|
+
capiscio_sdk/types.py,sha256=A_0SLhrbX7apkH5yYGTkG-_284xRjCK0N3lEseVRJRs,7978
|
|
7
8
|
capiscio_sdk/infrastructure/__init__.py,sha256=5yK7kbk1tcHqlnFIN0lR5N_4ZWWquNivLfHkXOAtso0,152
|
|
8
9
|
capiscio_sdk/infrastructure/cache.py,sha256=FjIRRzNbsPxFZrxdeek51IwFSmR682Wpj1bvvmgqwiU,1841
|
|
9
10
|
capiscio_sdk/infrastructure/rate_limiter.py,sha256=7Q_GfcooWKjioDeB46PpPb00laDmkLpUYGM0Ui8nXqI,3642
|
|
11
|
+
capiscio_sdk/integrations/fastapi.py,sha256=op8Bwib0IoebIOihUzDEimY1Z_wmX2b3I374Io5bVcU,2896
|
|
10
12
|
capiscio_sdk/scoring/__init__.py,sha256=OjHeqLe6aZ7yjbpmtt_upqXDwYUnX5CDXTVNngOMHcg,1083
|
|
11
13
|
capiscio_sdk/scoring/availability.py,sha256=CzXA1ED48U1Cc06sh0Mtl_kxZP6af-9cceBumTXQhO8,9130
|
|
12
14
|
capiscio_sdk/scoring/compliance.py,sha256=JZyYuT18A_eiDNdOz-doTIYwW6YhVPvfRj_siNAkkTY,9780
|
|
@@ -20,7 +22,7 @@ capiscio_sdk/validators/protocol.py,sha256=bkaJJXseulTJ4Sdiio8gE8Q_Pyqj0BRsJe6BG
|
|
|
20
22
|
capiscio_sdk/validators/semver.py,sha256=mlF3GO5ZPA-w6FzSxhjcr56sgCdS0YVVAd1dUr1bxWs,6385
|
|
21
23
|
capiscio_sdk/validators/signature.py,sha256=lI8XzaKfG_dXSOQXZ40Lda0ntga9EqqC4zAId2kOt6g,8072
|
|
22
24
|
capiscio_sdk/validators/url_security.py,sha256=SdpOrB48hrfgAMuLvpWH2P0LLCJtg6QBohGDIye8f1E,9802
|
|
23
|
-
capiscio_sdk-0.
|
|
24
|
-
capiscio_sdk-0.
|
|
25
|
-
capiscio_sdk-0.
|
|
26
|
-
capiscio_sdk-0.
|
|
25
|
+
capiscio_sdk-0.3.0.dist-info/METADATA,sha256=MMJh_eCqXM-DyX1bkj3CDJG3UqxBE-Ie81peUbEnV-0,4641
|
|
26
|
+
capiscio_sdk-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
27
|
+
capiscio_sdk-0.3.0.dist-info/licenses/LICENSE,sha256=AMM_E-ILcCpX0JALqX3BL9yfgSx654BtkhX-CBFYp1Q,10758
|
|
28
|
+
capiscio_sdk-0.3.0.dist-info/RECORD,,
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: capiscio-sdk
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Runtime security middleware for A2A agents
|
|
5
|
-
Project-URL: Homepage, https://capisc.io
|
|
6
|
-
Project-URL: Documentation, https://docs.capisc.io/sdk-python
|
|
7
|
-
Project-URL: Repository, https://github.com/capiscio/capiscio-sdk-python
|
|
8
|
-
Project-URL: Issues, https://github.com/capiscio/capiscio-sdk-python/issues
|
|
9
|
-
Author-email: Capiscio Team <team@capisc.io>
|
|
10
|
-
License: Apache-2.0
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Keywords: a2a,agent,agent-to-agent,middleware,security,validation
|
|
13
|
-
Classifier: Development Status :: 3 - Alpha
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
-
Classifier: Topic :: Security
|
|
22
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
-
Requires-Python: >=3.10
|
|
24
|
-
Requires-Dist: a2a-sdk>=0.1.0
|
|
25
|
-
Requires-Dist: cachetools>=5.3.0
|
|
26
|
-
Requires-Dist: cryptography>=42.0.0
|
|
27
|
-
Requires-Dist: httpx>=0.27.0
|
|
28
|
-
Requires-Dist: pydantic>=2.0.0
|
|
29
|
-
Requires-Dist: pyjwt[crypto]>=2.8.0
|
|
30
|
-
Provides-Extra: dev
|
|
31
|
-
Requires-Dist: black>=24.0.0; extra == 'dev'
|
|
32
|
-
Requires-Dist: mypy>=1.9.0; extra == 'dev'
|
|
33
|
-
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
34
|
-
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
35
|
-
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
36
|
-
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
37
|
-
Requires-Dist: types-cachetools>=5.3.0; extra == 'dev'
|
|
38
|
-
Description-Content-Type: text/markdown
|
|
39
|
-
|
|
40
|
-
# CapiscIO SDK (Python)
|
|
41
|
-
|
|
42
|
-
**Runtime security middleware for A2A (Agent-to-Agent) protocol agents**
|
|
43
|
-
|
|
44
|
-
[](https://badge.fury.io/py/capiscio-sdk)
|
|
45
|
-
[](https://opensource.org/licenses/Apache-2.0)
|
|
46
|
-
[](https://www.python.org/downloads/)
|
|
47
|
-
|
|
48
|
-
## What is CapiscIO SDK?
|
|
49
|
-
|
|
50
|
-
CapiscIO SDK provides **always-on runtime protection** for agents using the [A2A (Agent-to-Agent) protocol](https://github.com/google/A2A). It wraps your agent executor to validate incoming requests, verify signatures, and protect against malicious actors—all without requiring peer cooperation.
|
|
51
|
-
|
|
52
|
-
### Key Features
|
|
53
|
-
|
|
54
|
-
- ✅ **Message validation** - Schema and protocol compliance checking
|
|
55
|
-
- ✅ **Signature verification** - JWS/JWKS cryptographic validation (RFC 7515)
|
|
56
|
-
- ✅ **Upstream protection** - Validate agents you call
|
|
57
|
-
- ✅ **Downstream protection** - Validate agents calling you
|
|
58
|
-
- ✅ **Rate limiting** - Token bucket algorithm
|
|
59
|
-
- ✅ **Caching** - Performance-optimized validation results
|
|
60
|
-
- ✅ **Three integration patterns** - Minimal, explicit, or decorator
|
|
61
|
-
|
|
62
|
-
## Installation
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
pip install capiscio-sdk
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Quick Start
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
### Pattern 1: Minimal (One-liner with Preset)
|
|
72
|
-
|
|
73
|
-
```python
|
|
74
|
-
from capiscio_sdk import secure, SecurityConfig
|
|
75
|
-
from a2a.server.request_handlers import DefaultRequestHandler
|
|
76
|
-
from a2a.server.tasks import InMemoryTaskStore
|
|
77
|
-
|
|
78
|
-
# Wrap your agent with security (production defaults)
|
|
79
|
-
agent = secure(MyAgentExecutor(), SecurityConfig.production())
|
|
80
|
-
|
|
81
|
-
# Use in A2A request handler
|
|
82
|
-
handler = DefaultRequestHandler(
|
|
83
|
-
agent_executor=agent,
|
|
84
|
-
task_store=InMemoryTaskStore()
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
# Access validation results (three-dimensional scoring)
|
|
88
|
-
result = await agent.validate_agent_card(card_url)
|
|
89
|
-
print(result.compliance.total, result.trust.total, result.availability.total)
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Pattern 2: Granular Control
|
|
93
|
-
|
|
94
|
-
```python
|
|
95
|
-
from capiscio_sdk import CapiscIOSecurityExecutor, SecurityConfig
|
|
96
|
-
|
|
97
|
-
# Start with a preset, customize what matters to you
|
|
98
|
-
config = SecurityConfig.production()
|
|
99
|
-
config.downstream.rate_limit_requests_per_minute = 100 # Higher rate limit
|
|
100
|
-
config.downstream.require_signatures = True # Enforce signatures
|
|
101
|
-
config.upstream.test_endpoints = True # Test before calling
|
|
102
|
-
config.fail_mode = "monitor" # Log but don't block yet
|
|
103
|
-
|
|
104
|
-
secure_agent = CapiscIOSecurityExecutor(
|
|
105
|
-
delegate=MyAgentExecutor(),
|
|
106
|
-
config=config
|
|
107
|
-
)
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Pattern 3: Environment-Driven (12-Factor App)
|
|
111
|
-
|
|
112
|
-
```python
|
|
113
|
-
from capiscio_sdk import secure_agent, SecurityConfig
|
|
114
|
-
from a2a import AgentExecutor, RequestContext, EventQueue
|
|
115
|
-
|
|
116
|
-
@secure_agent(config=SecurityConfig.from_env())
|
|
117
|
-
class MyAgentExecutor(AgentExecutor):
|
|
118
|
-
async def execute(self, context: RequestContext, event_queue: EventQueue):
|
|
119
|
-
# Your agent logic - config loaded from env vars
|
|
120
|
-
pass
|
|
121
|
-
|
|
122
|
-
# Already secured - use directly!
|
|
123
|
-
handler = DefaultRequestHandler(agent_executor=MyAgentExecutor())
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
**All 16 configuration options documented in the [Configuration Guide](https://docs.capisc.io/sdk-python/guides/configuration/).**
|
|
127
|
-
|
|
128
|
-
## Why CapiscIO?
|
|
129
|
-
|
|
130
|
-
### The Problem
|
|
131
|
-
|
|
132
|
-
When building A2A agents, you face security risks from:
|
|
133
|
-
- **Malicious downstream agents** sending invalid/malicious requests
|
|
134
|
-
- **Broken upstream dependencies** with invalid agent cards
|
|
135
|
-
- **Protocol violations** causing runtime failures
|
|
136
|
-
- **Missing signatures** with no authenticity verification
|
|
137
|
-
|
|
138
|
-
### The Solution
|
|
139
|
-
|
|
140
|
-
CapiscIO wraps your agent executor and provides:
|
|
141
|
-
|
|
142
|
-
1. **Downstream Protection** - Validates all incoming requests
|
|
143
|
-
2. **Upstream Protection** - Validates agents you call
|
|
144
|
-
3. **Always-On** - Works without peer cooperation
|
|
145
|
-
4. **Performance** - Caching and parallel validation
|
|
146
|
-
5. **Three-Dimensional Scoring** - Compliance, trust, and availability insights
|
|
147
|
-
|
|
148
|
-
## Configuration
|
|
149
|
-
|
|
150
|
-
### Presets
|
|
151
|
-
|
|
152
|
-
```python
|
|
153
|
-
# Development - Permissive, verbose logging
|
|
154
|
-
SecurityConfig.development()
|
|
155
|
-
|
|
156
|
-
# Production - Balanced (default)
|
|
157
|
-
SecurityConfig.production()
|
|
158
|
-
|
|
159
|
-
# Strict - Maximum security
|
|
160
|
-
SecurityConfig.strict()
|
|
161
|
-
|
|
162
|
-
# From environment variables
|
|
163
|
-
SecurityConfig.from_env()
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Custom Configuration
|
|
167
|
-
|
|
168
|
-
```python
|
|
169
|
-
from capiscio_sdk import SecurityConfig, DownstreamConfig, UpstreamConfig
|
|
170
|
-
|
|
171
|
-
config = SecurityConfig(
|
|
172
|
-
downstream=DownstreamConfig(
|
|
173
|
-
validate_schema=True,
|
|
174
|
-
verify_signatures=True,
|
|
175
|
-
require_signatures=False,
|
|
176
|
-
enable_rate_limiting=True,
|
|
177
|
-
rate_limit_requests_per_minute=100
|
|
178
|
-
),
|
|
179
|
-
upstream=UpstreamConfig(
|
|
180
|
-
validate_agent_cards=True,
|
|
181
|
-
verify_signatures=True,
|
|
182
|
-
cache_validation=True,
|
|
183
|
-
cache_timeout=3600 # seconds
|
|
184
|
-
),
|
|
185
|
-
fail_mode="block", # "block" | "monitor" | "log"
|
|
186
|
-
timeout_ms=5000
|
|
187
|
-
)
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
## Documentation
|
|
191
|
-
|
|
192
|
-
- [Quickstart Guide](docs/quickstart.md)
|
|
193
|
-
- [Configuration Reference](docs/configuration.md)
|
|
194
|
-
- [API Documentation](docs/api-reference.md)
|
|
195
|
-
- [Examples](examples/)
|
|
196
|
-
|
|
197
|
-
## Roadmap
|
|
198
|
-
|
|
199
|
-
- **V1.0** (Q4 2025) - Core middleware (this package)
|
|
200
|
-
- **V2.0** (Q2 2026) - Extension protocol (validation feedback)
|
|
201
|
-
- **V3.0** (Q3 2026) - Platform integration (trust network)
|
|
202
|
-
- **V4.0** (Q4 2026) - Enterprise features (policies, audit logs)
|
|
203
|
-
|
|
204
|
-
## Contributing
|
|
205
|
-
|
|
206
|
-
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
207
|
-
|
|
208
|
-
## License
|
|
209
|
-
|
|
210
|
-
Apache License 2.0 - see [LICENSE](LICENSE) for details.
|
|
211
|
-
|
|
212
|
-
## About A2A
|
|
213
|
-
|
|
214
|
-
The [Agent-to-Agent (A2A) protocol](https://github.com/google/A2A) is an open standard for agent interoperability, supported by Google and 50+ partners including Salesforce, ServiceNow, SAP, Intuit, and more. CapiscIO provides the security layer for production A2A deployments.
|
|
215
|
-
|
|
216
|
-
## Support
|
|
217
|
-
|
|
218
|
-
- **Issues:** [GitHub Issues](https://github.com/capiscio/capiscio-sdk-python/issues)
|
|
219
|
-
- **Discussions:** [GitHub Discussions](https://github.com/capiscio/capiscio-sdk-python/discussions)
|
|
220
|
-
- **Documentation:** [docs.capisc.io](https://docs.capisc.io)
|
|
221
|
-
- **Website:** [capisc.io](https://capisc.io)
|
|
File without changes
|
|
File without changes
|