Tresorix — Handout für Coding-Agents

Eine Seite für Coding-Agents, CI-Bots und andere Automatisierung. Kein Login nötig.

URL an den Agent geben:
https://tresorix.app/agent-handout

Was ist Tresorix?

Kein Konto. Kein Passwort. Nur ein Tresor.

Tresorix ist ein Zero-Knowledge-Passwort-Tresor: Einträge werden auf dem Gerät des Nutzers verschlüsselt. Der Server speichert nur Ciphertext — nie Klartext-Passwörter, nie die 24-Wörter-Recovery-Phrase.

Wann brauchst du das als Agent?

Wenn du im Auftrag des Nutzers auf gespeicherte Zugangsdaten zugreifen sollst (Deploy, SSH, API-Tests, Skripte). Es gibt keine mitgelieferte CLI beim Kunden — du implementierst den Zugriff selbst oder nutzt ein vom Kunden bereitgestelltes Hilfsskript.

Was der Mensch einrichten muss

  1. In Tresorix: Tresor entsperren → API-Keys → neuen Agent-Key anlegen (Lesen/Schreiben, Tag-Scope, Ablaufdatum).
  2. Token und wrap_key einmalig in den OS-Keyring legen (Anleitung in der UI nach Key-Anlage).
  3. Optional: .keyfile herunterladen (nur Metadaten in v3, ohne Token in der Datei).
  4. Dir mitteilen: tresor_id, key_id, gewünschte entry_id oder Eintragstitel.

OS-Keyring — Schema (aktuell)

Der Agent liest nur. Niemals cmdkey, secret-tool store oder Keyring schreiben.

Python (Paket keyring):

import keyring
raw = keyring.get_password("tresorix", "t13:KEY_ID_UUID")
api_token, wrap_key = raw.split("~", 1)

Linux: secret-tool lookup service tresorix username t13:KEY_ID
macOS: security find-generic-password -s tresorix -a t13:KEY_ID -w

Ältere Keys können Legacy-Targets (checkn-vault/agent-api-token:…) nutzen — der Mensch sagt, welches Schema gilt.

API-Ablauf

Basis = https://tresorix.app

1. Umschlag

GET {instance_url}/api/v1/agent/wrap
Authorization: Bearer {api_token}

Antwort: wrap_blob. Bei Widerruf, Pause oder Ablauf: Fehler — stoppen.

2. MEK lokal — mit wrap_key und wrap_blob den Tresor-MEK entschlüsseln (AES-GCM-Umschlag).

3. Einträge

GET {instance_url}/api/v1/entries?limit=50
GET https://tresorix.app/api/v1/entries/{entry_id}

payload_enc mit MEK lokal entschlüsseln → Felder wie secret, username, url.

Der API-Key allein reicht nicht für Klartext — dafür brauchst du wrap_key + gültigen wrap_blob.

Beispiel (Python)

Vollständiger Leseablauf: Keyring → API → lokal entschlüsseln → Daten im Skript nutzen. Abhängigkeiten: pip install cryptography keyring

#!/usr/bin/env python3
"""Tresorix — Eintrag lesen (Keyring + API).
Secrets nur im Skript verwenden — nie in Chat, Logs oder Commits."""
from __future__ import annotations

import base64
import json
import urllib.request

import keyring
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

BASE_URL = "https://tresorix.app"
TRESOR_ID = 13              # vom Menschen
KEY_ID = "00000000-0000-0000-0000-000000000000"  # Agent-Key-UUID
ENTRY_ID = 94               # gewünschter Eintrag


def keyring_creds(tresor_id: int, key_id: str) -> tuple[str, bytes]:
    account = f"t{tresor_id}:{key_id}"
    raw = keyring.get_password("tresorix", account)
    if not raw or "~" not in raw:
        raise RuntimeError("Keyring fehlt oder falsches Format (TOKEN~WRAP)")
    token, wrap_key_b64 = raw.split("~", 1)
    return token, base64.b64decode(wrap_key_b64)


def api_get(path: str, token: str, retries: int = 8) -> dict:
    import time
    import urllib.error

    url = f"{BASE_URL.rstrip('/')}{path}"
    headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
    for attempt in range(retries + 1):
        req = urllib.request.Request(url, headers=headers)
        try:
            with urllib.request.urlopen(req, timeout=45) as resp:
                return json.loads(resp.read().decode())
        except urllib.error.HTTPError as exc:
            if exc.code != 429 or attempt >= retries:
                raise
            raw = exc.read().decode()
            try:
                body = json.loads(raw)
                wait = int(body.get("retry_after", 2))
            except (ValueError, json.JSONDecodeError, TypeError):
                wait = 2
            time.sleep(max(wait, 2))


def aes_gcm_decrypt(key: bytes, blob: bytes) -> bytes:
    return AESGCM(key).decrypt(blob[:12], blob[12:], None)


def load_entry(entry_id: int) -> dict[str, str]:
    token, wrap_key = keyring_creds(TRESOR_ID, KEY_ID)
    wrap_blob_b64 = api_get("/api/v1/agent/wrap", token)["wrap_blob"]
    mek = aes_gcm_decrypt(wrap_key, base64.b64decode(wrap_blob_b64))
    entry = api_get(f"/api/v1/entries/{entry_id}", token)["entry"]
    plain = aes_gcm_decrypt(mek, base64.b64decode(entry["payload_enc"]))
    payload = json.loads(plain.decode())
    return {
        "title": str(payload.get("title") or ""),
        "username": str(payload.get("username") or ""),
        "secret": str(payload.get("secret") or ""),
        "url": str(payload.get("url") or ""),
        "notes": str(payload.get("notes") or ""),
    }


if __name__ == "__main__":
    data = load_entry(ENTRY_ID)
    # Nur lokal weiterverwenden — Secret nicht ausgeben:
    print(data["title"], data["username"], "***")

Das Ergebnis-Dict (load_entry) kannst du im Skript an andere Funktionen übergeben — z. B. SSH, HTTP-Client, Deploy. Nicht in Agent-Chats oder Tickets posten.

Verbindliche Regeln

Gesamttext zum Kopieren (für den Agent)

Markieren oder „Gesamttext kopieren“ — dann in den Agent einfügen.