shieldcortex 0.1.1__tar.gz → 0.3.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.
- shieldcortex-0.3.0/CHANGELOG.md +57 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/PKG-INFO +46 -8
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/README.md +44 -6
- shieldcortex-0.3.0/examples/audit_export.py +48 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/pyproject.toml +1 -1
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/src/shieldcortex/__init__.py +120 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/src/shieldcortex/_http.py +68 -8
- shieldcortex-0.3.0/src/shieldcortex/_payloads.py +341 -0
- shieldcortex-0.3.0/src/shieldcortex/_version.py +1 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/src/shieldcortex/async_client.py +456 -5
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/src/shieldcortex/client.py +446 -5
- shieldcortex-0.3.0/src/shieldcortex/types.py +1256 -0
- shieldcortex-0.3.0/tests/conftest.py +23 -0
- shieldcortex-0.3.0/tests/endpoint_manifest.py +133 -0
- shieldcortex-0.3.0/tests/test_audit_exports.py +571 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/tests/test_client.py +109 -2
- shieldcortex-0.3.0/tests/test_incidents_recall.py +257 -0
- shieldcortex-0.3.0/tests/test_license.py +146 -0
- shieldcortex-0.3.0/tests/test_parity.py +100 -0
- shieldcortex-0.3.0/tests/test_skills_threats.py +243 -0
- shieldcortex-0.3.0/tests/test_sync.py +551 -0
- shieldcortex-0.3.0/tests/test_verify.py +446 -0
- shieldcortex-0.1.1/examples/audit_export.py +0 -29
- shieldcortex-0.1.1/src/shieldcortex/_version.py +0 -1
- shieldcortex-0.1.1/src/shieldcortex/types.py +0 -512
- shieldcortex-0.1.1/tests/conftest.py +0 -12
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/.github/workflows/publish.yml +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/.github/workflows/test.yml +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/.gitignore +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/LICENSE +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/examples/basic_scan.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/examples/batch_scanning.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/examples/crewai_memory.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/examples/langchain_callback.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/src/shieldcortex/errors.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/src/shieldcortex/integrations/__init__.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/src/shieldcortex/integrations/crewai.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/src/shieldcortex/integrations/langchain.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/src/shieldcortex/pagination.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/tests/__init__.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/tests/fixtures.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/tests/integrations/__init__.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/tests/integrations/test_crewai.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/tests/test_errors.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/tests/test_pagination.py +0 -0
- {shieldcortex-0.1.1 → shieldcortex-0.3.0}/tests/test_types.py +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 — 2026-08-11
|
|
4
|
+
|
|
5
|
+
SDK lockstep release: the Python SDK now covers the full customer surface
|
|
6
|
+
documented in the SDK lockstep contract, in lockstep with the TypeScript SDK.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **Verification** — `submit_verification()`, `list_verifications()`,
|
|
11
|
+
`get_verification()`, `get_verification_stats()`, `delete_verification()`
|
|
12
|
+
- **Skills** — `ingest_skill_scans()`, `list_skill_scans()`
|
|
13
|
+
- **Threats** — `report_threat()` (OpenClaw realtime compat shim)
|
|
14
|
+
- **Incidents** — `replay_incidents()`
|
|
15
|
+
- **Recall** — `explain_recall()`
|
|
16
|
+
- **Sync** — `get_sync_health()`, `push_memories()`, `list_synced_memories()`,
|
|
17
|
+
`push_memory_graph()`. Note: `list_synced_memories()` omits the
|
|
18
|
+
`include_deleted` param unless truthy — the server Boolean-coerces any
|
|
19
|
+
present value (even `"false"`) as enabling it. Every other boolean query
|
|
20
|
+
param was audited for the same coercion trap; none affected.
|
|
21
|
+
- **Licence** — `get_license()`, `regenerate_license()`
|
|
22
|
+
- **Audit** — `ingest_audit_events()`, `get_iron_dome_stats()`,
|
|
23
|
+
`get_iron_dome_events()`, and the export-manifest surface
|
|
24
|
+
(`list_audit_exports()`, `get_audit_export_manifest()`,
|
|
25
|
+
`verify_audit_export()`, `list_audit_export_verifications()`)
|
|
26
|
+
- Cross-SDK endpoint parity manifest and drift guard
|
|
27
|
+
(`tests/endpoint_manifest.py`, kept in lockstep with the TS SDK's
|
|
28
|
+
`tests/endpoint-manifest.ts`), including a sync/async surface parity test
|
|
29
|
+
- Shared request builders (`_payloads.py`) so the sync and async clients
|
|
30
|
+
construct identical wire payloads and cannot drift
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **BREAKING:** `export_audit_logs()` (both clients) now returns
|
|
35
|
+
`AuditExportResult` — the raw file body in `.content` plus the parsed
|
|
36
|
+
`X-ShieldCortex-Export-*` integrity headers in `.headers` — instead of a
|
|
37
|
+
bare `str`. Previously the integrity headers were discarded, so exports
|
|
38
|
+
structurally could not be verified. An absent `sha256`/`signature` header
|
|
39
|
+
is `None` (never `""`): the export is unverifiable. Migration:
|
|
40
|
+
`client.export_audit_logs(...)` → `client.export_audit_logs(...).content`.
|
|
41
|
+
|
|
42
|
+
### Deprecated
|
|
43
|
+
|
|
44
|
+
- `create_checkout_session()` / `create_portal_session()` now emit
|
|
45
|
+
`DeprecationWarning`: self-serve plans were retired 2026-07 (Free +
|
|
46
|
+
Enterprise model); the endpoints remain for grandfathered licence holders.
|
|
47
|
+
|
|
48
|
+
## 0.1.1 — 2026-03-12
|
|
49
|
+
|
|
50
|
+
- Added Iron Dome patterns/policies client support; fixed the default-policy
|
|
51
|
+
verb (PUT).
|
|
52
|
+
|
|
53
|
+
## 0.1.0 — 2026-02-15
|
|
54
|
+
|
|
55
|
+
- Initial release: scanning, audit, quarantine, keys, teams, invites,
|
|
56
|
+
billing, devices, alerts, webhooks, firewall rules; sync + async clients;
|
|
57
|
+
CrewAI and LangChain integrations; Python 3.9+ compatibility fixes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: shieldcortex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Official Python SDK for the ShieldCortex API — AI memory security scanning
|
|
5
5
|
Project-URL: Homepage, https://shieldcortex.ai
|
|
6
6
|
Project-URL: Documentation, https://shieldcortex.ai/docs
|
|
@@ -166,8 +166,10 @@ logs = client.get_audit_logs(AuditQuery(level="BLOCK", limit=10))
|
|
|
166
166
|
for entry in client.iter_audit_logs():
|
|
167
167
|
print(entry.id, entry.firewall_result)
|
|
168
168
|
|
|
169
|
-
# Export as CSV
|
|
170
|
-
|
|
169
|
+
# Export as CSV — returns the raw body plus integrity headers
|
|
170
|
+
export = client.export_audit_logs(format="csv")
|
|
171
|
+
print(export.content)
|
|
172
|
+
print(export.headers.sha256) # None ⇒ export unverifiable
|
|
171
173
|
```
|
|
172
174
|
|
|
173
175
|
## Error Handling
|
|
@@ -185,24 +187,60 @@ except ValidationError:
|
|
|
185
187
|
print("Invalid request")
|
|
186
188
|
```
|
|
187
189
|
|
|
188
|
-
##
|
|
190
|
+
## API Coverage
|
|
189
191
|
|
|
190
|
-
The SDK covers
|
|
192
|
+
The SDK covers the full customer surface documented in the SDK lockstep contract:
|
|
191
193
|
|
|
192
194
|
| Category | Methods |
|
|
193
195
|
|----------|---------|
|
|
194
196
|
| **Scanning** | `scan()`, `scan_batch()`, `scan_skill()` |
|
|
195
|
-
| **Audit** | `get_audit_logs()`, `get_audit_entry()`, `get_audit_stats()`, `get_audit_trends()`, `export_audit_logs()`, `iter_audit_logs()` |
|
|
197
|
+
| **Audit** | `get_audit_logs()`, `get_audit_entry()`, `get_audit_stats()`, `get_audit_trends()`, `export_audit_logs()`, `iter_audit_logs()`, `ingest_audit_events()`, `get_iron_dome_stats()`, `get_iron_dome_events()` |
|
|
198
|
+
| **Audit Export Manifests** | `list_audit_exports()`, `get_audit_export_manifest()`, `verify_audit_export()`, `list_audit_export_verifications()` |
|
|
196
199
|
| **Quarantine** | `get_quarantine()`, `get_quarantine_item()`, `review_quarantine_item()` |
|
|
197
200
|
| **API Keys** | `create_api_key()`, `list_api_keys()`, `revoke_api_key()` |
|
|
198
201
|
| **Teams** | `get_team()`, `update_team()`, `get_team_members()`, `get_usage()` |
|
|
199
202
|
| **Invites** | `create_invite()`, `list_invites()`, `delete_invite()`, `resend_invite()` |
|
|
200
|
-
| **Billing** | `create_checkout_session()`, `create_portal_session()` |
|
|
203
|
+
| **Billing** (deprecated) | `create_checkout_session()`, `create_portal_session()` — self-serve plans retired 2026-07; retained for grandfathered licence holders |
|
|
201
204
|
| **Devices** | `get_devices()`, `register_device()`, `update_device()`, `device_heartbeat()` |
|
|
202
205
|
| **Alerts** | `get_alerts()`, `create_alert()`, `update_alert()`, `delete_alert()` |
|
|
203
206
|
| **Webhooks** | `get_webhooks()`, `create_webhook()`, `update_webhook()`, `delete_webhook()`, `test_webhook()`, `get_webhook_deliveries()` |
|
|
204
207
|
| **Firewall Rules** | `get_firewall_rules()`, `get_active_firewall_rules()`, `create_firewall_rule()`, `update_firewall_rule()`, `delete_firewall_rule()` |
|
|
205
208
|
| **Iron Dome** | `get_injection_patterns()`, `get_injection_patterns_sync()`, `create_injection_pattern()`, `update_injection_pattern()`, `test_injection_pattern()`, `delete_injection_pattern()`, `get_iron_dome_policies()`, `get_iron_dome_policy_sync()`, `create_iron_dome_policy()`, `update_iron_dome_policy()`, `set_default_iron_dome_policy()`, `delete_iron_dome_policy()` |
|
|
209
|
+
| **Verification** | `submit_verification()`, `list_verifications()`, `get_verification()`, `get_verification_stats()`, `delete_verification()` |
|
|
210
|
+
| **Skills** | `ingest_skill_scans()`, `list_skill_scans()` |
|
|
211
|
+
| **Threats** | `report_threat()` |
|
|
212
|
+
| **Incidents** | `replay_incidents()` |
|
|
213
|
+
| **Recall** | `explain_recall()` |
|
|
214
|
+
| **Sync** | `get_sync_health()`, `push_memories()`, `list_synced_memories()`, `push_memory_graph()` |
|
|
215
|
+
| **Licence** | `get_license()`, `regenerate_license()` |
|
|
216
|
+
|
|
217
|
+
### Deferred endpoints
|
|
218
|
+
|
|
219
|
+
The audit **verification-export download chain** (`GET .../verifications/export`,
|
|
220
|
+
`GET .../verification-exports`, `GET .../verification-exports/{id}` and its
|
|
221
|
+
`/download`) is deliberately deferred — it serves CLI tooling rather than SDK
|
|
222
|
+
consumers. See `tests/endpoint_manifest.py` for the canonical list.
|
|
223
|
+
|
|
224
|
+
### Out of scope
|
|
225
|
+
|
|
226
|
+
- `/v1/platform/*` — platform-owner operations
|
|
227
|
+
- `/v1/auth/*` — magic-link login flows (browser/dashboard, not API-key auth)
|
|
228
|
+
- `/v1/customizer/*` — retired 2026-07
|
|
229
|
+
|
|
230
|
+
Additional dashboard/session-scoped `/v1` routes exist server-side; they sit
|
|
231
|
+
outside the SDK's API-key surface.
|
|
232
|
+
|
|
233
|
+
### Cross-SDK policy notes
|
|
234
|
+
|
|
235
|
+
The Python and TypeScript SDKs cover the same endpoint manifest, with two
|
|
236
|
+
deliberate differences:
|
|
237
|
+
|
|
238
|
+
1. **Deletes** — the TypeScript SDK returns `void` from every delete method;
|
|
239
|
+
the Python SDK returns the typed response body where the server sends one
|
|
240
|
+
(per-language internal consistency).
|
|
241
|
+
2. **Audit export** — both SDKs return the raw file content together with the
|
|
242
|
+
parsed `X-ShieldCortex-Export-*` integrity headers. An absent `sha256` or
|
|
243
|
+
`signature` header means the export is unverifiable.
|
|
206
244
|
|
|
207
245
|
## Documentation
|
|
208
246
|
|
|
@@ -127,8 +127,10 @@ logs = client.get_audit_logs(AuditQuery(level="BLOCK", limit=10))
|
|
|
127
127
|
for entry in client.iter_audit_logs():
|
|
128
128
|
print(entry.id, entry.firewall_result)
|
|
129
129
|
|
|
130
|
-
# Export as CSV
|
|
131
|
-
|
|
130
|
+
# Export as CSV — returns the raw body plus integrity headers
|
|
131
|
+
export = client.export_audit_logs(format="csv")
|
|
132
|
+
print(export.content)
|
|
133
|
+
print(export.headers.sha256) # None ⇒ export unverifiable
|
|
132
134
|
```
|
|
133
135
|
|
|
134
136
|
## Error Handling
|
|
@@ -146,24 +148,60 @@ except ValidationError:
|
|
|
146
148
|
print("Invalid request")
|
|
147
149
|
```
|
|
148
150
|
|
|
149
|
-
##
|
|
151
|
+
## API Coverage
|
|
150
152
|
|
|
151
|
-
The SDK covers
|
|
153
|
+
The SDK covers the full customer surface documented in the SDK lockstep contract:
|
|
152
154
|
|
|
153
155
|
| Category | Methods |
|
|
154
156
|
|----------|---------|
|
|
155
157
|
| **Scanning** | `scan()`, `scan_batch()`, `scan_skill()` |
|
|
156
|
-
| **Audit** | `get_audit_logs()`, `get_audit_entry()`, `get_audit_stats()`, `get_audit_trends()`, `export_audit_logs()`, `iter_audit_logs()` |
|
|
158
|
+
| **Audit** | `get_audit_logs()`, `get_audit_entry()`, `get_audit_stats()`, `get_audit_trends()`, `export_audit_logs()`, `iter_audit_logs()`, `ingest_audit_events()`, `get_iron_dome_stats()`, `get_iron_dome_events()` |
|
|
159
|
+
| **Audit Export Manifests** | `list_audit_exports()`, `get_audit_export_manifest()`, `verify_audit_export()`, `list_audit_export_verifications()` |
|
|
157
160
|
| **Quarantine** | `get_quarantine()`, `get_quarantine_item()`, `review_quarantine_item()` |
|
|
158
161
|
| **API Keys** | `create_api_key()`, `list_api_keys()`, `revoke_api_key()` |
|
|
159
162
|
| **Teams** | `get_team()`, `update_team()`, `get_team_members()`, `get_usage()` |
|
|
160
163
|
| **Invites** | `create_invite()`, `list_invites()`, `delete_invite()`, `resend_invite()` |
|
|
161
|
-
| **Billing** | `create_checkout_session()`, `create_portal_session()` |
|
|
164
|
+
| **Billing** (deprecated) | `create_checkout_session()`, `create_portal_session()` — self-serve plans retired 2026-07; retained for grandfathered licence holders |
|
|
162
165
|
| **Devices** | `get_devices()`, `register_device()`, `update_device()`, `device_heartbeat()` |
|
|
163
166
|
| **Alerts** | `get_alerts()`, `create_alert()`, `update_alert()`, `delete_alert()` |
|
|
164
167
|
| **Webhooks** | `get_webhooks()`, `create_webhook()`, `update_webhook()`, `delete_webhook()`, `test_webhook()`, `get_webhook_deliveries()` |
|
|
165
168
|
| **Firewall Rules** | `get_firewall_rules()`, `get_active_firewall_rules()`, `create_firewall_rule()`, `update_firewall_rule()`, `delete_firewall_rule()` |
|
|
166
169
|
| **Iron Dome** | `get_injection_patterns()`, `get_injection_patterns_sync()`, `create_injection_pattern()`, `update_injection_pattern()`, `test_injection_pattern()`, `delete_injection_pattern()`, `get_iron_dome_policies()`, `get_iron_dome_policy_sync()`, `create_iron_dome_policy()`, `update_iron_dome_policy()`, `set_default_iron_dome_policy()`, `delete_iron_dome_policy()` |
|
|
170
|
+
| **Verification** | `submit_verification()`, `list_verifications()`, `get_verification()`, `get_verification_stats()`, `delete_verification()` |
|
|
171
|
+
| **Skills** | `ingest_skill_scans()`, `list_skill_scans()` |
|
|
172
|
+
| **Threats** | `report_threat()` |
|
|
173
|
+
| **Incidents** | `replay_incidents()` |
|
|
174
|
+
| **Recall** | `explain_recall()` |
|
|
175
|
+
| **Sync** | `get_sync_health()`, `push_memories()`, `list_synced_memories()`, `push_memory_graph()` |
|
|
176
|
+
| **Licence** | `get_license()`, `regenerate_license()` |
|
|
177
|
+
|
|
178
|
+
### Deferred endpoints
|
|
179
|
+
|
|
180
|
+
The audit **verification-export download chain** (`GET .../verifications/export`,
|
|
181
|
+
`GET .../verification-exports`, `GET .../verification-exports/{id}` and its
|
|
182
|
+
`/download`) is deliberately deferred — it serves CLI tooling rather than SDK
|
|
183
|
+
consumers. See `tests/endpoint_manifest.py` for the canonical list.
|
|
184
|
+
|
|
185
|
+
### Out of scope
|
|
186
|
+
|
|
187
|
+
- `/v1/platform/*` — platform-owner operations
|
|
188
|
+
- `/v1/auth/*` — magic-link login flows (browser/dashboard, not API-key auth)
|
|
189
|
+
- `/v1/customizer/*` — retired 2026-07
|
|
190
|
+
|
|
191
|
+
Additional dashboard/session-scoped `/v1` routes exist server-side; they sit
|
|
192
|
+
outside the SDK's API-key surface.
|
|
193
|
+
|
|
194
|
+
### Cross-SDK policy notes
|
|
195
|
+
|
|
196
|
+
The Python and TypeScript SDKs cover the same endpoint manifest, with two
|
|
197
|
+
deliberate differences:
|
|
198
|
+
|
|
199
|
+
1. **Deletes** — the TypeScript SDK returns `void` from every delete method;
|
|
200
|
+
the Python SDK returns the typed response body where the server sends one
|
|
201
|
+
(per-language internal consistency).
|
|
202
|
+
2. **Audit export** — both SDKs return the raw file content together with the
|
|
203
|
+
parsed `X-ShieldCortex-Export-*` integrity headers. An absent `sha256` or
|
|
204
|
+
`signature` header means the export is unverifiable.
|
|
167
205
|
|
|
168
206
|
## Documentation
|
|
169
207
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Audit log querying and export example."""
|
|
2
|
+
|
|
3
|
+
from shieldcortex import ShieldCortex, AuditQuery
|
|
4
|
+
|
|
5
|
+
client = ShieldCortex(api_key="sc_live_YOUR_KEY_HERE")
|
|
6
|
+
|
|
7
|
+
# Get summary stats
|
|
8
|
+
stats = client.get_audit_stats(time_range="7d")
|
|
9
|
+
print(f"Total operations: {stats.total_operations}")
|
|
10
|
+
print(f"Allowed: {stats.allowed_count}")
|
|
11
|
+
print(f"Blocked: {stats.blocked_count}")
|
|
12
|
+
print(f"Quarantined: {stats.quarantined_count}")
|
|
13
|
+
|
|
14
|
+
# Query blocked entries
|
|
15
|
+
logs = client.get_audit_logs(AuditQuery(level="BLOCK", limit=5))
|
|
16
|
+
print(f"\nRecent blocks ({logs.total} total):")
|
|
17
|
+
for entry in logs.logs:
|
|
18
|
+
print(f" [{entry.timestamp}] {entry.reason} (trust: {entry.trust_score})")
|
|
19
|
+
|
|
20
|
+
# Auto-paginate through all entries
|
|
21
|
+
print("\nAll audit entries:")
|
|
22
|
+
for entry in client.iter_audit_logs(AuditQuery(level="BLOCK")):
|
|
23
|
+
print(f" #{entry.id}: {entry.firewall_result}")
|
|
24
|
+
|
|
25
|
+
# Export as CSV — returns the raw body plus X-ShieldCortex-Export-* integrity headers
|
|
26
|
+
export = client.export_audit_logs(format="csv")
|
|
27
|
+
with open("audit_export.csv", "w") as f:
|
|
28
|
+
f.write(export.content)
|
|
29
|
+
print("\nExported to audit_export.csv")
|
|
30
|
+
|
|
31
|
+
# Verify the export's integrity. sha256/signature/manifest_id are None when
|
|
32
|
+
# the server did not send them — the export is unverifiable, never "".
|
|
33
|
+
headers = export.headers
|
|
34
|
+
if headers.sha256 is None or headers.signature is None:
|
|
35
|
+
print("Export is UNVERIFIABLE — integrity headers absent")
|
|
36
|
+
else:
|
|
37
|
+
print(f"Entries: {headers.count}")
|
|
38
|
+
print(f"SHA-256: {headers.sha256}")
|
|
39
|
+
print(f"Signature ({headers.signature_algorithm}): {headers.signature}")
|
|
40
|
+
if headers.manifest_id is not None:
|
|
41
|
+
# Server-side check against the persisted, signed manifest
|
|
42
|
+
response = client.verify_audit_export(
|
|
43
|
+
headers.manifest_id, export_sha256=headers.sha256
|
|
44
|
+
)
|
|
45
|
+
result = response.verification
|
|
46
|
+
print(f"Manifest {headers.manifest_id}:")
|
|
47
|
+
print(f" signature valid: {result.signature_valid}")
|
|
48
|
+
print(f" sha256 matches: {result.sha256_matches}")
|
|
@@ -24,6 +24,10 @@ from shieldcortex.errors import (
|
|
|
24
24
|
from shieldcortex.types import (
|
|
25
25
|
AlertRule,
|
|
26
26
|
AuditEntry,
|
|
27
|
+
AuditExportHeaders,
|
|
28
|
+
AuditExportResult,
|
|
29
|
+
AuditIngestEntry,
|
|
30
|
+
AuditIngestResponse,
|
|
27
31
|
AuditQuery,
|
|
28
32
|
AuditResponse,
|
|
29
33
|
AuditStats,
|
|
@@ -32,19 +36,39 @@ from shieldcortex.types import (
|
|
|
32
36
|
CheckoutResponse,
|
|
33
37
|
CreateKeyResponse,
|
|
34
38
|
CreateWebhookResponse,
|
|
39
|
+
DeleteVerificationResponse,
|
|
40
|
+
DetectedThreat,
|
|
35
41
|
Device,
|
|
42
|
+
ExportManifest,
|
|
43
|
+
ExportManifestDetail,
|
|
44
|
+
ExportManifestListResponse,
|
|
45
|
+
ExportVerificationEvent,
|
|
46
|
+
ExportVerificationListResponse,
|
|
47
|
+
ExportVerifyExpected,
|
|
48
|
+
ExportVerifyResponse,
|
|
49
|
+
ExportVerifyResult,
|
|
36
50
|
FirewallResult,
|
|
37
51
|
FirewallRule,
|
|
38
52
|
FragmentationResult,
|
|
53
|
+
IncidentCoverage,
|
|
54
|
+
IncidentEvent,
|
|
55
|
+
IncidentReplayResponse,
|
|
56
|
+
IncidentStreamSummary,
|
|
39
57
|
InjectionPattern,
|
|
40
58
|
InjectionPatternsResponse,
|
|
41
59
|
Invite,
|
|
42
60
|
InviteListResponse,
|
|
61
|
+
IronDomeEvent,
|
|
62
|
+
IronDomeEventsResponse,
|
|
43
63
|
IronDomePoliciesResponse,
|
|
44
64
|
IronDomePolicy,
|
|
65
|
+
IronDomeStats,
|
|
66
|
+
IronDomeTopDevice,
|
|
45
67
|
KeyInfo,
|
|
46
68
|
KeyListItem,
|
|
47
69
|
KeyListResponse,
|
|
70
|
+
LicenseInfo,
|
|
71
|
+
ManifestVerification,
|
|
48
72
|
MembersResponse,
|
|
49
73
|
Pagination,
|
|
50
74
|
PatternSyncItem,
|
|
@@ -55,24 +79,56 @@ from shieldcortex.types import (
|
|
|
55
79
|
QuarantineItem,
|
|
56
80
|
QuarantineQuery,
|
|
57
81
|
QuarantineResponse,
|
|
82
|
+
RecallExplainResponse,
|
|
83
|
+
RecallLinkedEntity,
|
|
84
|
+
RecallMemory,
|
|
85
|
+
RecallResult,
|
|
86
|
+
RecallScoreBreakdown,
|
|
87
|
+
RegenerateLicenseResponse,
|
|
58
88
|
ReviewResponse,
|
|
59
89
|
ScanConfig,
|
|
60
90
|
ScanResult,
|
|
61
91
|
ScanSource,
|
|
62
92
|
SensitivityResult,
|
|
93
|
+
SkillFinding,
|
|
94
|
+
SkillIngestResponse,
|
|
95
|
+
SkillScanFile,
|
|
96
|
+
SkillScanListResponse,
|
|
63
97
|
SkillScanMetadata,
|
|
64
98
|
SkillScanResult,
|
|
65
99
|
SkillThreat,
|
|
66
100
|
SourceCount,
|
|
101
|
+
SyncDeviceHealth,
|
|
102
|
+
SyncedMemoriesResponse,
|
|
103
|
+
SyncedMemory,
|
|
104
|
+
SyncedSkillScan,
|
|
105
|
+
SyncGraphEntity,
|
|
106
|
+
SyncGraphResponse,
|
|
107
|
+
SyncGraphTriple,
|
|
108
|
+
SyncHealth,
|
|
109
|
+
SyncHealthSummary,
|
|
110
|
+
SyncLatestDeviceSync,
|
|
111
|
+
SyncMemory,
|
|
112
|
+
SyncMemoryEntityLink,
|
|
113
|
+
SyncMemorySummary,
|
|
114
|
+
SyncPushResponse,
|
|
67
115
|
TeamInfo,
|
|
68
116
|
TeamMember,
|
|
69
117
|
TestWebhookResponse,
|
|
118
|
+
ThreatReportResponse,
|
|
70
119
|
TrendBucket,
|
|
71
120
|
TrendResponse,
|
|
72
121
|
TrustResult,
|
|
73
122
|
UsageBreakdown,
|
|
74
123
|
UsageInfo,
|
|
75
124
|
UsageResponse,
|
|
125
|
+
VerificationDayStats,
|
|
126
|
+
VerificationDetail,
|
|
127
|
+
VerificationListItem,
|
|
128
|
+
VerificationListResponse,
|
|
129
|
+
VerificationQuota,
|
|
130
|
+
VerificationResult,
|
|
131
|
+
VerificationStats,
|
|
76
132
|
Webhook,
|
|
77
133
|
WebhookDelivery,
|
|
78
134
|
)
|
|
@@ -111,6 +167,8 @@ __all__ = [
|
|
|
111
167
|
"SourceCount",
|
|
112
168
|
"TrendBucket",
|
|
113
169
|
"TrendResponse",
|
|
170
|
+
"AuditExportHeaders",
|
|
171
|
+
"AuditExportResult",
|
|
114
172
|
"Pagination",
|
|
115
173
|
# Quarantine types
|
|
116
174
|
"QuarantineQuery",
|
|
@@ -154,4 +212,66 @@ __all__ = [
|
|
|
154
212
|
"IronDomePolicy",
|
|
155
213
|
"IronDomePoliciesResponse",
|
|
156
214
|
"PolicySyncResponse",
|
|
215
|
+
# Verification types
|
|
216
|
+
"DetectedThreat",
|
|
217
|
+
"VerificationResult",
|
|
218
|
+
"VerificationListItem",
|
|
219
|
+
"VerificationListResponse",
|
|
220
|
+
"VerificationDetail",
|
|
221
|
+
"VerificationDayStats",
|
|
222
|
+
"VerificationQuota",
|
|
223
|
+
"VerificationStats",
|
|
224
|
+
"DeleteVerificationResponse",
|
|
225
|
+
# Skill sync types
|
|
226
|
+
"SkillFinding",
|
|
227
|
+
"SkillScanFile",
|
|
228
|
+
"SkillIngestResponse",
|
|
229
|
+
"SyncedSkillScan",
|
|
230
|
+
"SkillScanListResponse",
|
|
231
|
+
# Threat types
|
|
232
|
+
"ThreatReportResponse",
|
|
233
|
+
# Incident types
|
|
234
|
+
"IncidentEvent",
|
|
235
|
+
"IncidentStreamSummary",
|
|
236
|
+
"IncidentCoverage",
|
|
237
|
+
"IncidentReplayResponse",
|
|
238
|
+
# Recall types
|
|
239
|
+
"RecallMemory",
|
|
240
|
+
"RecallLinkedEntity",
|
|
241
|
+
"RecallScoreBreakdown",
|
|
242
|
+
"RecallResult",
|
|
243
|
+
"RecallExplainResponse",
|
|
244
|
+
# Sync types
|
|
245
|
+
"SyncHealth",
|
|
246
|
+
"SyncMemory",
|
|
247
|
+
"SyncPushResponse",
|
|
248
|
+
"SyncedMemory",
|
|
249
|
+
"SyncLatestDeviceSync",
|
|
250
|
+
"SyncDeviceHealth",
|
|
251
|
+
"SyncHealthSummary",
|
|
252
|
+
"SyncMemorySummary",
|
|
253
|
+
"SyncedMemoriesResponse",
|
|
254
|
+
"SyncGraphEntity",
|
|
255
|
+
"SyncGraphTriple",
|
|
256
|
+
"SyncMemoryEntityLink",
|
|
257
|
+
"SyncGraphResponse",
|
|
258
|
+
# License types
|
|
259
|
+
"LicenseInfo",
|
|
260
|
+
"RegenerateLicenseResponse",
|
|
261
|
+
# Audit Iron Dome & export types
|
|
262
|
+
"IronDomeTopDevice",
|
|
263
|
+
"IronDomeStats",
|
|
264
|
+
"IronDomeEvent",
|
|
265
|
+
"IronDomeEventsResponse",
|
|
266
|
+
"AuditIngestEntry",
|
|
267
|
+
"AuditIngestResponse",
|
|
268
|
+
"ExportManifest",
|
|
269
|
+
"ExportManifestListResponse",
|
|
270
|
+
"ManifestVerification",
|
|
271
|
+
"ExportManifestDetail",
|
|
272
|
+
"ExportVerifyResult",
|
|
273
|
+
"ExportVerifyExpected",
|
|
274
|
+
"ExportVerifyResponse",
|
|
275
|
+
"ExportVerificationEvent",
|
|
276
|
+
"ExportVerificationListResponse",
|
|
157
277
|
]
|
|
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import dataclasses
|
|
10
10
|
import re
|
|
11
|
+
from collections.abc import Callable
|
|
11
12
|
from typing import Any, TypeVar, get_type_hints
|
|
12
13
|
|
|
13
14
|
import httpx
|
|
@@ -21,6 +22,7 @@ from shieldcortex.errors import (
|
|
|
21
22
|
ShieldCortexError,
|
|
22
23
|
ValidationError,
|
|
23
24
|
)
|
|
25
|
+
from shieldcortex.types import AuditExportHeaders
|
|
24
26
|
|
|
25
27
|
T = TypeVar("T")
|
|
26
28
|
|
|
@@ -54,12 +56,12 @@ _QUERY_ALIASES: dict[str, str] = {
|
|
|
54
56
|
# ── Serialisation ─────────────────────────────────────────────────────────────
|
|
55
57
|
|
|
56
58
|
|
|
57
|
-
def
|
|
58
|
-
"""
|
|
59
|
+
def _serialize(obj: Any, key_of: Callable[[dataclasses.Field[Any]], str]) -> Any:
|
|
60
|
+
"""Shared serialisation traversal.
|
|
59
61
|
|
|
60
|
-
-
|
|
62
|
+
- Wire keys are derived per field by ``key_of``
|
|
61
63
|
- None values are omitted
|
|
62
|
-
- Nested dataclasses are recursed
|
|
64
|
+
- Nested dataclasses, lists and dicts are recursed
|
|
63
65
|
"""
|
|
64
66
|
if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
|
|
65
67
|
result: dict[str, Any] = {}
|
|
@@ -67,16 +69,44 @@ def serialize(obj: Any) -> Any:
|
|
|
67
69
|
value = getattr(obj, f.name)
|
|
68
70
|
if value is None:
|
|
69
71
|
continue
|
|
70
|
-
|
|
71
|
-
result[api_key] = serialize(value)
|
|
72
|
+
result[key_of(f)] = _serialize(value, key_of)
|
|
72
73
|
return result
|
|
73
74
|
if isinstance(obj, list):
|
|
74
|
-
return [
|
|
75
|
+
return [_serialize(item, key_of) for item in obj]
|
|
75
76
|
if isinstance(obj, dict):
|
|
76
|
-
return {k:
|
|
77
|
+
return {k: _serialize(v, key_of) for k, v in obj.items()}
|
|
77
78
|
return obj
|
|
78
79
|
|
|
79
80
|
|
|
81
|
+
def _camel_key(f: dataclasses.Field[Any]) -> str:
|
|
82
|
+
return _to_camel(f.name)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _snake_wire_key(f: dataclasses.Field[Any]) -> str:
|
|
86
|
+
return str(f.metadata.get("wire", f.name))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def serialize(obj: Any) -> Any:
|
|
90
|
+
"""Convert a dataclass (or primitive) to an API-compatible dict.
|
|
91
|
+
|
|
92
|
+
- Dataclass fields are converted to camelCase
|
|
93
|
+
- None values are omitted
|
|
94
|
+
- Nested dataclasses are recursed
|
|
95
|
+
"""
|
|
96
|
+
return _serialize(obj, _camel_key)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def serialize_snake(obj: Any) -> Any:
|
|
100
|
+
"""Convert a dataclass (or primitive) to a snake_case wire dict.
|
|
101
|
+
|
|
102
|
+
Unlike :func:`serialize`, field names are kept snake_case (the house wire
|
|
103
|
+
convention). Known camelCase warts (e.g. skills findings[].matchedText)
|
|
104
|
+
are declared per field via ``metadata={"wire": ...}`` in types.py.
|
|
105
|
+
None values are omitted; nested dataclasses are recursed.
|
|
106
|
+
"""
|
|
107
|
+
return _serialize(obj, _snake_wire_key)
|
|
108
|
+
|
|
109
|
+
|
|
80
110
|
def serialize_query(obj: Any) -> dict[str, str]:
|
|
81
111
|
"""Convert a dataclass to query parameter dict (string values, no Nones)."""
|
|
82
112
|
if not dataclasses.is_dataclass(obj) or isinstance(obj, type):
|
|
@@ -223,6 +253,36 @@ def raise_for_status(response: httpx.Response) -> None:
|
|
|
223
253
|
raise ShieldCortexError(f"API error: {status}", status, body)
|
|
224
254
|
|
|
225
255
|
|
|
256
|
+
# ── Audit export integrity headers ────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def parse_export_headers(headers: httpx.Headers) -> AuditExportHeaders:
|
|
260
|
+
"""Parse the ``X-ShieldCortex-Export-*`` integrity headers.
|
|
261
|
+
|
|
262
|
+
Mirrors the TS SDK exactly: absent ``sha256``/``signature``/
|
|
263
|
+
``manifest_id`` stay ``None`` (never ``""``) — the export is
|
|
264
|
+
unverifiable, and a ``""`` manifest_id would build a malformed
|
|
265
|
+
manifest URL; ``count`` is ``None`` when absent, empty, or
|
|
266
|
+
non-numeric; the remaining fields default to ``""`` / ``False``.
|
|
267
|
+
"""
|
|
268
|
+
count_header = headers.get("X-ShieldCortex-Export-Count")
|
|
269
|
+
count: int | None = None
|
|
270
|
+
if count_header is not None:
|
|
271
|
+
try:
|
|
272
|
+
count = int(count_header)
|
|
273
|
+
except ValueError:
|
|
274
|
+
count = None
|
|
275
|
+
return AuditExportHeaders(
|
|
276
|
+
sha256=headers.get("X-ShieldCortex-Export-SHA256"),
|
|
277
|
+
count=count,
|
|
278
|
+
generated_at=headers.get("X-ShieldCortex-Export-Generated-At", ""),
|
|
279
|
+
manifest_id=headers.get("X-ShieldCortex-Export-Manifest-Id"),
|
|
280
|
+
signature=headers.get("X-ShieldCortex-Export-Signature"),
|
|
281
|
+
signature_algorithm=headers.get("X-ShieldCortex-Export-Signature-Alg", ""),
|
|
282
|
+
manifest_persisted=headers.get("X-ShieldCortex-Export-Manifest-Persisted") == "1",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
226
286
|
# ── Headers ───────────────────────────────────────────────────────────────────
|
|
227
287
|
|
|
228
288
|
|