swiftapi-python 1.0.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.
- swiftapi/__init__.py +91 -0
- swiftapi/client.py +282 -0
- swiftapi/enforcement.py +280 -0
- swiftapi/exceptions.py +61 -0
- swiftapi/utils.py +89 -0
- swiftapi/verifier.py +122 -0
- swiftapi_python-1.0.0.dist-info/METADATA +256 -0
- swiftapi_python-1.0.0.dist-info/RECORD +10 -0
- swiftapi_python-1.0.0.dist-info/WHEEL +5 -0
- swiftapi_python-1.0.0.dist-info/top_level.txt +1 -0
swiftapi/__init__.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SwiftAPI Python SDK
|
|
3
|
+
|
|
4
|
+
No AI action executes without verification.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from swiftapi import SwiftAPI, Enforcement
|
|
8
|
+
|
|
9
|
+
# Initialize client
|
|
10
|
+
api = SwiftAPI(key="swiftapi_live_...")
|
|
11
|
+
|
|
12
|
+
# Create enforcement point
|
|
13
|
+
guard = Enforcement(api)
|
|
14
|
+
|
|
15
|
+
# Protect dangerous operations
|
|
16
|
+
guard.run(
|
|
17
|
+
lambda: os.system("rm -rf /tmp/data"),
|
|
18
|
+
action="file_delete",
|
|
19
|
+
intent="Cleanup temporary data"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Or use decorator
|
|
23
|
+
@guard.protect(action="api_call", intent="Send email")
|
|
24
|
+
def send_email(to, subject, body):
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
# Or use context manager
|
|
28
|
+
with guard.guard(action="database_write", intent="Update user"):
|
|
29
|
+
db.update(user_id, data)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
__version__ = "1.0.0"
|
|
33
|
+
__author__ = "Rayan Pal"
|
|
34
|
+
|
|
35
|
+
# Core exports
|
|
36
|
+
from .client import SwiftAPI
|
|
37
|
+
from .enforcement import Enforcement, enforce
|
|
38
|
+
from .verifier import verify_signature, is_valid, get_public_key
|
|
39
|
+
|
|
40
|
+
# Exceptions
|
|
41
|
+
from .exceptions import (
|
|
42
|
+
SwiftAPIError,
|
|
43
|
+
AuthenticationError,
|
|
44
|
+
PolicyViolation,
|
|
45
|
+
SignatureVerificationError,
|
|
46
|
+
AttestationExpiredError,
|
|
47
|
+
AttestationRevokedError,
|
|
48
|
+
RateLimitError,
|
|
49
|
+
NetworkError,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# UX utilities
|
|
53
|
+
from .utils import (
|
|
54
|
+
Colors,
|
|
55
|
+
Symbols,
|
|
56
|
+
print_approved,
|
|
57
|
+
print_denied,
|
|
58
|
+
print_verified,
|
|
59
|
+
print_error,
|
|
60
|
+
print_info,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
# Version
|
|
65
|
+
"__version__",
|
|
66
|
+
# Core
|
|
67
|
+
"SwiftAPI",
|
|
68
|
+
"Enforcement",
|
|
69
|
+
"enforce",
|
|
70
|
+
# Verification
|
|
71
|
+
"verify_signature",
|
|
72
|
+
"is_valid",
|
|
73
|
+
"get_public_key",
|
|
74
|
+
# Exceptions
|
|
75
|
+
"SwiftAPIError",
|
|
76
|
+
"AuthenticationError",
|
|
77
|
+
"PolicyViolation",
|
|
78
|
+
"SignatureVerificationError",
|
|
79
|
+
"AttestationExpiredError",
|
|
80
|
+
"AttestationRevokedError",
|
|
81
|
+
"RateLimitError",
|
|
82
|
+
"NetworkError",
|
|
83
|
+
# UX
|
|
84
|
+
"Colors",
|
|
85
|
+
"Symbols",
|
|
86
|
+
"print_approved",
|
|
87
|
+
"print_denied",
|
|
88
|
+
"print_verified",
|
|
89
|
+
"print_error",
|
|
90
|
+
"print_info",
|
|
91
|
+
]
|
swiftapi/client.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SwiftAPI SDK - HTTP Client
|
|
3
|
+
|
|
4
|
+
This module provides the HTTP client for communicating with SwiftAPI.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from typing import Dict, Any, Optional, List
|
|
9
|
+
|
|
10
|
+
from .exceptions import (
|
|
11
|
+
SwiftAPIError,
|
|
12
|
+
AuthenticationError,
|
|
13
|
+
PolicyViolation,
|
|
14
|
+
RateLimitError,
|
|
15
|
+
NetworkError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
DEFAULT_BASE_URL = "https://swiftapi.ai"
|
|
19
|
+
DEFAULT_TIMEOUT = 30
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SwiftAPI:
|
|
23
|
+
"""
|
|
24
|
+
SwiftAPI HTTP Client.
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
api = SwiftAPI(key="swiftapi_live_...")
|
|
28
|
+
result = api.verify("file_write", "Save config", {"path": "/etc/config"})
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
key: str,
|
|
34
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
35
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Initialize SwiftAPI client.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
key: SwiftAPI authority key (swiftapi_live_...)
|
|
42
|
+
base_url: API base URL (default: https://swiftapi.ai)
|
|
43
|
+
timeout: Request timeout in seconds
|
|
44
|
+
"""
|
|
45
|
+
if not key:
|
|
46
|
+
raise AuthenticationError("API key is required")
|
|
47
|
+
if not key.startswith("swiftapi_live_"):
|
|
48
|
+
raise AuthenticationError("Invalid key format: must start with 'swiftapi_live_'")
|
|
49
|
+
|
|
50
|
+
self.key = key
|
|
51
|
+
self.base_url = base_url.rstrip("/")
|
|
52
|
+
self.timeout = timeout
|
|
53
|
+
self._session = requests.Session()
|
|
54
|
+
self._session.headers.update({
|
|
55
|
+
"X-SwiftAPI-Authority": key,
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
"User-Agent": "swiftapi-python/1.0.0",
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
def _request(
|
|
61
|
+
self,
|
|
62
|
+
method: str,
|
|
63
|
+
endpoint: str,
|
|
64
|
+
json: Dict = None,
|
|
65
|
+
params: Dict = None,
|
|
66
|
+
) -> Dict[str, Any]:
|
|
67
|
+
"""Make HTTP request to SwiftAPI."""
|
|
68
|
+
url = f"{self.base_url}{endpoint}"
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
response = self._session.request(
|
|
72
|
+
method=method,
|
|
73
|
+
url=url,
|
|
74
|
+
json=json,
|
|
75
|
+
params=params,
|
|
76
|
+
timeout=self.timeout,
|
|
77
|
+
)
|
|
78
|
+
except requests.exceptions.ConnectionError as e:
|
|
79
|
+
raise NetworkError(f"Failed to connect to SwiftAPI: {e}")
|
|
80
|
+
except requests.exceptions.Timeout:
|
|
81
|
+
raise NetworkError(f"Request timed out after {self.timeout}s")
|
|
82
|
+
except requests.exceptions.RequestException as e:
|
|
83
|
+
raise NetworkError(f"Request failed: {e}")
|
|
84
|
+
|
|
85
|
+
# Handle response
|
|
86
|
+
return self._handle_response(response)
|
|
87
|
+
|
|
88
|
+
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
|
89
|
+
"""Handle API response and raise appropriate exceptions."""
|
|
90
|
+
# Rate limit
|
|
91
|
+
if response.status_code == 429:
|
|
92
|
+
retry_after = response.headers.get("Retry-After")
|
|
93
|
+
raise RateLimitError(int(retry_after) if retry_after else None)
|
|
94
|
+
|
|
95
|
+
# Auth errors
|
|
96
|
+
if response.status_code == 401:
|
|
97
|
+
raise AuthenticationError("Invalid API key")
|
|
98
|
+
if response.status_code == 403:
|
|
99
|
+
try:
|
|
100
|
+
data = response.json()
|
|
101
|
+
raise AuthenticationError(data.get("detail", {}).get("message", "Forbidden"))
|
|
102
|
+
except ValueError:
|
|
103
|
+
raise AuthenticationError("Forbidden")
|
|
104
|
+
|
|
105
|
+
# Parse JSON
|
|
106
|
+
try:
|
|
107
|
+
data = response.json()
|
|
108
|
+
except ValueError:
|
|
109
|
+
raise SwiftAPIError(f"Invalid JSON response: {response.text[:200]}")
|
|
110
|
+
|
|
111
|
+
# Policy violations (denied actions)
|
|
112
|
+
if response.status_code == 200 and data.get("approved") is False:
|
|
113
|
+
raise PolicyViolation(
|
|
114
|
+
message=data.get("reason", "Action denied by policy"),
|
|
115
|
+
action_type=data.get("action", {}).get("type"),
|
|
116
|
+
denial_reason=data.get("reason"),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Other errors
|
|
120
|
+
if response.status_code >= 400:
|
|
121
|
+
detail = data.get("detail", {})
|
|
122
|
+
if isinstance(detail, dict):
|
|
123
|
+
message = detail.get("message", str(detail))
|
|
124
|
+
else:
|
|
125
|
+
message = str(detail)
|
|
126
|
+
raise SwiftAPIError(message, response.status_code, data)
|
|
127
|
+
|
|
128
|
+
return data
|
|
129
|
+
|
|
130
|
+
# =========================================================================
|
|
131
|
+
# Core Endpoints
|
|
132
|
+
# =========================================================================
|
|
133
|
+
|
|
134
|
+
def verify(
|
|
135
|
+
self,
|
|
136
|
+
action_type: str,
|
|
137
|
+
intent: str,
|
|
138
|
+
params: Optional[Dict[str, Any]] = None,
|
|
139
|
+
actor: str = "sdk",
|
|
140
|
+
app_id: str = "swiftapi-python",
|
|
141
|
+
) -> Dict[str, Any]:
|
|
142
|
+
"""
|
|
143
|
+
Submit an action for verification and get an execution attestation.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
action_type: Type of action (e.g., "file_write", "api_call")
|
|
147
|
+
intent: Human-readable description of intent
|
|
148
|
+
params: Optional action parameters
|
|
149
|
+
actor: Identifier for the requesting agent
|
|
150
|
+
app_id: Application identifier
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Full verification response including execution_attestation
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
PolicyViolation: If action is denied
|
|
157
|
+
AuthenticationError: If key is invalid
|
|
158
|
+
SwiftAPIError: For other errors
|
|
159
|
+
"""
|
|
160
|
+
payload = {
|
|
161
|
+
"action": {
|
|
162
|
+
"type": action_type,
|
|
163
|
+
"intent": intent,
|
|
164
|
+
"params": params or {},
|
|
165
|
+
},
|
|
166
|
+
"actor": actor,
|
|
167
|
+
"app_id": app_id,
|
|
168
|
+
}
|
|
169
|
+
return self._request("POST", "/verify", json=payload)
|
|
170
|
+
|
|
171
|
+
def verify_attestation(self, attestation: Dict[str, Any]) -> Dict[str, Any]:
|
|
172
|
+
"""
|
|
173
|
+
Verify an existing attestation with the server.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
attestation: The execution_attestation to verify
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Verification result
|
|
180
|
+
"""
|
|
181
|
+
return self._request("POST", "/attestation/verify", json=attestation)
|
|
182
|
+
|
|
183
|
+
def revoke(self, jti: str, reason: str = "SDK revocation") -> Dict[str, Any]:
|
|
184
|
+
"""
|
|
185
|
+
Revoke an attestation by JTI.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
jti: The attestation's unique identifier
|
|
189
|
+
reason: Reason for revocation
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Revocation confirmation
|
|
193
|
+
"""
|
|
194
|
+
payload = {"jti": jti, "reason": reason}
|
|
195
|
+
return self._request("POST", "/attestation/revoke", json=payload)
|
|
196
|
+
|
|
197
|
+
def check_revocation(self, jti: str) -> bool:
|
|
198
|
+
"""
|
|
199
|
+
Check if an attestation has been revoked.
|
|
200
|
+
|
|
201
|
+
This is a fast check - use for real-time revocation verification.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
jti: The attestation's unique identifier
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
True if revoked, False if valid
|
|
208
|
+
"""
|
|
209
|
+
try:
|
|
210
|
+
result = self._request("GET", f"/attestation/revoked/{jti}")
|
|
211
|
+
return result.get("revoked", False)
|
|
212
|
+
except SwiftAPIError:
|
|
213
|
+
# If endpoint fails, assume not revoked (fail-open for availability)
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
# =========================================================================
|
|
217
|
+
# Info Endpoints
|
|
218
|
+
# =========================================================================
|
|
219
|
+
|
|
220
|
+
def get_info(self) -> Dict[str, Any]:
|
|
221
|
+
"""Get API info and public key."""
|
|
222
|
+
return self._request("GET", "/")
|
|
223
|
+
|
|
224
|
+
def health(self) -> bool:
|
|
225
|
+
"""Check API health."""
|
|
226
|
+
try:
|
|
227
|
+
result = self._request("GET", "/health")
|
|
228
|
+
return result.get("status") == "healthy"
|
|
229
|
+
except Exception:
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
def get_policies(self) -> List[Dict[str, Any]]:
|
|
233
|
+
"""Get active policies."""
|
|
234
|
+
result = self._request("GET", "/policies")
|
|
235
|
+
return result.get("policies", [])
|
|
236
|
+
|
|
237
|
+
def get_scopes(self) -> List[str]:
|
|
238
|
+
"""Get available authority scopes."""
|
|
239
|
+
result = self._request("GET", "/authority/scopes")
|
|
240
|
+
return result.get("scopes", [])
|
|
241
|
+
|
|
242
|
+
# =========================================================================
|
|
243
|
+
# Key Management (Admin Only)
|
|
244
|
+
# =========================================================================
|
|
245
|
+
|
|
246
|
+
def list_keys(self) -> List[Dict[str, Any]]:
|
|
247
|
+
"""List authority keys (requires admin scope)."""
|
|
248
|
+
result = self._request("GET", "/authority/keys")
|
|
249
|
+
return result.get("keys", [])
|
|
250
|
+
|
|
251
|
+
def create_key(
|
|
252
|
+
self,
|
|
253
|
+
name: str,
|
|
254
|
+
scopes: List[str],
|
|
255
|
+
) -> Dict[str, Any]:
|
|
256
|
+
"""
|
|
257
|
+
Create a new authority key (requires admin scope).
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
name: Key name
|
|
261
|
+
scopes: List of scopes to grant
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
New key details (including the key itself - shown only once!)
|
|
265
|
+
"""
|
|
266
|
+
payload = {"name": name, "scopes": scopes}
|
|
267
|
+
return self._request("POST", "/authority/keys", json=payload)
|
|
268
|
+
|
|
269
|
+
def revoke_key(self, key_hash: str) -> Dict[str, Any]:
|
|
270
|
+
"""Revoke an authority key by hash."""
|
|
271
|
+
return self._request("DELETE", f"/authority/keys/{key_hash}")
|
|
272
|
+
|
|
273
|
+
# =========================================================================
|
|
274
|
+
# Context Manager
|
|
275
|
+
# =========================================================================
|
|
276
|
+
|
|
277
|
+
def __enter__(self):
|
|
278
|
+
return self
|
|
279
|
+
|
|
280
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
281
|
+
self._session.close()
|
|
282
|
+
return False
|
swiftapi/enforcement.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SwiftAPI SDK - Enforcement Point (The Golden Loop)
|
|
3
|
+
|
|
4
|
+
This module provides the high-level enforcement mechanism that ensures
|
|
5
|
+
no action executes without proper verification and attestation.
|
|
6
|
+
|
|
7
|
+
The Golden Loop:
|
|
8
|
+
1. API Call: client.verify() -> Get Attestation
|
|
9
|
+
2. Crypto Check: verifier.verify_signature() -> OFFLINE TRUTH
|
|
10
|
+
3. Online Check: client.check_revocation() -> ONLINE TRUTH (optional)
|
|
11
|
+
4. Execute: Run the protected function
|
|
12
|
+
|
|
13
|
+
If any step fails, the action is BLOCKED.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Callable, Any, Dict, Optional
|
|
17
|
+
from functools import wraps
|
|
18
|
+
|
|
19
|
+
from .client import SwiftAPI
|
|
20
|
+
from .verifier import verify_signature, is_valid
|
|
21
|
+
from .exceptions import (
|
|
22
|
+
PolicyViolation,
|
|
23
|
+
SignatureVerificationError,
|
|
24
|
+
AttestationRevokedError,
|
|
25
|
+
AttestationExpiredError,
|
|
26
|
+
SwiftAPIError,
|
|
27
|
+
)
|
|
28
|
+
from .utils import print_approved, print_denied, print_verified, print_error
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Enforcement:
|
|
32
|
+
"""
|
|
33
|
+
SwiftAPI Enforcement Point.
|
|
34
|
+
|
|
35
|
+
This is the "ignition key" - no action executes without verification.
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
api = SwiftAPI(key="swiftapi_live_...")
|
|
39
|
+
guard = Enforcement(api)
|
|
40
|
+
|
|
41
|
+
# Option 1: Run with enforcement
|
|
42
|
+
guard.run(
|
|
43
|
+
lambda: dangerous_operation(),
|
|
44
|
+
action="file_delete",
|
|
45
|
+
intent="Remove temp files"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Option 2: Decorator
|
|
49
|
+
@guard.protect(action="api_call", intent="Send notification")
|
|
50
|
+
def send_notification(user_id, message):
|
|
51
|
+
...
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
client: SwiftAPI,
|
|
57
|
+
paranoid: bool = False,
|
|
58
|
+
verbose: bool = True,
|
|
59
|
+
):
|
|
60
|
+
"""
|
|
61
|
+
Initialize enforcement point.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
client: SwiftAPI client instance
|
|
65
|
+
paranoid: If True, always check revocation online (slower but safer)
|
|
66
|
+
verbose: If True, print status messages
|
|
67
|
+
"""
|
|
68
|
+
self.client = client
|
|
69
|
+
self.paranoid = paranoid
|
|
70
|
+
self.verbose = verbose
|
|
71
|
+
|
|
72
|
+
def run(
|
|
73
|
+
self,
|
|
74
|
+
func: Callable[[], Any],
|
|
75
|
+
action: str,
|
|
76
|
+
intent: str,
|
|
77
|
+
params: Optional[Dict[str, Any]] = None,
|
|
78
|
+
actor: str = "sdk",
|
|
79
|
+
) -> Any:
|
|
80
|
+
"""
|
|
81
|
+
Execute a function with SwiftAPI enforcement.
|
|
82
|
+
|
|
83
|
+
The function will ONLY execute if:
|
|
84
|
+
1. SwiftAPI approves the action
|
|
85
|
+
2. The attestation signature is valid (cryptographic proof)
|
|
86
|
+
3. The attestation is not revoked (if paranoid mode)
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
func: The function to execute (takes no arguments)
|
|
90
|
+
action: Action type for verification
|
|
91
|
+
intent: Human-readable intent description
|
|
92
|
+
params: Optional action parameters
|
|
93
|
+
actor: Actor identifier
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
The return value of func()
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
PolicyViolation: If action is denied by policy
|
|
100
|
+
SignatureVerificationError: If attestation signature is invalid
|
|
101
|
+
AttestationRevokedError: If attestation was revoked
|
|
102
|
+
SwiftAPIError: For other API errors
|
|
103
|
+
"""
|
|
104
|
+
# Step 1: API Call - Get attestation
|
|
105
|
+
try:
|
|
106
|
+
result = self.client.verify(
|
|
107
|
+
action_type=action,
|
|
108
|
+
intent=intent,
|
|
109
|
+
params=params,
|
|
110
|
+
actor=actor,
|
|
111
|
+
)
|
|
112
|
+
except PolicyViolation as e:
|
|
113
|
+
if self.verbose:
|
|
114
|
+
print_denied(action, intent, e.denial_reason)
|
|
115
|
+
raise
|
|
116
|
+
|
|
117
|
+
# Check if approved
|
|
118
|
+
if not result.get("approved"):
|
|
119
|
+
reason = result.get("reason", "Unknown denial reason")
|
|
120
|
+
if self.verbose:
|
|
121
|
+
print_denied(action, intent, reason)
|
|
122
|
+
raise PolicyViolation(
|
|
123
|
+
message=f"Action denied: {reason}",
|
|
124
|
+
action_type=action,
|
|
125
|
+
denial_reason=reason,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
attestation = result.get("execution_attestation")
|
|
129
|
+
if not attestation:
|
|
130
|
+
raise SwiftAPIError("No attestation in response")
|
|
131
|
+
|
|
132
|
+
jti = attestation.get("jti")
|
|
133
|
+
|
|
134
|
+
# Step 2: Crypto Check - OFFLINE TRUTH
|
|
135
|
+
try:
|
|
136
|
+
verify_signature(attestation)
|
|
137
|
+
if self.verbose:
|
|
138
|
+
print_verified(jti)
|
|
139
|
+
except (SignatureVerificationError, AttestationExpiredError) as e:
|
|
140
|
+
if self.verbose:
|
|
141
|
+
print_error(str(e))
|
|
142
|
+
raise
|
|
143
|
+
|
|
144
|
+
# Step 3: Online Check - ONLINE TRUTH (optional but recommended)
|
|
145
|
+
if self.paranoid:
|
|
146
|
+
if self.client.check_revocation(jti):
|
|
147
|
+
if self.verbose:
|
|
148
|
+
print_error(f"Attestation {jti} has been revoked")
|
|
149
|
+
raise AttestationRevokedError(jti)
|
|
150
|
+
|
|
151
|
+
# Step 4: Execute - THE ACTION RUNS
|
|
152
|
+
if self.verbose:
|
|
153
|
+
print_approved(action, intent)
|
|
154
|
+
|
|
155
|
+
return func()
|
|
156
|
+
|
|
157
|
+
def protect(
|
|
158
|
+
self,
|
|
159
|
+
action: str,
|
|
160
|
+
intent: str,
|
|
161
|
+
params: Optional[Dict[str, Any]] = None,
|
|
162
|
+
):
|
|
163
|
+
"""
|
|
164
|
+
Decorator to protect a function with SwiftAPI enforcement.
|
|
165
|
+
|
|
166
|
+
Usage:
|
|
167
|
+
@guard.protect(action="file_write", intent="Save config")
|
|
168
|
+
def save_config(data):
|
|
169
|
+
with open("/etc/config", "w") as f:
|
|
170
|
+
f.write(data)
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
action: Action type for verification
|
|
174
|
+
intent: Human-readable intent description
|
|
175
|
+
params: Optional action parameters
|
|
176
|
+
"""
|
|
177
|
+
def decorator(func: Callable) -> Callable:
|
|
178
|
+
@wraps(func)
|
|
179
|
+
def wrapper(*args, **kwargs):
|
|
180
|
+
return self.run(
|
|
181
|
+
func=lambda: func(*args, **kwargs),
|
|
182
|
+
action=action,
|
|
183
|
+
intent=intent,
|
|
184
|
+
params=params,
|
|
185
|
+
actor=func.__name__,
|
|
186
|
+
)
|
|
187
|
+
return wrapper
|
|
188
|
+
return decorator
|
|
189
|
+
|
|
190
|
+
def guard(
|
|
191
|
+
self,
|
|
192
|
+
action: str,
|
|
193
|
+
intent: str,
|
|
194
|
+
params: Optional[Dict[str, Any]] = None,
|
|
195
|
+
) -> "AttestationGuard":
|
|
196
|
+
"""
|
|
197
|
+
Context manager for guarded execution blocks.
|
|
198
|
+
|
|
199
|
+
Usage:
|
|
200
|
+
with guard.guard(action="file_delete", intent="Cleanup temp"):
|
|
201
|
+
os.remove("/tmp/file.txt")
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
action: Action type for verification
|
|
205
|
+
intent: Human-readable intent description
|
|
206
|
+
params: Optional action parameters
|
|
207
|
+
"""
|
|
208
|
+
return AttestationGuard(self, action, intent, params)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class AttestationGuard:
|
|
212
|
+
"""Context manager for guarded execution."""
|
|
213
|
+
|
|
214
|
+
def __init__(
|
|
215
|
+
self,
|
|
216
|
+
enforcement: Enforcement,
|
|
217
|
+
action: str,
|
|
218
|
+
intent: str,
|
|
219
|
+
params: Optional[Dict[str, Any]] = None,
|
|
220
|
+
):
|
|
221
|
+
self.enforcement = enforcement
|
|
222
|
+
self.action = action
|
|
223
|
+
self.intent = intent
|
|
224
|
+
self.params = params
|
|
225
|
+
self.attestation = None
|
|
226
|
+
|
|
227
|
+
def __enter__(self):
|
|
228
|
+
# Get and verify attestation before entering the block
|
|
229
|
+
result = self.enforcement.client.verify(
|
|
230
|
+
action_type=self.action,
|
|
231
|
+
intent=self.intent,
|
|
232
|
+
params=self.params,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if not result.get("approved"):
|
|
236
|
+
reason = result.get("reason", "Unknown")
|
|
237
|
+
if self.enforcement.verbose:
|
|
238
|
+
print_denied(self.action, self.intent, reason)
|
|
239
|
+
raise PolicyViolation(
|
|
240
|
+
message=f"Action denied: {reason}",
|
|
241
|
+
action_type=self.action,
|
|
242
|
+
denial_reason=reason,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
self.attestation = result.get("execution_attestation")
|
|
246
|
+
verify_signature(self.attestation)
|
|
247
|
+
|
|
248
|
+
if self.enforcement.paranoid:
|
|
249
|
+
jti = self.attestation.get("jti")
|
|
250
|
+
if self.enforcement.client.check_revocation(jti):
|
|
251
|
+
raise AttestationRevokedError(jti)
|
|
252
|
+
|
|
253
|
+
if self.enforcement.verbose:
|
|
254
|
+
print_approved(self.action, self.intent)
|
|
255
|
+
|
|
256
|
+
return self.attestation
|
|
257
|
+
|
|
258
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
259
|
+
return False # Don't suppress exceptions
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# Convenience function for one-off protected execution
|
|
263
|
+
def enforce(
|
|
264
|
+
client: SwiftAPI,
|
|
265
|
+
func: Callable[[], Any],
|
|
266
|
+
action: str,
|
|
267
|
+
intent: str,
|
|
268
|
+
params: Optional[Dict[str, Any]] = None,
|
|
269
|
+
) -> Any:
|
|
270
|
+
"""
|
|
271
|
+
One-off enforcement without creating an Enforcement instance.
|
|
272
|
+
|
|
273
|
+
Usage:
|
|
274
|
+
from swiftapi import SwiftAPI, enforce
|
|
275
|
+
|
|
276
|
+
api = SwiftAPI(key="...")
|
|
277
|
+
enforce(api, lambda: rm_rf("/"), action="file_delete", intent="Nuke it")
|
|
278
|
+
"""
|
|
279
|
+
guard = Enforcement(client)
|
|
280
|
+
return guard.run(func, action, intent, params)
|
swiftapi/exceptions.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SwiftAPI SDK Exceptions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SwiftAPIError(Exception):
|
|
7
|
+
"""Base exception for SwiftAPI SDK errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, status_code: int = None, response: dict = None):
|
|
10
|
+
self.message = message
|
|
11
|
+
self.status_code = status_code
|
|
12
|
+
self.response = response
|
|
13
|
+
super().__init__(self.message)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AuthenticationError(SwiftAPIError):
|
|
17
|
+
"""Raised when API key is invalid or missing."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PolicyViolation(SwiftAPIError):
|
|
22
|
+
"""Raised when an action violates policy and is denied."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, message: str, action_type: str = None, denial_reason: str = None):
|
|
25
|
+
self.action_type = action_type
|
|
26
|
+
self.denial_reason = denial_reason
|
|
27
|
+
super().__init__(message)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SignatureVerificationError(SwiftAPIError):
|
|
31
|
+
"""Raised when attestation signature verification fails."""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AttestationExpiredError(SwiftAPIError):
|
|
36
|
+
"""Raised when an attestation has expired."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AttestationRevokedError(SwiftAPIError):
|
|
41
|
+
"""Raised when an attestation has been revoked."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, jti: str):
|
|
44
|
+
self.jti = jti
|
|
45
|
+
super().__init__(f"Attestation {jti} has been revoked")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class RateLimitError(SwiftAPIError):
|
|
49
|
+
"""Raised when rate limit is exceeded."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, retry_after: int = None):
|
|
52
|
+
self.retry_after = retry_after
|
|
53
|
+
message = "Rate limit exceeded"
|
|
54
|
+
if retry_after:
|
|
55
|
+
message += f", retry after {retry_after}s"
|
|
56
|
+
super().__init__(message)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class NetworkError(SwiftAPIError):
|
|
60
|
+
"""Raised when network connectivity fails."""
|
|
61
|
+
pass
|
swiftapi/utils.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SwiftAPI SDK Utilities - UX and Display
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from colorama import Fore, Style, init
|
|
9
|
+
init(autoreset=True)
|
|
10
|
+
COLORS_AVAILABLE = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
COLORS_AVAILABLE = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Color constants
|
|
16
|
+
class Colors:
|
|
17
|
+
"""Terminal color codes for SwiftAPI output."""
|
|
18
|
+
|
|
19
|
+
if COLORS_AVAILABLE:
|
|
20
|
+
RED = Fore.RED
|
|
21
|
+
GREEN = Fore.GREEN
|
|
22
|
+
YELLOW = Fore.YELLOW
|
|
23
|
+
BLUE = Fore.BLUE
|
|
24
|
+
MAGENTA = Fore.MAGENTA
|
|
25
|
+
CYAN = Fore.CYAN
|
|
26
|
+
WHITE = Fore.WHITE
|
|
27
|
+
RESET = Style.RESET_ALL
|
|
28
|
+
BOLD = Style.BRIGHT
|
|
29
|
+
else:
|
|
30
|
+
RED = GREEN = YELLOW = BLUE = MAGENTA = CYAN = WHITE = RESET = BOLD = ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Unicode symbols
|
|
34
|
+
class Symbols:
|
|
35
|
+
"""Unicode symbols for SwiftAPI output."""
|
|
36
|
+
|
|
37
|
+
LOCK = "\U0001F512" # Locked
|
|
38
|
+
UNLOCK = "\U0001F513" # Unlocked
|
|
39
|
+
CHECK = "\u2713" # Checkmark
|
|
40
|
+
CROSS = "\u2717" # X mark
|
|
41
|
+
SHIELD = "\U0001F6E1" # Shield
|
|
42
|
+
KEY = "\U0001F511" # Key
|
|
43
|
+
WARNING = "\u26A0" # Warning
|
|
44
|
+
LIGHTNING = "\u26A1" # Lightning bolt
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def print_approved(action_type: str, intent: str):
|
|
48
|
+
"""Print approval message with green styling."""
|
|
49
|
+
print(f"{Colors.GREEN}{Symbols.CHECK} APPROVED{Colors.RESET} [{action_type}] {intent}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def print_denied(action_type: str, intent: str, reason: str = None):
|
|
53
|
+
"""Print denial message with red styling."""
|
|
54
|
+
msg = f"{Colors.RED}{Symbols.LOCK} DENIED{Colors.RESET} [{action_type}] {intent}"
|
|
55
|
+
if reason:
|
|
56
|
+
msg += f"\n {Colors.YELLOW}Reason: {reason}{Colors.RESET}"
|
|
57
|
+
print(msg, file=sys.stderr)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def print_verified(jti: str):
|
|
61
|
+
"""Print verification success message."""
|
|
62
|
+
print(f"{Colors.CYAN}{Symbols.SHIELD} VERIFIED{Colors.RESET} Attestation {jti[:16]}...")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def print_revoked(jti: str):
|
|
66
|
+
"""Print revocation message."""
|
|
67
|
+
print(f"{Colors.RED}{Symbols.CROSS} REVOKED{Colors.RESET} Attestation {jti[:16]}...")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def print_error(message: str):
|
|
71
|
+
"""Print error message."""
|
|
72
|
+
print(f"{Colors.RED}{Symbols.WARNING} ERROR{Colors.RESET} {message}", file=sys.stderr)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def print_info(message: str):
|
|
76
|
+
"""Print info message."""
|
|
77
|
+
print(f"{Colors.BLUE}{Symbols.LIGHTNING} INFO{Colors.RESET} {message}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def format_attestation(attestation: dict) -> str:
|
|
81
|
+
"""Format attestation for display."""
|
|
82
|
+
lines = [
|
|
83
|
+
f"{Colors.CYAN}Execution Attestation{Colors.RESET}",
|
|
84
|
+
f" JTI: {attestation.get('jti', 'N/A')}",
|
|
85
|
+
f" Expires: {attestation.get('expires_at', 'N/A')}",
|
|
86
|
+
f" Fingerprint: {attestation.get('action_fingerprint', 'N/A')[:32]}...",
|
|
87
|
+
f" Signed: {attestation.get('signing_mode', 'N/A')}",
|
|
88
|
+
]
|
|
89
|
+
return "\n".join(lines)
|
swiftapi/verifier.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SwiftAPI SDK - Ed25519 Signature Verifier (The Shield)
|
|
3
|
+
|
|
4
|
+
This module provides OFFLINE cryptographic verification of execution attestations.
|
|
5
|
+
It does not require network connectivity - it uses the hardcoded SwiftAPI public key.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import base64
|
|
9
|
+
import json
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from typing import Dict, Any
|
|
12
|
+
|
|
13
|
+
from nacl.signing import VerifyKey
|
|
14
|
+
from nacl.exceptions import BadSignatureError
|
|
15
|
+
|
|
16
|
+
from .exceptions import (
|
|
17
|
+
SignatureVerificationError,
|
|
18
|
+
AttestationExpiredError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# SwiftAPI's Ed25519 Public Key (Base64)
|
|
22
|
+
# This is the ONLY source of truth for attestation verification
|
|
23
|
+
SWIFTAPI_PUBLIC_KEY_B64 = "jajZXZ0R3CUWkE5/5Mxx5Y76CdsSzaPDuT7aWnooZSk="
|
|
24
|
+
|
|
25
|
+
# Decode public key once at module load
|
|
26
|
+
SWIFTAPI_PUBLIC_KEY = base64.b64decode(SWIFTAPI_PUBLIC_KEY_B64)
|
|
27
|
+
VERIFY_KEY = VerifyKey(SWIFTAPI_PUBLIC_KEY)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def verify_signature(attestation: Dict[str, Any]) -> bool:
|
|
31
|
+
"""
|
|
32
|
+
Verify the Ed25519 signature of an execution attestation.
|
|
33
|
+
|
|
34
|
+
This is OFFLINE verification - no network required.
|
|
35
|
+
The signature proves the attestation was issued by SwiftAPI.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
attestation: The execution_attestation dict from /verify response
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if signature is valid
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
SignatureVerificationError: If signature is invalid or missing
|
|
45
|
+
AttestationExpiredError: If attestation has expired
|
|
46
|
+
"""
|
|
47
|
+
# Extract required fields
|
|
48
|
+
signature_b64 = attestation.get("signature")
|
|
49
|
+
if not signature_b64:
|
|
50
|
+
raise SignatureVerificationError("Missing signature in attestation")
|
|
51
|
+
|
|
52
|
+
jti = attestation.get("jti")
|
|
53
|
+
action_fingerprint = attestation.get("action_fingerprint")
|
|
54
|
+
expires_at = attestation.get("expires_at")
|
|
55
|
+
|
|
56
|
+
if not all([jti, action_fingerprint, expires_at]):
|
|
57
|
+
raise SignatureVerificationError("Incomplete attestation: missing required fields")
|
|
58
|
+
|
|
59
|
+
# Check expiration FIRST
|
|
60
|
+
try:
|
|
61
|
+
expiry = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
|
|
62
|
+
now = datetime.now(timezone.utc)
|
|
63
|
+
if now > expiry:
|
|
64
|
+
raise AttestationExpiredError(f"Attestation expired at {expires_at}")
|
|
65
|
+
except ValueError as e:
|
|
66
|
+
raise SignatureVerificationError(f"Invalid expiration format: {e}")
|
|
67
|
+
|
|
68
|
+
# Reconstruct the signed payload (deterministic serialization)
|
|
69
|
+
# This MUST match the server's signing logic exactly
|
|
70
|
+
signed_payload = _reconstruct_signed_payload(attestation)
|
|
71
|
+
|
|
72
|
+
# Decode signature
|
|
73
|
+
try:
|
|
74
|
+
signature = base64.b64decode(signature_b64)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
raise SignatureVerificationError(f"Invalid signature encoding: {e}")
|
|
77
|
+
|
|
78
|
+
# Verify signature
|
|
79
|
+
try:
|
|
80
|
+
VERIFY_KEY.verify(signed_payload, signature)
|
|
81
|
+
return True
|
|
82
|
+
except BadSignatureError:
|
|
83
|
+
raise SignatureVerificationError(
|
|
84
|
+
"INVALID SIGNATURE: Attestation was not signed by SwiftAPI. "
|
|
85
|
+
"This could indicate a forged or tampered attestation."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _reconstruct_signed_payload(attestation: Dict[str, Any]) -> bytes:
|
|
90
|
+
"""
|
|
91
|
+
Reconstruct the exact payload that was signed by SwiftAPI.
|
|
92
|
+
|
|
93
|
+
The server signs the attestation BEFORE adding signature/signing_mode fields.
|
|
94
|
+
We must reconstruct that exact payload.
|
|
95
|
+
"""
|
|
96
|
+
# Copy attestation and remove signature fields (they weren't in the signed payload)
|
|
97
|
+
signed_fields = {k: v for k, v in attestation.items() if k not in ("signature", "signing_mode")}
|
|
98
|
+
|
|
99
|
+
# Deterministic JSON serialization (must match server exactly)
|
|
100
|
+
payload_str = json.dumps(signed_fields, sort_keys=True, separators=(",", ":"))
|
|
101
|
+
return payload_str.encode("utf-8")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_public_key() -> str:
|
|
105
|
+
"""Return SwiftAPI's public key in Base64 format."""
|
|
106
|
+
return SWIFTAPI_PUBLIC_KEY_B64
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def is_valid(attestation: Dict[str, Any]) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Check if attestation is valid without raising exceptions.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
attestation: The execution_attestation dict
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
True if valid, False otherwise
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
return verify_signature(attestation)
|
|
121
|
+
except Exception:
|
|
122
|
+
return False
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: swiftapi-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: SwiftAPI Python SDK - AI Action Verification Gateway
|
|
5
|
+
Author-email: Rayan Pal <rayan@swiftapi.ai>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://swiftapi.ai
|
|
8
|
+
Project-URL: Documentation, https://docs.swiftapi.ai
|
|
9
|
+
Project-URL: Repository, https://github.com/swiftapi/swiftapi-python
|
|
10
|
+
Project-URL: Issues, https://github.com/swiftapi/swiftapi-python/issues
|
|
11
|
+
Keywords: swiftapi,ai,verification,attestation,security,governance,enforcement
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: requests>=2.31.0
|
|
25
|
+
Requires-Dist: pynacl>=1.5.0
|
|
26
|
+
Requires-Dist: colorama>=0.4.6
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: types-requests>=2.31.0; extra == "dev"
|
|
33
|
+
|
|
34
|
+
# SwiftAPI Python SDK
|
|
35
|
+
|
|
36
|
+
**No AI action executes without verification.**
|
|
37
|
+
|
|
38
|
+
SwiftAPI is the ignition key for AI agents. This SDK provides Python bindings for the SwiftAPI execution governance protocol.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install swiftapi
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from swiftapi import SwiftAPI, Enforcement
|
|
50
|
+
|
|
51
|
+
# Initialize client with your API key
|
|
52
|
+
api = SwiftAPI(key="swiftapi_live_...")
|
|
53
|
+
|
|
54
|
+
# Create an enforcement point
|
|
55
|
+
guard = Enforcement(api)
|
|
56
|
+
|
|
57
|
+
# THE LINE THAT SAVES THE COMPANY
|
|
58
|
+
guard.run(
|
|
59
|
+
lambda: os.system("rm -rf /tmp/data"),
|
|
60
|
+
action="file_delete",
|
|
61
|
+
intent="Cleanup temporary files"
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If the action is denied by policy, a `PolicyViolation` exception is raised and **nothing executes**.
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
- **Cryptographic Enforcement**: Ed25519 signed attestations prove authorization
|
|
70
|
+
- **Offline Verification**: Verify attestation signatures without network calls
|
|
71
|
+
- **Policy Enforcement**: Actions blocked if they violate configured policies
|
|
72
|
+
- **Rate Limiting**: Built-in handling for API rate limits
|
|
73
|
+
- **Beautiful Output**: Color-coded terminal output for approvals/denials
|
|
74
|
+
|
|
75
|
+
## Usage Patterns
|
|
76
|
+
|
|
77
|
+
### 1. Direct Execution
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from swiftapi import SwiftAPI, Enforcement
|
|
81
|
+
|
|
82
|
+
api = SwiftAPI(key="swiftapi_live_...")
|
|
83
|
+
guard = Enforcement(api)
|
|
84
|
+
|
|
85
|
+
# Execute with verification
|
|
86
|
+
result = guard.run(
|
|
87
|
+
lambda: dangerous_operation(),
|
|
88
|
+
action="database_write",
|
|
89
|
+
intent="Update user preferences"
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. Decorator
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
@guard.protect(action="api_call", intent="Send notification")
|
|
97
|
+
def send_notification(user_id: str, message: str):
|
|
98
|
+
# This only runs if SwiftAPI approves
|
|
99
|
+
notification_service.send(user_id, message)
|
|
100
|
+
|
|
101
|
+
# Usage - automatically enforced
|
|
102
|
+
send_notification("user123", "Hello!")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. Context Manager
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
with guard.guard(action="file_write", intent="Save configuration"):
|
|
109
|
+
# This block only executes if approved
|
|
110
|
+
with open("/etc/myapp/config.json", "w") as f:
|
|
111
|
+
json.dump(config, f)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4. One-off Enforcement
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from swiftapi import SwiftAPI, enforce
|
|
118
|
+
|
|
119
|
+
api = SwiftAPI(key="swiftapi_live_...")
|
|
120
|
+
enforce(api, lambda: risky_operation(), action="admin", intent="Reset system")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Paranoid Mode
|
|
124
|
+
|
|
125
|
+
For maximum security, enable paranoid mode to check revocation status online:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
guard = Enforcement(api, paranoid=True)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This adds an extra network call but ensures revoked attestations are caught in real-time.
|
|
132
|
+
|
|
133
|
+
## Offline Verification
|
|
134
|
+
|
|
135
|
+
You can verify attestation signatures without any network calls:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from swiftapi import verify_signature, is_valid
|
|
139
|
+
|
|
140
|
+
# Verify signature (raises exception if invalid)
|
|
141
|
+
verify_signature(attestation)
|
|
142
|
+
|
|
143
|
+
# Check validity without exceptions
|
|
144
|
+
if is_valid(attestation):
|
|
145
|
+
print("Attestation is valid")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Error Handling
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from swiftapi import (
|
|
152
|
+
SwiftAPI,
|
|
153
|
+
Enforcement,
|
|
154
|
+
PolicyViolation,
|
|
155
|
+
SignatureVerificationError,
|
|
156
|
+
AttestationRevokedError,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
api = SwiftAPI(key="swiftapi_live_...")
|
|
160
|
+
guard = Enforcement(api)
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
guard.run(lambda: delete_everything(), action="nuke", intent="YOLO")
|
|
164
|
+
except PolicyViolation as e:
|
|
165
|
+
print(f"Action denied: {e.denial_reason}")
|
|
166
|
+
except SignatureVerificationError:
|
|
167
|
+
print("CRITICAL: Attestation signature is invalid!")
|
|
168
|
+
except AttestationRevokedError as e:
|
|
169
|
+
print(f"Attestation {e.jti} was revoked")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## API Client
|
|
173
|
+
|
|
174
|
+
The SDK also provides direct API access:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from swiftapi import SwiftAPI
|
|
178
|
+
|
|
179
|
+
api = SwiftAPI(key="swiftapi_live_...")
|
|
180
|
+
|
|
181
|
+
# Get API info
|
|
182
|
+
info = api.get_info()
|
|
183
|
+
|
|
184
|
+
# Verify an action
|
|
185
|
+
result = api.verify(
|
|
186
|
+
action_type="file_write",
|
|
187
|
+
intent="Save user data",
|
|
188
|
+
params={"path": "/data/users.json"}
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Check attestation revocation
|
|
192
|
+
is_revoked = api.check_revocation(jti="attestation-id")
|
|
193
|
+
|
|
194
|
+
# List authority keys (admin only)
|
|
195
|
+
keys = api.list_keys()
|
|
196
|
+
|
|
197
|
+
# Create new key (admin only)
|
|
198
|
+
new_key = api.create_key(name="agent-1", scopes=["verify"])
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Configuration
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
api = SwiftAPI(
|
|
205
|
+
key="swiftapi_live_...",
|
|
206
|
+
base_url="https://swiftapi.ai", # Default
|
|
207
|
+
timeout=30, # Request timeout in seconds
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
guard = Enforcement(
|
|
211
|
+
client=api,
|
|
212
|
+
paranoid=False, # Enable online revocation checks
|
|
213
|
+
verbose=True, # Print status messages
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## The Golden Loop
|
|
218
|
+
|
|
219
|
+
Every protected action goes through this verification chain:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
┌─────────────────────────────────────────────────────────┐
|
|
223
|
+
│ THE GOLDEN LOOP │
|
|
224
|
+
├─────────────────────────────────────────────────────────┤
|
|
225
|
+
│ │
|
|
226
|
+
│ 1. API CALL │
|
|
227
|
+
│ client.verify() ──────────────────────────────┐ │
|
|
228
|
+
│ │ │
|
|
229
|
+
│ 2. CRYPTO CHECK (Offline Truth) │ │
|
|
230
|
+
│ verifier.verify_signature() ◄─────────────────┤ │
|
|
231
|
+
│ │ │
|
|
232
|
+
│ 3. ONLINE CHECK (Optional/Paranoid) │ │
|
|
233
|
+
│ client.check_revocation() ◄───────────────────┤ │
|
|
234
|
+
│ │ │
|
|
235
|
+
│ 4. EXECUTE │ │
|
|
236
|
+
│ func() ◄──────────────────────────────────────┘ │
|
|
237
|
+
│ │
|
|
238
|
+
│ If ANY step fails → PolicyViolation raised │
|
|
239
|
+
│ The action NEVER executes without full verification │
|
|
240
|
+
│ │
|
|
241
|
+
└─────────────────────────────────────────────────────────┘
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT License - See LICENSE file for details.
|
|
247
|
+
|
|
248
|
+
## Links
|
|
249
|
+
|
|
250
|
+
- **API**: https://swiftapi.ai
|
|
251
|
+
- **Documentation**: https://docs.swiftapi.ai
|
|
252
|
+
- **GitHub**: https://github.com/swiftapi/swiftapi-python
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
*Built by Rayan Pal. No AI action executes without verification.*
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
swiftapi/__init__.py,sha256=u4aQrcZqV7Hjdy1eOV_-G1OvYH7_iZ1Yr4qelDXPg2c,1938
|
|
2
|
+
swiftapi/client.py,sha256=i7O4GmlS_7HVxKwFvdh550pP1jcNUZXAyXkhTqviCi8,9373
|
|
3
|
+
swiftapi/enforcement.py,sha256=L-2JKvvL5U3Q4ZU_o889RxZteuN-ELuxKGIMUXtx2sw,8725
|
|
4
|
+
swiftapi/exceptions.py,sha256=hBleOCc1WwDEM70WNYTy1TDOSOaBJSx3PtzFzfmVOGY,1701
|
|
5
|
+
swiftapi/utils.py,sha256=HVDhQq7-5dDiuifzo58BH57S1VAvXYB4hIh41PiS20U,2765
|
|
6
|
+
swiftapi/verifier.py,sha256=KoZWx6SIgeCs2ke3ufQYN8or5-8Uo3DeKsn9AaXBuB0,4161
|
|
7
|
+
swiftapi_python-1.0.0.dist-info/METADATA,sha256=pWrSZBuOKXPpkyG1cbx6_WhNNmh59prvB0TYGE72TdE,7814
|
|
8
|
+
swiftapi_python-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
swiftapi_python-1.0.0.dist-info/top_level.txt,sha256=oio-d2BTrT91P7dmufUhlSYjOtWBh4bOYz6rsTDDvu4,9
|
|
10
|
+
swiftapi_python-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
swiftapi
|