SikkerKey Python SDK — read secrets with Ed25519 machine authentication
Project description
SikkerKey Python SDK
The official Python SDK for SikkerKey. Read-only access to secrets using Ed25519 machine authentication. Runs on persistent hosts (identity on disk) and serverless or ephemeral environments (in-memory bootstrap).
Installation
pip install sikkerkey
Requires Python 3.10+. Single dependency: cryptography (for Ed25519 signing).
Quick Start
from sikkerkey import SikkerKey
sk = SikkerKey("vault_abc123")
api_key = sk.get_secret("sk_stripe_key")
The SDK reads the machine identity from ~/.sikkerkey/vaults/<vault-id>/identity.json, signs every request with the machine's Ed25519 private key, and returns the decrypted value.
Client Creation
# Explicit vault ID
sk = SikkerKey("vault_abc123")
# Direct path to identity file
sk = SikkerKey("/etc/sikkerkey/vaults/vault_abc123/identity.json")
# Auto-detect from SIKKERKEY_IDENTITY env or single vault on disk
sk = SikkerKey()
Raises ConfigurationError if the identity is missing, the key can't be loaded, or multiple vaults exist without a specified vault ID.
Serverless (In-Memory Bootstrap)
On a long-lived host the SDK loads a persistent identity from disk. Serverless and other ephemeral or read-only-filesystem environments (AWS Lambda, Google Cloud Run, Fly.io, and similar) have no identity to persist. SikkerKey.bootstrap_in_memory() handles that case: it generates an Ed25519 keypair in memory, registers an ephemeral machine with an enrollment token, and returns a ready client. Nothing is written to disk.
import os
from sikkerkey import SikkerKey
sk = SikkerKey.bootstrap_in_memory(
os.environ["SIKKERKEY_VAULT_ID"],
os.environ["SIKKERKEY_ENROLLMENT_TOKEN"],
)
db_url = sk.get_secret("sk_db_prod")
Create an enrollment token in the dashboard and supply its plaintext plus your vault ID. The token only registers an ephemeral machine scoped to the policy you set (projects, secrets, lifetime); it cannot read secrets on its own.
Enrollment happens once, in the bootstrap_in_memory call. The returned client then behaves exactly like one loaded from disk: it signs each read with the in-memory key. The private key is gone when the process exits. The ephemeral machine lives for the lifetime set on the token; reading after it expires raises AuthenticationError, so size the token's machine lifetime to your workload. The common path is to read secrets at startup and hold the values.
Options
sk = SikkerKey.bootstrap_in_memory(
vault_id,
token,
hostname="worker-1", # defaults to $HOSTNAME, then "serverless"
name="batch-runner", # overridden if the token defines a name pattern
)
Provisioning the Token
When you create the enrollment token for a serverless or ephemeral deployment:
- Set a short machine lifetime (minutes). Each cold start mints a fresh ephemeral machine, and short-lived ones free their slot quickly as they expire.
- Set max-uses high enough for your cold-start and concurrency volume.
- Leave the source-CIDR restriction unset, since serverless egress IPs are dynamic.
- If the vault has an IP allowlist, make sure it permits the platform's egress or leave it off.
- Set a name pattern on the token (for example
worker-{uuid8}) so each machine gets a unique name. A name pattern takes precedence overname.
Each live ephemeral machine counts against your plan's machine limit until it expires.
Reading Secrets
Single Value
api_key = sk.get_secret("sk_stripe_prod")
Structured (Multiple Fields)
fields = sk.get_fields("sk_db_prod")
host = fields["host"] # "db.example.com"
password = fields["password"] # "hunter2"
Raises SecretStructureError if the secret value is not a JSON object.
Single Field
password = sk.get_field("sk_db_prod", "password")
Raises FieldNotFoundError if the field doesn't exist. The error message includes available field names.
Listing Secrets
# All secrets this machine can access
secrets = sk.list_secrets()
for s in secrets:
print(f"{s.id}: {s.name}")
# Secrets in a specific project
secrets = sk.list_secrets_by_project("proj_production")
Each SecretListItem has id, name, field_names (None for single-value), and project_id.
Export
# All secrets as a flat dict
env = sk.export()
# {"API_KEY": "sk-live-...", "DB_CREDS_HOST": "db.example.com", "DB_CREDS_PASSWORD": "s3cret"}
# Scoped to a project
env = sk.export("proj_production")
# Inject into environment
import os
os.environ.update(sk.export())
Structured secrets are flattened: SECRET_NAME_FIELD_NAME.
Watching for Changes
Watch secrets for real-time updates. When a secret is rotated, updated, or deleted, the callback fires with the new value. Polling happens on a background daemon thread - your application is never blocked.
from sikkerkey import WatchStatus
def on_change(event):
if event.status == WatchStatus.CHANGED:
print(f"New value: {event.value}")
# Structured secrets include parsed fields
print(f"Fields: {event.fields}")
elif event.status == WatchStatus.DELETED:
print("Secret was deleted")
elif event.status == WatchStatus.ACCESS_DENIED:
print("Access revoked")
elif event.status == WatchStatus.ERROR:
print(f"Error: {event.error}")
sk.watch("sk_db_password", on_change)
Practical Example
# Auto-rotate database credentials
def rotate_db(event):
if event.status == WatchStatus.CHANGED:
db.configure_credentials(
username=event.fields["username"],
password=event.fields["password"],
)
sk.watch("sk_db_credentials", rotate_db)
Poll Interval
The default poll interval is 15 seconds. The server enforces a minimum of 10 seconds.
sk.set_poll_interval(30) # seconds
Stop Watching
# Stop watching a specific secret
sk.unwatch("sk_db_password")
# Stop all watches and shut down polling
sk.close()
SikkerKey can be used as a context manager:
with SikkerKey("vault_abc123") as sk:
sk.watch("sk_api_key", on_change)
# ... application logic ...
# Automatically closed on exit
Multi-Vault
prod = SikkerKey("vault_a1b2c3")
staging = SikkerKey("vault_x9y8z7")
prod_key = prod.get_secret("sk_api_key")
staging_key = staging.get_secret("sk_api_key")
List Registered Vaults
vaults = SikkerKey.list_vaults()
# ["vault_a1b2c3", "vault_x9y8z7"]
Machine Info
sk.machine_id # "550e8400-e29b-41d4-a716-446655440000"
sk.machine_name # "api-server-1"
sk.vault_id # "vault_abc123"
sk.api_url # "https://api.sikkerkey.com"
Error Handling
from sikkerkey import SikkerKey, NotFoundError, AccessDeniedError, AuthenticationError
try:
secret = sk.get_secret("sk_nonexistent")
except NotFoundError:
# Secret doesn't exist
except AccessDeniedError:
# Machine not approved or no grant
except AuthenticationError:
# Invalid signature or unknown machine
Exception Hierarchy
SikkerKeyError
├── ConfigurationError — identity file missing, bad key, invalid config
├── SecretStructureError — secret is not a JSON object (get_fields)
├── FieldNotFoundError — field not in structured secret (get_field)
└── ApiError — HTTP error (has http_status attribute)
├── AuthenticationError — 401
├── AccessDeniedError — 403
├── NotFoundError — 404
├── ConflictError — 409
├── RateLimitedError — 429
└── ServerSealedError — 503
Identity Resolution
- Explicit path — starts with
/or containsidentity.json - Vault ID — looks up
~/.sikkerkey/vaults/{vault_id}/identity.json SIKKERKEY_IDENTITYenv — path to identity file- Auto-detect — single vault on disk
The vault_ prefix is added automatically if not present.
Environment Variables
| Variable | Description |
|---|---|
SIKKERKEY_IDENTITY |
Path to identity.json — overrides vault lookup |
SIKKERKEY_HOME |
Base config directory (default: ~/.sikkerkey) |
SIKKERKEY_API_URL |
Override the API base URL. Local development only (default: https://api.sikkerkey.com) |
Retry Behavior
429 and 503 responses are retried up to 3 times with exponential backoff (1s, 2s, 4s). Each retry uses a fresh timestamp and nonce. Network errors are also retried.
Authentication
Every request includes Ed25519-signed headers: X-Machine-Id, X-Timestamp, X-Nonce, X-Signature. HTTPS enforced for non-localhost. 15-second timeout.
Method Reference
| Method | Returns | Description |
|---|---|---|
SikkerKey(vault_or_path?) |
SikkerKey |
Create client from disk identity |
SikkerKey.bootstrap_in_memory(vault_id, token, *, hostname?, name?) |
SikkerKey |
Memory-only serverless bootstrap (classmethod) |
SikkerKey.list_vaults() |
list[str] |
List registered vault IDs (static) |
get_secret(secret_id) |
str |
Read a secret value |
get_fields(secret_id) |
dict[str, str] |
Read structured secret |
get_field(secret_id, field) |
str |
Read single field |
list_secrets() |
list[SecretListItem] |
List all accessible secrets |
list_secrets_by_project(project_id) |
list[SecretListItem] |
List secrets in a project |
export(project_id?) |
dict[str, str] |
Export as env map |
watch(secret_id, callback) |
None |
Watch a secret for changes |
unwatch(secret_id) |
None |
Stop watching a secret |
set_poll_interval(seconds) |
None |
Set poll interval (min 10s) |
close() |
None |
Stop all watches, shut down polling |
Dependencies
cryptography>=41.0— Ed25519 key loading and signing
All other functionality uses Python stdlib: urllib, json, hashlib, os, pathlib.
Documentation
License
MIT — see LICENSE for details.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sikkerkey-1.2.1.tar.gz.
File metadata
- Download URL: sikkerkey-1.2.1.tar.gz
- Upload date:
- Size: 15.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb96427d93add2477910643e1f3d95f64e082b8696258cd82bd32441d133cfed
|
|
| MD5 |
a4884ed36c0f9c4c4109161c964c6d83
|
|
| BLAKE2b-256 |
08ce4a507ed748546663c05e813a71cdc23107185f8126d16fa773b31d820b3f
|
Provenance
The following attestation bundles were made for sikkerkey-1.2.1.tar.gz:
Publisher:
publish.yml on SikkerKeyOfficial/sikkerkey-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sikkerkey-1.2.1.tar.gz -
Subject digest:
fb96427d93add2477910643e1f3d95f64e082b8696258cd82bd32441d133cfed - Sigstore transparency entry: 1694902772
- Sigstore integration time:
-
Permalink:
SikkerKeyOfficial/sikkerkey-python@82f07d7a4beed548fc64728840cb9a1a6b6e7656 -
Branch / Tag:
refs/tags/v1.2.1 - Owner: https://github.com/SikkerKeyOfficial
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@82f07d7a4beed548fc64728840cb9a1a6b6e7656 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sikkerkey-1.2.1-py3-none-any.whl.
File metadata
- Download URL: sikkerkey-1.2.1-py3-none-any.whl
- Upload date:
- Size: 13.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4e807cd2745a14512cde7081ffa49745e83186f658630cbf9241a84732bf170
|
|
| MD5 |
bd52ad673234680c7ac64ef633d94943
|
|
| BLAKE2b-256 |
7393c34f84866ff3da07f9a7398766efc2d4eae93b2e7b84edb6b9a0fc3b0b4f
|
Provenance
The following attestation bundles were made for sikkerkey-1.2.1-py3-none-any.whl:
Publisher:
publish.yml on SikkerKeyOfficial/sikkerkey-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sikkerkey-1.2.1-py3-none-any.whl -
Subject digest:
a4e807cd2745a14512cde7081ffa49745e83186f658630cbf9241a84732bf170 - Sigstore transparency entry: 1694902908
- Sigstore integration time:
-
Permalink:
SikkerKeyOfficial/sikkerkey-python@82f07d7a4beed548fc64728840cb9a1a6b6e7656 -
Branch / Tag:
refs/tags/v1.2.1 - Owner: https://github.com/SikkerKeyOfficial
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@82f07d7a4beed548fc64728840cb9a1a6b6e7656 -
Trigger Event:
push
-
Statement type: