Skip to main content

Python SDK for SanctumAI — secure credential management for AI agents

Project description

SanctumAI Python SDK

PyPI version Python License: MIT

Python SDK for SanctumAI — a local-first credential vault for AI agents.

Your agent authenticates with the vault, then uses credentials without ever seeing them. Keys never enter agent memory.

Install

pip install sanctum-ai

Requires Python 3.9+.

Quick Start

from sanctum_ai import SanctumClient

with SanctumClient("my-agent") as client:
    # Make an API call — agent never sees the key
    response = client.use_credential("openai/api-key", "http_request", {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "headers": {"Content-Type": "application/json"},
        "body": '{"model": "gpt-4", "messages": [{"role": "user", "content": "Hello"}]}',
        "header_type": "bearer",
    })
    print(response["status"])   # 200
    print(response["body"])     # {"choices": [...]}

The agent sent a request through the vault. The API key was injected server-side — it never existed in your process.

Use Don't Retrieve

This is the core idea. Instead of retrieving a secret and using it yourself, you tell the vault what to do with the secret. The vault does it and returns the result.

Proxy an HTTP Request

The most common operation. The vault injects the credential into the request and makes it on your behalf:

# Call any API through the vault
result = client.use_credential("openai/api-key", "http_request", {
    "method": "POST",
    "url": "https://api.openai.com/v1/chat/completions",
    "headers": {"Content-Type": "application/json"},
    "body": '{"model": "gpt-4", "messages": [{"role": "user", "content": "Hello"}]}',
    "header_type": "bearer",  # bearer | api_key | basic | custom
})
# Returns: {"status": 200, "headers": {...}, "body": "..."}

Supported header_type values:

Type Header
bearer Authorization: Bearer <secret>
api_key X-API-Key: <secret>
basic Authorization: Basic <base64>
custom You specify the header name via header_name param

Get an HTTP Header

If you need to make the request yourself (e.g., streaming), get just the header:

result = client.use_credential("github/token", "http_header", {
    "header_type": "bearer",
})
# Returns: {"header_name": "Authorization", "header_value": "Bearer ghp_..."}

# Use it in your own request
import urllib.request
req = urllib.request.Request("https://api.github.com/user")
req.add_header(result["header_name"], result["header_value"])

Sign Data

HMAC-sign a payload without exposing the signing key:

result = client.use_credential("webhook/secret", "sign", {
    "algorithm": "hmac-sha256",
    "data": "payload-to-sign",
})
# Returns: {"signature": "base64-encoded-signature"}

Encrypt and Decrypt

# Encrypt
encrypted = client.use_credential("encryption/key", "encrypt", {
    "data": "sensitive data",
})
# Returns: {"ciphertext": "..."}

# Decrypt
decrypted = client.use_credential("encryption/key", "decrypt", {
    "data": encrypted["ciphertext"],
})
# Returns: {"plaintext": "sensitive data"}

When to Use retrieve Instead

Sometimes you need the raw secret (e.g., passing it to a library that manages its own connections). That's fine — retrieve is still available:

api_key = client.retrieve("openai/api-key")
# Lease is auto-tracked and released when the client closes

But prefer use_credential whenever possible. It's safer.

Connecting

# Unix socket (default: ~/.sanctum/vault.sock)
with SanctumClient("my-agent") as client:
    ...

# TCP connection
with SanctumClient("my-agent", host="127.0.0.1", port=7600) as client:
    ...

# Custom socket path
with SanctumClient("my-agent", socket_path="/tmp/sanctum.sock") as client:
    ...

# Manual connect/close
client = SanctumClient("my-agent", host="127.0.0.1", port=7600)
client.connect()
# ... do work ...
client.close()

The client authenticates automatically on connect using Ed25519 challenge-response. Keys are loaded from ~/.sanctum/keys/<agent_name>.key by default, or specify key_path explicitly.

API Reference

SanctumClient(agent_name, *, socket_path=None, host=None, port=None, key_path=None, passphrase=None)

Parameter Description
agent_name Agent identity for authentication
socket_path Unix socket path (default: ~/.sanctum/vault.sock)
host / port TCP connection (default port: 7600)
key_path Path to Ed25519 key file (default: ~/.sanctum/keys/{agent_name}.key)
passphrase Passphrase for encrypted .key.enc files

Methods

Method Returns Description
connect(target=None) SanctumClient Connect and authenticate
use_credential(path, operation, params=None) dict Use a credential without seeing it
retrieve(path, *, ttl=None) str Retrieve credential value (lease auto-tracked)
retrieve_raw(path, *, ttl=None) dict Full result with lease_id, ttl, etc.
list() list List accessible credentials
release_lease(lease_id) None Explicitly release a lease
close() None Release all leases and disconnect

Error Handling

All exceptions inherit from VaultError and carry structured context:

from sanctum_ai import SanctumClient
from sanctum_ai.exceptions import (
    VaultError, AuthError, AccessDenied,
    CredentialNotFound, VaultLocked, LeaseExpired,
    RateLimited, SessionExpired,
)

with SanctumClient("my-agent") as client:
    try:
        result = client.use_credential("openai/api-key", "http_request", {
            "method": "POST",
            "url": "https://api.openai.com/v1/chat/completions",
            "headers": {"Content-Type": "application/json"},
            "body": '{"model": "gpt-4", "messages": [{"role": "user", "content": "Hi"}]}',
            "header_type": "bearer",
        })
    except AccessDenied as e:
        print(f"No access: {e.detail}")
        print(f"Suggestion: {e.suggestion}")
    except CredentialNotFound:
        print("Credential path not found")
    except AuthError:
        print("Authentication failed — check your Ed25519 key")
    except VaultLocked:
        print("Vault is sealed — an operator needs to unseal it")
    except VaultError as e:
        print(f"[{e.code}] {e.detail}")

Exception Reference

Exception Error Code When
VaultError Base exception
AuthError AUTH_FAILED Bad key or agent not registered
AccessDenied ACCESS_DENIED Agent lacks permission for this credential
CredentialNotFound CREDENTIAL_NOT_FOUND Path doesn't exist in the vault
VaultLocked VAULT_LOCKED Vault is sealed
LeaseExpired LEASE_EXPIRED Lease timed out
RateLimited RATE_LIMITED Too many requests
SessionExpired SESSION_EXPIRED Re-authenticate needed

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Write tests for new functionality
  4. Ensure all tests pass (pytest)
  5. Submit a pull request

License

MIT — see LICENSE.

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sanctum_ai-0.4.0.tar.gz (9.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sanctum_ai-0.4.0-py3-none-any.whl (10.0 kB view details)

Uploaded Python 3

File details

Details for the file sanctum_ai-0.4.0.tar.gz.

File metadata

  • Download URL: sanctum_ai-0.4.0.tar.gz
  • Upload date:
  • Size: 9.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for sanctum_ai-0.4.0.tar.gz
Algorithm Hash digest
SHA256 a6d85497b566b8e407a7937c9f861924c67a7ecf1d191a14fc887ab2c90ee23f
MD5 bc7be6dfb0fd1d3888f064df30f8ae5a
BLAKE2b-256 dc6244d7ccea361fc93d0d7ca1dfe983b46ad7dd397510e439453dda0a18341a

See more details on using hashes here.

File details

Details for the file sanctum_ai-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: sanctum_ai-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 10.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for sanctum_ai-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d3cc18bb2e3316a1676a96792a6f8a466cd73606a720402f38277c226a078bef
MD5 0623348dc14513a2afe4c3dd00432315
BLAKE2b-256 f482c30adf25d9fa4fdb7ce56d1698bcb77448ef4429d0eb18afa8defeb4aac0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page