glacis 0.1.0__py3-none-any.whl → 0.1.1__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.
glacis/__init__.py CHANGED
@@ -33,7 +33,7 @@ Streaming Example:
33
33
  >>> session = await StreamingSession.start(glacis, {
34
34
  ... "service_id": "voice-assistant",
35
35
  ... "operation_type": "completion",
36
- ... "session_do_url": "https://session-do.glacis.dev",
36
+ ... "session_do_url": "https://session-do.glacis.io",
37
37
  ... })
38
38
  >>> await session.attest_chunk(input=audio_chunk, output=transcript)
39
39
  >>> receipt = await session.end(metadata={"duration": "00:05:23"})
glacis/__main__.py CHANGED
@@ -9,12 +9,13 @@ import argparse
9
9
  import json
10
10
  import sys
11
11
  from pathlib import Path
12
+ from typing import Any, Union
12
13
 
13
14
  from glacis import Glacis
14
- from glacis.models import AttestReceipt, OfflineAttestReceipt
15
+ from glacis.models import AttestReceipt, OfflineAttestReceipt, OfflineVerifyResult, VerifyResult
15
16
 
16
17
 
17
- def verify_command(args):
18
+ def verify_command(args: argparse.Namespace) -> None:
18
19
  """Verify a receipt file."""
19
20
  receipt_path = Path(args.receipt)
20
21
 
@@ -24,21 +25,29 @@ def verify_command(args):
24
25
 
25
26
  try:
26
27
  with open(receipt_path) as f:
27
- data = json.load(f)
28
+ data: dict[str, Any] = json.load(f)
28
29
  except json.JSONDecodeError as e:
29
30
  print(f"Error: Invalid JSON: {e}", file=sys.stderr)
30
31
  sys.exit(1)
31
32
 
32
- # Determine receipt type
33
+ # Determine receipt type and verify
34
+ receipt: Union[AttestReceipt, OfflineAttestReceipt]
35
+ result: Union[VerifyResult, OfflineVerifyResult]
36
+
33
37
  if data.get("attestation_id", "").startswith("oatt_"):
34
- receipt = OfflineAttestReceipt(**data)
35
- glacis = Glacis(mode="offline")
38
+ offline_receipt = OfflineAttestReceipt(**data)
39
+ # For verification, we need a signing_seed but it's not used
40
+ # since we verify using the public key from the receipt
41
+ dummy_seed = bytes(32) # All zeros - only used to satisfy constructor
42
+ glacis = Glacis(mode="offline", signing_seed=dummy_seed)
43
+ result = glacis.verify(offline_receipt)
44
+ receipt = offline_receipt
36
45
  else:
37
- receipt = AttestReceipt(**data)
38
- glacis = Glacis(mode="online")
39
-
40
- # Verify
41
- result = glacis.verify(receipt)
46
+ online_receipt = AttestReceipt(**data)
47
+ # Online verification doesn't require API key for public receipts
48
+ glacis = Glacis(api_key="verify_only")
49
+ result = glacis.verify(online_receipt)
50
+ receipt = online_receipt
42
51
 
43
52
  # Output
44
53
  print(f"Receipt: {receipt.attestation_id}")
@@ -47,17 +56,22 @@ def verify_command(args):
47
56
 
48
57
  if result.valid:
49
58
  print("Status: VALID")
50
- print(f" Signature: {'PASS' if result.signature_valid else 'FAIL'}")
51
- if hasattr(result, "proof_valid") and result.proof_valid is not None:
52
- print(f" Merkle proof: {'PASS' if result.proof_valid else 'FAIL'}")
59
+ # Get signature validity based on result type
60
+ if isinstance(result, OfflineVerifyResult):
61
+ sig_valid = result.signature_valid
62
+ else:
63
+ sig_valid = result.verification.signature_valid
64
+ print(f" Signature: {'PASS' if sig_valid else 'FAIL'}")
65
+ if isinstance(result, VerifyResult):
66
+ print(f" Merkle proof: {'PASS' if result.verification.proof_valid else 'FAIL'}")
53
67
  else:
54
68
  print("Status: INVALID")
55
- if hasattr(result, "error"):
69
+ if result.error:
56
70
  print(f" Error: {result.error}")
57
71
  sys.exit(1)
58
72
 
59
73
 
60
- def main():
74
+ def main() -> None:
61
75
  parser = argparse.ArgumentParser(
62
76
  prog="glacis", description="Glacis CLI - Cryptographic attestation for AI systems"
63
77
  )
glacis/client.py CHANGED
@@ -7,7 +7,7 @@ using RFC 8785 canonical JSON + SHA-256 - the actual payload never leaves
7
7
  your infrastructure.
8
8
 
9
9
  Supports two modes:
10
- - Online (default): Sends attestations to api.glacis.dev for witnessing
10
+ - Online (default): Sends attestations to api.glacis.io for witnessing
11
11
  - Offline: Signs attestations locally using Ed25519 via WASM
12
12
 
13
13
  Example (online):
@@ -44,9 +44,7 @@ from glacis.crypto import hash_payload
44
44
  from glacis.models import (
45
45
  AttestReceipt,
46
46
  GlacisApiError,
47
- GlacisConfig,
48
47
  GlacisRateLimitError,
49
- LogQueryParams,
50
48
  LogQueryResult,
51
49
  OfflineAttestReceipt,
52
50
  OfflineVerifyResult,
@@ -67,7 +65,7 @@ class GlacisMode(str, Enum):
67
65
 
68
66
  logger = logging.getLogger("glacis")
69
67
 
70
- DEFAULT_BASE_URL = "https://api.glacis.dev"
68
+ DEFAULT_BASE_URL = "https://api.glacis.io"
71
69
  DEFAULT_TIMEOUT = 30.0
72
70
  DEFAULT_MAX_RETRIES = 3
73
71
  DEFAULT_BASE_DELAY = 1.0
@@ -84,7 +82,7 @@ class Glacis:
84
82
 
85
83
  Args:
86
84
  api_key: API key for authenticated endpoints (required for online mode)
87
- base_url: Base URL for the API (default: https://api.glacis.dev)
85
+ base_url: Base URL for the API (default: https://api.glacis.io)
88
86
  debug: Enable debug logging
89
87
  timeout: Request timeout in seconds
90
88
  max_retries: Maximum number of retries for transient errors
@@ -226,7 +224,7 @@ class Glacis:
226
224
  """Create a server-witnessed attestation."""
227
225
  self._debug(f"Attesting (online): service_id={service_id}, hash={payload_hash[:16]}...")
228
226
 
229
- body = {
227
+ body: dict[str, Any] = {
230
228
  "serviceId": service_id,
231
229
  "operationType": operation_type,
232
230
  "payloadHash": payload_hash,
@@ -356,37 +354,10 @@ class Glacis:
356
354
  self._debug(f"Verifying (offline): {receipt.attestation_id}")
357
355
 
358
356
  try:
359
- # Reconstruct the attestation payload that was signed
360
- # We need to rebuild the exact JSON that was signed
361
- attestation_payload = {
362
- "version": 1,
363
- "serviceId": receipt.service_id,
364
- "operationType": receipt.operation_type,
365
- "payloadHash": receipt.payload_hash,
366
- "timestampMs": receipt.timestamp.replace("Z", "").replace("-", "").replace(":", "").replace("T", ""),
367
- "mode": "offline",
368
- }
369
-
370
- # Note: We stored the timestamp in ISO format, but signed with ms timestamp
371
- # For verification, we need the original signed payload
372
- # Since we can't perfectly reconstruct it, we'll verify using the stored signature
373
- # against the public key directly
374
-
375
- # Get the public key bytes
376
- public_key = bytes.fromhex(receipt.public_key)
377
-
378
- # Decode the signature
379
- import base64
380
-
381
- signature = base64.b64decode(receipt.signature)
382
-
383
- # We need to reconstruct the exact payload that was signed
384
- # The payload was: {"mode":"offline","operationType":"...","payloadHash":"...","serviceId":"...","timestampMs":"...","version":1}
385
- # Since we don't store the exact timestampMs, we need a different approach
386
-
387
- # For now, we'll trust that if the signature was created by us with the same key,
388
- # and the receipt exists in our database, it's valid
389
- # A more robust solution would store the signed payload or timestampMs
357
+ # For offline verification, we verify the public key matches our signing seed.
358
+ # Full signature verification would require storing the original timestampMs,
359
+ # which we don't currently do. A more robust solution would store the
360
+ # signed payload or timestampMs for later verification.
390
361
 
391
362
  # Use WASM to verify if we have the runtime
392
363
  if self._wasm_runtime and self._signing_seed:
@@ -530,6 +501,7 @@ class Glacis:
530
501
  headers: Optional[dict[str, str]] = None,
531
502
  ) -> dict[str, Any]:
532
503
  """Make a request with exponential backoff retry."""
504
+ assert self._client is not None, "HTTP client not initialized"
533
505
  last_error: Optional[Exception] = None
534
506
 
535
507
  for attempt in range(self.max_retries + 1):
@@ -543,7 +515,8 @@ class Glacis:
543
515
  )
544
516
 
545
517
  if response.is_success:
546
- return response.json()
518
+ result: dict[str, Any] = response.json()
519
+ return result
547
520
 
548
521
  if response.status_code == 429:
549
522
  retry_after = response.headers.get("Retry-After")
@@ -597,7 +570,7 @@ class AsyncGlacis:
597
570
 
598
571
  Args:
599
572
  api_key: API key for authenticated endpoints
600
- base_url: Base URL for the API (default: https://api.glacis.dev)
573
+ base_url: Base URL for the API (default: https://api.glacis.io)
601
574
  debug: Enable debug logging
602
575
  timeout: Request timeout in seconds
603
576
  max_retries: Maximum number of retries for transient errors
@@ -679,7 +652,7 @@ class AsyncGlacis:
679
652
 
680
653
  self._debug(f"Attesting: service_id={service_id}, hash={payload_hash[:16]}...")
681
654
 
682
- body = {
655
+ body: dict[str, Any] = {
683
656
  "serviceId": service_id,
684
657
  "operationType": operation_type,
685
658
  "payloadHash": payload_hash,
@@ -792,6 +765,7 @@ class AsyncGlacis:
792
765
  """Make a request with exponential backoff retry."""
793
766
  import asyncio
794
767
 
768
+ assert self._client is not None, "HTTP client not initialized"
795
769
  last_error: Optional[Exception] = None
796
770
 
797
771
  for attempt in range(self.max_retries + 1):
@@ -805,7 +779,8 @@ class AsyncGlacis:
805
779
  )
806
780
 
807
781
  if response.is_success:
808
- return response.json()
782
+ result: dict[str, Any] = response.json()
783
+ return result
809
784
 
810
785
  if response.status_code == 429:
811
786
  retry_after = response.headers.get("Retry-After")
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
23
23
  def attested_anthropic(
24
24
  glacis_api_key: str,
25
25
  anthropic_api_key: Optional[str] = None,
26
- glacis_base_url: str = "https://api.glacis.dev",
26
+ glacis_base_url: str = "https://api.glacis.io",
27
27
  service_id: str = "anthropic",
28
28
  debug: bool = False,
29
29
  **anthropic_kwargs: Any,
@@ -137,7 +137,7 @@ def attested_anthropic(
137
137
  def attested_async_anthropic(
138
138
  glacis_api_key: str,
139
139
  anthropic_api_key: Optional[str] = None,
140
- glacis_base_url: str = "https://api.glacis.dev",
140
+ glacis_base_url: str = "https://api.glacis.io",
141
141
  service_id: str = "anthropic",
142
142
  debug: bool = False,
143
143
  **anthropic_kwargs: Any,
@@ -52,7 +52,7 @@ def get_last_receipt() -> Optional[Union["AttestReceipt", "OfflineAttestReceipt"
52
52
  def attested_openai(
53
53
  glacis_api_key: Optional[str] = None,
54
54
  openai_api_key: Optional[str] = None,
55
- glacis_base_url: str = "https://api.glacis.dev",
55
+ glacis_base_url: str = "https://api.glacis.io",
56
56
  service_id: str = "openai",
57
57
  debug: bool = False,
58
58
  offline: bool = False,
glacis/models.py CHANGED
@@ -15,7 +15,7 @@ class GlacisConfig(BaseModel):
15
15
 
16
16
  api_key: str = Field(..., description="API key (glsk_live_xxx or glsk_test_xxx)")
17
17
  base_url: str = Field(
18
- default="https://api.glacis.dev", description="Base URL for the API"
18
+ default="https://api.glacis.io", description="Base URL for the API"
19
19
  )
20
20
  debug: bool = Field(default=False, description="Enable debug logging")
21
21
  timeout: float = Field(default=30.0, description="Request timeout in seconds")
glacis/storage.py CHANGED
@@ -11,7 +11,7 @@ import json
11
11
  import sqlite3
12
12
  from datetime import datetime
13
13
  from pathlib import Path
14
- from typing import TYPE_CHECKING, Optional
14
+ from typing import TYPE_CHECKING, Any, Optional
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  from glacis.models import OfflineAttestReceipt
@@ -119,7 +119,7 @@ class ReceiptStorage:
119
119
  receipt: "OfflineAttestReceipt",
120
120
  input_preview: Optional[str] = None,
121
121
  output_preview: Optional[str] = None,
122
- metadata: Optional[dict] = None,
122
+ metadata: Optional[dict[str, Any]] = None,
123
123
  ) -> None:
124
124
  """
125
125
  Store an offline receipt.
@@ -242,7 +242,7 @@ class ReceiptStorage:
242
242
  cursor = conn.cursor()
243
243
 
244
244
  query = "SELECT * FROM offline_receipts WHERE 1=1"
245
- params: list = []
245
+ params: list[Any] = []
246
246
 
247
247
  if service_id:
248
248
  query += " AND service_id = ?"
@@ -326,6 +326,11 @@ class ReceiptStorage:
326
326
  """Context manager entry."""
327
327
  return self
328
328
 
329
- def __exit__(self, exc_type, exc_val, exc_tb) -> None:
329
+ def __exit__(
330
+ self,
331
+ exc_type: Optional[type[BaseException]],
332
+ exc_val: Optional[BaseException],
333
+ exc_tb: Optional[Any],
334
+ ) -> None:
330
335
  """Context manager exit."""
331
336
  self.close()
glacis/streaming.py CHANGED
@@ -12,7 +12,7 @@ Example:
12
12
  >>> session = await StreamingSession.start(glacis, {
13
13
  ... "service_id": "voice-assistant",
14
14
  ... "operation_type": "completion",
15
- ... "session_do_url": "https://session-do.glacis.dev",
15
+ ... "session_do_url": "https://session-do.glacis.io",
16
16
  ... })
17
17
  >>>
18
18
  >>> await session.attest_chunk({"input": audio_chunk, "output": transcript})
@@ -26,8 +26,8 @@ Context Manager:
26
26
 
27
27
  import asyncio
28
28
  import uuid
29
- from dataclasses import dataclass, field
30
- from typing import Any, Callable, Optional, TypedDict
29
+ from dataclasses import dataclass
30
+ from typing import Any, Optional, TypedDict
31
31
 
32
32
  import httpx
33
33
 
@@ -73,8 +73,6 @@ class StreamingSession:
73
73
  api_key: str,
74
74
  session_token: str,
75
75
  ):
76
- from glacis import AsyncGlacis
77
-
78
76
  self._glacis = glacis
79
77
  self._session_id = session_id
80
78
  self._session_do_url = session_do_url.rstrip("/")
@@ -315,7 +313,8 @@ class StreamingSession:
315
313
  f"Failed to get status: {error.get('error', response.status_code)}"
316
314
  )
317
315
 
318
- return response.json()
316
+ result: dict[str, Any] = response.json()
317
+ return result
319
318
 
320
319
  async def __aenter__(self) -> "StreamingSession":
321
320
  return self
glacis/wasm_runtime.py CHANGED
@@ -7,9 +7,9 @@ Falls back to PyNaCl if WASM runtime fails.
7
7
  This provides Ed25519 signing and verification without reimplementing crypto in Python.
8
8
  """
9
9
 
10
+ import ctypes
10
11
  import hashlib
11
12
  import json
12
- import ctypes
13
13
  from base64 import b64encode
14
14
  from pathlib import Path
15
15
  from typing import Any, Optional
@@ -30,8 +30,8 @@ class PyNaClRuntime:
30
30
 
31
31
  def __init__(self) -> None:
32
32
  try:
33
- import nacl.signing
34
33
  import nacl.encoding
34
+ import nacl.signing
35
35
  self._nacl_signing = nacl.signing
36
36
  self._nacl_encoding = nacl.encoding
37
37
  except ImportError:
@@ -172,12 +172,13 @@ class WasmRuntime:
172
172
  else:
173
173
  # Use PyNaCl by default for better compatibility
174
174
  cls._instance = PyNaClRuntime() # type: ignore[assignment]
175
+ assert cls._instance is not None
175
176
  return cls._instance
176
177
 
177
178
  def _write_bytes(self, data: bytes) -> int:
178
179
  """Allocate WASM memory and write bytes, returning the pointer."""
179
180
  size = len(data)
180
- ptr = self._alloc(self._store, size)
181
+ ptr: int = self._alloc(self._store, size)
181
182
  if ptr == 0:
182
183
  raise WasmRuntimeError("Failed to allocate WASM memory")
183
184
 
@@ -399,7 +400,7 @@ class WasmRuntime:
399
400
  sig_ptr = self._write_bytes(signature)
400
401
 
401
402
  try:
402
- result = self._ed25519_verify(
403
+ result: int = self._ed25519_verify(
403
404
  self._store, pubkey_ptr, 32, msg_ptr, len(message), sig_ptr, 64
404
405
  )
405
406
  return result == 0
@@ -433,7 +434,8 @@ class WasmRuntime:
433
434
  # Allocate generous output buffer (input + base64 sig + JSON wrapper)
434
435
  out_cap = len(json_bytes) + 200
435
436
  out_ptr = self._alloc(self._store, out_cap)
436
- out_len_ptr = self._alloc(self._store, 8) # size_t on wasm32 is 4 bytes, but use 8 for safety
437
+ # size_t on wasm32 is 4 bytes, but use 8 for safety
438
+ out_len_ptr = self._alloc(self._store, 8)
437
439
 
438
440
  if out_ptr == 0 or out_len_ptr == 0:
439
441
  self._free(seed_ptr, 32)
@@ -516,4 +518,5 @@ def sign_offline_attestation(
516
518
  attestation_json = json.dumps(attestation, separators=(",", ":"), sort_keys=True)
517
519
  signed_json = runtime.sign_attestation_json(signing_seed, attestation_json)
518
520
 
519
- return json.loads(signed_json)
521
+ result: dict[str, Any] = json.loads(signed_json)
522
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glacis
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: GLACIS SDK for Python - AI Compliance Attestation
5
5
  Project-URL: Homepage, https://glacis.io
6
6
  Project-URL: Documentation, https://docs.glacis.io/sdk/python
@@ -0,0 +1,16 @@
1
+ glacis/__init__.py,sha256=9TLZCbUyUQ0JpqHBNiCjvkgt9HbiRRy3B03GMvxdlQ4,2465
2
+ glacis/__main__.py,sha256=GTA84o2Lx5md1kvI80ATu9yNDGidYtW808_TqOmIJSE,2969
3
+ glacis/client.py,sha256=hM3dokL7HZz3E8ovndvd-uHui31Hz8afv8IFGs4aH4U,27414
4
+ glacis/crypto.py,sha256=DiHIMGMKV616wpba0n7Niuw4t-Zz_q5ij4a7m4HDDvk,3579
5
+ glacis/models.py,sha256=S-n-anzfXJL03lENluNL0cbiXfVsPEsKZyh-dPBH93U,9792
6
+ glacis/storage.py,sha256=tsmlG8bAq5daN1o6I9vmppKhstURm3jcO4_oLhOiyPg,10253
7
+ glacis/streaming.py,sha256=hAmR4XHQixLia6awnFfCaA9hZOtCq2b-pOzgaMd3YzQ,11260
8
+ glacis/wasm_runtime.py,sha256=P1dY20G2rHGGJBDKGA_lCRhkJuXuLycjw6mo5vuw02I,17452
9
+ glacis/integrations/__init__.py,sha256=BeLSA6eGkpBWAIbXPodgS42yD_GpQ-IuXhrgF2271Ps,353
10
+ glacis/integrations/anthropic.py,sha256=xJo0Dgx4Y7TK9cQPfEaBXygX0X2lZs0D_pfioZ_P6AM,6927
11
+ glacis/integrations/openai.py,sha256=VkLtu8iQTMiAAa2pX7byC3je8BjzYiv-8_1T7sq3J4E,6941
12
+ glacis/wasm/s3p_core_wasi.wasm,sha256=gGrYI6hd0VkgqCg1mLADzSJP1pEUekkS-3PKd5mdBJo,253289
13
+ glacis-0.1.1.dist-info/METADATA,sha256=lHHPaAjTfbWPtW9CqT9T7XaQlOAhdP4BTmqAD9QS5Js,9558
14
+ glacis-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
+ glacis-0.1.1.dist-info/licenses/LICENSE,sha256=B7g2sM9vz4NF1poTFNMil2cnkwMFel1qUnimyvYW1sw,10766
16
+ glacis-0.1.1.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- glacis/__init__.py,sha256=MCUcFXSMtHtPJKjzRe8cmP_5SfEy7XeQb2mv0qQcWww,2466
2
- glacis/__main__.py,sha256=VQcKiAwcmK5npWG5z5kw71_X6PFSnzKS21z6XS886lk,2103
3
- glacis/client.py,sha256=DZlyEQN9e6eCspb1pY_5UG_IYaT6uFKtLrfP5S4XwQA,28433
4
- glacis/crypto.py,sha256=DiHIMGMKV616wpba0n7Niuw4t-Zz_q5ij4a7m4HDDvk,3579
5
- glacis/models.py,sha256=J89KCe4vnhnDkUKLYn9uksYMCj-ZChIKGRYdOw6CM58,9793
6
- glacis/storage.py,sha256=1q7PCLLukHoVYp7AoAbpTxbMVQAY7RiKpNObn45mxvk,10123
7
- glacis/streaming.py,sha256=btBae4Q5fiTiQcPGk2tl9DNuoFGphO-T2M_r3x3GTFo,11278
8
- glacis/wasm_runtime.py,sha256=oy2C4sWb6klxGTb7jh2WxrfrG7pMxBXHmWVL7oTxcdo,17358
9
- glacis/integrations/__init__.py,sha256=BeLSA6eGkpBWAIbXPodgS42yD_GpQ-IuXhrgF2271Ps,353
10
- glacis/integrations/anthropic.py,sha256=AiUI-rYyJLI3jEaTrHQ5RyzHJI9BnmjoC_LMEnydikk,6929
11
- glacis/integrations/openai.py,sha256=zbGDmck2eBqoQlkAobn9gqVlYOE2uvp3qjg2JLUfMEs,6942
12
- glacis/wasm/s3p_core_wasi.wasm,sha256=gGrYI6hd0VkgqCg1mLADzSJP1pEUekkS-3PKd5mdBJo,253289
13
- glacis-0.1.0.dist-info/METADATA,sha256=RRRyPCHIuMX0c1MVxDU2DZhKDCXUPKSG7_6TVQm-7Kw,9558
14
- glacis-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
- glacis-0.1.0.dist-info/licenses/LICENSE,sha256=B7g2sM9vz4NF1poTFNMil2cnkwMFel1qUnimyvYW1sw,10766
16
- glacis-0.1.0.dist-info/RECORD,,
File without changes