Sicherheitsmodell
aipager führt Claude Code für dich aus und wird aus Telegram gesteuert. Daraus folgen zwei Fragen:
- Wer kann den Bot steuern?
- Was kann der Bot tun, wenn er gesteuert wird?
Vertrauensgrenze#
Jeder Handler, den der Bot anbietet (Nachricht, Datei, Sprachnachricht, Button), läuft durch den Chat-Filter von python-telegram-bot, der aus den in ~/.config/aipager/aipager.yaml eingerichteten Chats gebaut ist, oder (bei Befehlen) durch dieselbe Prüfung in der eigenen Berechtigungsprüfung von aipager. Die einzige Ausnahme notiert nur Absender in Gruppen, die nicht eingerichtet sind (unten). Nur die eingerichteten Chats können mit dem Bot interagieren. Nichts aus einem anderen Chat wird bearbeitet oder an eine Sitzung geleitet. Ein privater Chat, der nicht eingerichtet ist, bekommt eine einzige Antwort „Dieser Bot ist nicht für dich eingerichtet“. In einer Gruppe, die nicht eingerichtet ist, antwortet der Bot nie, notiert aber, wer ihm dort eine Nachricht geschickt hat (die Telegram-ID, den Benutzernamen und den Namen der Person sowie die ID, den Titel und den Typ der Gruppe) in ~/.claude/aipager-pending-users.json, damit aipager config eine neue Gruppe und ihre Leute finden kann, während aipager läuft. Alle können den Bot zu einer Gruppe hinzufügen, also können alle eine Zeile zu dieser Datei hinzufügen; das gibt keinerlei Rechte. Die Datei kann nur von dir gelesen werden (Rechte 0600), wird bei jedem Schreiben komplett ersetzt (nie halb geschrieben) und behält höchstens die neuesten 200 Personen, wobei jede entfernt wird, die 30 Tage nicht gesehen wurde. Im Teambetrieb kommt darüber eine Liste erlaubter Personen mit Rollen, und in einer Gruppe wird nur eine Nachricht bearbeitet, die an den Bot gerichtet ist (ein Befehl, eine Antwort auf ihn, eine Erwähnung oder ein Tippen auf seine Tastatur), egal ob der Bot Admin der Gruppe ist oder nicht (siehe Gruppen).
Das heißt, die zur Außenwelt offene Angriffsfläche ist:
- Der Token des Bots (ein Geheimnis).
- Die Chat-ID (eine lange Ganzzahl).
Gelangen beide nach außen, kann ein Angreifer dein aipager steuern. Gelangt nur der Token nach außen, kann ein Angreifer die Nachrichten lesen, die aus deinem Chat an den Bot gehen, aber keine Befehle schicken, die aipager ausführt. Gelangt nur die Chat-ID nach außen, bringt das nichts: Der Bot braucht den Token, um überhaupt mit Telegram zu sprechen.
Geheimnisse#
| Geheimnis | Ort | Rechte |
|---|---|---|
| Token des Bots | ~/.config/aipager/aipager.yaml | standardmäßig 600 |
| Chat-ID(s) | dieselbe Datei | standardmäßig 600 |
Der Token bleibt aus dem Log von aipager heraus: Die Zeilen der HTTP-Anfragen, die ihn tragen, werden nicht geloggt, und Fehlermeldungen, aipager doctor und der Assistent schwärzen alles, was wie ein Bot-Token aussieht. Chat-IDs stehen durchaus im Log von aipager (zum Beispiel in den Zeilen zu Ratenlimits), behandle die Ausgabe von aipager logs also als privat. Keins von beiden wird ins Repo übernommen: Die Konfiguration liegt im ~/.config des Benutzers, nicht im Repo. Der Veröffentlichungsweg nach PyPI über Trusted Publisher berührt ebenfalls nie Geheimnisse; die Anmeldung übernimmt OIDC.
Wenn du vermutest, dass der Token kompromittiert ist, widerrufe ihn bei @BotFather (/revoke), erzeug einen neuen und führ aipager config noch einmal aus.
Die Claude-Anmeldedaten: was sie wirklich schützt#
aipager führt einen autonomen Agenten mit Zugriff auf Bash als dein UNIX-Benutzer aus. Jedes Geheimnis, das aipager lesen kann, kann deshalb auch der Agent lesen, den es startet. Das sollte man klar sagen, denn es liegt nahe, zur falschen Art von Schutz zu greifen.
~/.config/aipager/ steht auf der Sperrliste von safety.py, die eine aus Telegram gesteuerte Sitzung daran hindert, Dateien darin zu lesen. Das ist ein Schutz über den Pfad, und er bringt hier nichts: Die Claude-Anmeldedaten (CLAUDE_CODE_OAUTH_TOKEN oder ANTHROPIC_API_KEY) werden nicht über einen Pfad erreicht, sondern in die Prozessumgebung jeder gestarteten Sitzung vererbt. In einer Sitzung gibt echo $CLAUDE_CODE_OAUTH_TOKEN sie aus, Sperrliste hin oder her. Eine Regel für Dateipfade ist die falsche Art Schutz für eine Umgebungsvariable.
Wo die Anmeldedaten liegen#
| Installation | Quelle | Rechte |
|---|---|---|
| systemd-Benutzerdienst | LoadCredential=claude_oauth:%h/.config/aipager/daemon.env, gelesen aus $CREDENTIALS_DIRECTORY/claude_oauth | 600 (daemon.env); systemd hält die Anmeldedaten aus der eigenen Umgebung des Dienstes heraus |
| macOS / Docker / ohne systemd | ~/.config/aipager/daemon.env, direkt gelesen | 600 |
Beide Wege werden vom selben Code gelesen (aipager/daemon_secrets.py) und der gestarteten Sitzung über die Tabelle env= des Unterprozesses übergeben, nie in die Befehlszeichenkette von bash -c eingesetzt. /proc/PID/cmdline kann jeder lesen (0444); /proc/PID/environ ist 0400 (nur der Eigentümer).
Was LoadCredential= + 0600 wirklich bringt: Andere UNIX-Benutzer auf demselben Computer können die Anmeldedaten nicht lesen, und sie tauchen nie in systemctl show, in einer Sicherung der Dienstdatei oder in einem Screenshot der Ausgabe von journalctl auf, den ein Betreiber in einen Fehlerbericht einfügt. Es schafft keine Grenze zwischen aipager und dem Agenten, den aipager startet: Diese Grenze gibt es nicht, und kein noch so ausgeklügeltes Setzen von Dateirechten kann sie schaffen, weil die ganze Aufgabe des Agenten darin besteht, in genau dieser Umgebung als der Betreiber zu handeln.
Der eine Schutz, der wirklich funktioniert#
Begrenze die Anmeldedaten selbst. Lass aipager mit einem eigenen Anthropic-Konto laufen oder mit einem API-Schlüssel, der ein Ausgabenlimit hat, nicht mit demselben Token wie deine eigene interaktive Anmeldung bei claude. Die Anmeldedaten von aipager zu widerrufen darf den Menschen nie abmelden; täte es das, wären beide dieselben Anmeldedaten und keine davon begrenzt. aipager doctor --fix kann einen vorhandenen Token finden, um daemon.env zu füllen, aber welchen Token man ihm gibt, entscheidet der Betreiber, und das ist die eine Entscheidung in diesem Dokument, die mehr zählt als die Dateirechte drumherum.
Die Anmeldung ohne Raten prüfen#
aipager doctor (und der einzeilige Hinweis, der einmal pro Start von aipager kommt) melden die Anmeldung über das eigene claude auth status von Claude Code, nie über eine selbst gebaute Dateiprüfung. Drei Zustände bleiben überall, wo sie erscheinen, im Text unterscheidbar:
auth: <method> (<source>): Anmeldung bestätigt.auth: none (not logged in): bestätigt nicht angemeldet.auth: unknown (...): Der Versuch selbst ist fehlgeschlagen (Zeitüberschreitung, fehlendes Programm, nicht lesbare Ausgabe), oder das Programm ist älter als die Version, die die JSON-Ausgabe eingeführt hat. Das wird nie als „nicht angemeldet“ gemeldet: aipager verweigert den Start einer Sitzung nicht wegen einer Anmeldeprüfung, bei der es nicht sicher ist. Die Anmeldung über den macOS-Schlüsselbund, Anmeldedaten nur mit Refresh-Token und die Anmeldung im Max-Tarif ohne Datei sind für jede Dateiprüfung unsichtbar und sähen sonst genau wie „abgemeldet“ aus.
Wer entscheidet, ob ein Tool-Aufruf läuft#
Zwei getrennte Prüfungen entscheiden, ob ein Tool-Aufruf läuft, und sie laufen in dieser Reihenfolge:
- Die Sicherheitsprüfung von aipager. Claude Code führt vor jedem Tool-Aufruf den Hook
PreToolUsevon aipager aus. Bei einer aus Telegram gestarteten Aufgabe bindet er den Aufruf an die Regeln der Rolle der Person, die sie geschickt hat, und an die Sicherheitsuntergrenze (geschützte Pfade, Befehlsmuster) und lehnt ab, was diese ablehnen: Claude Code lehnt das Tool dann ab, ohne jemanden zu fragen, egal was seine eigenen Regeln sagen. Eine im Terminal getippte Nachricht wird von dieser Prüfung nicht eingeschränkt. Was sie abdeckt, steht unten unter wie die Regeln im Teambetrieb gelten. - Die eigenen Regeln von Claude Code (
~/.claude/settings.json, der Berechtigungsmodus der Sitzung). Sagen sie „fragen“, löst Claude CodePermissionRequestaus, und aipager zeigt die Anfrage in Telegram (siehe Hooks). Dein Tippen geht als Entscheidung des Hooks an Claude Code zurück, oder es wird in den eigenen Dialog von Claude Code getippt, wenn die 20 Sekunden Wartezeit vorbei sind. Lehnen die Regeln von Claude Code den Aufruf ab, kommt er nie bei Telegram an.
aipager kann also einen Aufruf ablehnen, den Claude Code erlauben würde, und wenn Claude Code fragt, sagt aipager nie von sich aus ja: Ein Erlauben ist immer das Tippen einer Person. Drei Dinge lassen Aufrufe laufen, ohne eine Person zu fragen:
- Der Vollzugriff-Modus, für Admins (und für eine Person in ihrem eigenen privaten Chat). Eine Sitzung im Vollzugriff-Modus wird mit
--dangerously-skip-permissionsgestartet: Claude Code fragt nach nichts, jeder Aufruf, den die Sicherheitsprüfung von aipager nicht ablehnt, läuft also ohne Tippen. Nur ein Admin (die Rollenownerundadminoder eine Rolle mitcan_manage) kann eine Sitzung im Vollzugriff-Modus starten, eine dorthin wechseln (/mode full, nach einer Bestätigung) oder eine darin fortsetzen, mit einer Ausnahme: In ihrem eigenen privaten Chat mit dem Bot darf eine Person, deren Rolle Nachrichten schicken und Claude antworten darf (can_promptundcan_approve, alsouser, aber nichtread_only), dasselbe für ihre eigenen Sitzungen tun (/new !x1, „Neue Sitzungen“ in diesem Chat auf den Vollzugriff-Modus gestellt,/mode full,/resume), in Telegram und in der Mini App. Nie in einer Gruppe und nie im privaten Chat einer anderen Person. Alle anderen bekommen stattdessen den Auto-Modus, und die Antwort sagt das. Der Vollzugriff-Modus überspringt nur die eigenen Anfragen von Claude Code: Der Hook PreToolUse von aipager wendet weiterhin die Regeln der Rolle dieser Person (die Sicherheitsuntergrenze, die Beschränkung auf ihren Ordner, die Ablehnungsregeln, die Dateien mit Anmeldedaten) auf jeden Aufruf an, genau wie im Fragen- und im Auto-Modus; der Modus, den Claude Code meldet, lockert sie nie. Die Sicherheitsprüfung von aipager läuft im Vollzugriff-Modus weiterhin bei jedem Aufruf, lehnt aber in den eigenen Aufgaben des Besitzers und bei einer im Terminal getippten Nachricht nichts ab. - Der Auto-Modus, der Standard. Eine Sitzung im Auto-Modus wird mit
--permission-mode autogestartet: Die eigene Sicherheitsprüfung von Claude Code sieht sich jeden Aufruf an, führt die sicheren aus und stoppt die riskanten, die dann wie im Fragen-Modus als Anfrage in Telegram ankommen. Jede Sitzung startet im Auto-Modus, außer der Chat hat unter/settings→ Neue Sitzungen etwas anderes gewählt. Alle, die auf eine Sitzung einwirken dürfen, können sie zwischen Fragen- und Auto-Modus wechseln (/mode); aipager macht das ohne Neustart, indem es in der Sitzung Shift+Tab drückt, nie mit einer Option, die den Vollzugriff-Modus in den Shift+Tab-Zyklus von Claude Code bringen würde. Meldet Claude Code je den Vollzugriff-Modus für eine Sitzung, die aipager nicht darin gestartet hat, zeigt aipager, was gemeldet wurde, loggt eine Warnung und startet die Sitzung deswegen nie im Vollzugriff-Modus oder startet sie darin neu. Bekannte Grenze: Der Modus, den Claude Code meldet, kommt über den Hook-Socket, auf den jeder lokale Prozess schreiben kann (siehe Netzwerkverbindungen). Eine gefälschte Meldung kann den Modus ändern, in dem eine Sitzung angezeigt wird, und während ein Wechsel zwischen Fragen- und Auto-Modus auf seine Bestätigung wartet, kann sie aipager einmal mehr Shift+Tab drücken lassen, was die laufende Sitzung im Auto-Modus lassen kann, obwohl der Fragen-Modus gewünscht war (der nächste echte Hook zeigt wieder den echten Modus). Sie ändert nie die Option, die ein Neustart oder ein Fortsetzen mitgibt, und sie kann nie den Vollzugriff-Modus erreichen: Den setzt nur die Startoption. aipager drückt kein Shift+Tab in einer Sitzung, von der es weiß, dass sie in den Vollzugriff-Modus weiterschalten kann: eine, die im Vollzugriff-Modus gestartet wurde (auch nachdem sie im Terminal in den Fragen- oder Auto-Modus gewechselt wurde), oder eine, die dem laufenden aipager seit ihrem Start den Vollzugriff-Modus gemeldet hat. Dort ist jeder Moduswechsel ein Neustart, der die Option des neuen Modus mitgibt und die des Vollzugriff-Modus weglässt, und eine gefälschte Meldung des Vollzugriff-Modus kann einen Wechsel nur zu einem Neustart machen. aipager kann das nicht wissen bei einer Sitzung, die in einem Terminal mit der Option gestartet wurde, die den Vollzugriff-Modus in den Shift+Tab-Zyklus aufnimmt (--allow-vor der Option des Vollzugriff-Modus), oder mit--dangerously-skip-permissions, wenn sie den Vollzugriff-Modus verlassen hat, bevor aipager etwas von ihr gehört hat (aipager lief damals nicht oder ist seitdem neu gestartet). Dort kann ein Wechsel vom Fragen- in den Auto-Modus im Vollzugriff-Modus landen, und die Karte sagt dann, dass die Sitzung im Vollzugriff-Modus ist. Nimm diese Optionen nicht für Terminal-Sitzungen, deren Modus Personen ohne Admin-Rolle aus Telegram wechseln können. Damit der Vollzugriff-Modus aus Telegram funktioniert, setzt aipager bei jedem StartskipDangerousModePermissionPromptin~/.claude/settings.json(die Warnung von Claude Code, bevor es in diesem Modus läuft, würde die Sitzung sonst anhalten); das schaltet die Warnung auch für die Sitzungen ab, die du selbst startest. Ebenso markiert es den Ordner, in dem eine Sitzung startet, in~/.claude.jsonals vertrauenswürdig (außer dieser Ordner oder einer darüber ist es schon), damit die Frage „Do you trust this folder?“ von Claude Code eine aus Telegram gestartete Sitzung nicht anhält: Die erste Nachricht würde sie mit „No, exit“ beantworten. Die eigenen.claude/-Einstellungen,.git/-Hooks und die.mcp.jsoneines vertrauenswürdigen Ordners können Befehle ausführen; deshalb können Mitglieder ohnecan_managediese Dateien nie schreiben (außerpolicy.yamlgibt ihrer Rolle eine Shell zurück). Claude Code überträgt das Vertrauen eines Ordners auf die Ordner darunter, die nicht in einem Git-Repository liegen; ein Repository bekommt trotzdem seine eigene Frage. Der Home-Ordner wird markiert, wenn eine Sitzung dort startet, weil Claude Code ein Ja im Home-Ordner nur für diese eine Sitzung behält. Danach fragt Claude Code in keinem Ordner unter deinem Home-Ordner mehr nach, der nicht in einem Git-Repository liegt, auch nicht in den Sitzungen, die du selbst startest./und die Ordner über dem Home-Ordner werden nie markiert: Das würde jeden solchen Ordner auf dem Computer erfassen, also bleibt eine Sitzung, die in einem davon startet, bei der Frage stehen. Beim Start markiert aipager nichts, nur den Ordner, in dem gleich eine Sitzung startet. Eine neue Sitzung startet in dem Ordner, in dem aipager läuft (der Home-Ordner beiaipager service installauf Linux und macOS und im Container), oder inAIPAGER_WORK_DIR, wenn das gesetzt ist; das Log von aipager warnt beim Start, wenn dieser Ordner/oder ein Ordner über dem Home-Ordner ist und nicht vertrauenswürdig. Versionen vor dieser markierten den Ordner von aipager bei jedem Start,/eingeschlossen bei einem macOS-Dienst (der in/lief); aipager kann einen solchen Eintrag nicht von einem unterscheiden, den du selbst angelegt hast, und lässt ihn deshalb stehen. aipager lässt~/.claude.jsonund~/.claude/settings.jsonin Ruhe, wenn es sie nicht vollständig lesen kann oder wenn ein laufendes Claude sie geändert hat, während aipager sie aktualisierte. - Immer erlauben, das Claude Code die Regel „nicht mehr fragen“ zurückgibt, die es mit der Anfrage angeboten hat, genau wie diese Option in seinem eigenen Dialog.
Um ein Tool für alle zu sperren, lehne es in ~/.claude/settings.json ab; um es für bestimmte Personen zu sperren, nimm die Regeln ihrer Rolle in policy.yaml (siehe Gruppen).
Was eine Anfrage zeigt, ist das, was läuft. Eine Berechtigungsanfrage zeigt den echten Befehl oder Pfad unter der Beschreibung, die Claude davon gibt. Die Richtungssteuerzeichen von Unicode (U+202A-U+202E, U+2066-U+2069, U+200E, U+200F, U+061C) ändern die Reihenfolge, in der Telegram, ein Browser oder ein Handy den Text danach zeichnet, während die Shell die Zeichen der Reihe nach ausführt; sie könnten also einen Befehl wie einen anderen aussehen lassen (der Trick „Trojan Source“, CVE-2021-42574). In jeder Anfrage, jeder Frage, jeder Diff-Vorschau und jedem Audit-Eintrag werden sie als sichtbare Markierungen gezeigt (⟨U+202E⟩), und die Anfrage warnt, dass der Befehl vielleicht nicht das ausführt, wonach er aussieht. Andere unsichtbare Zeichen bleiben, wie sie sind.
Wie die Regeln im Teambetrieb gelten#
Im Teambetrieb fügt aipager unter dem Tippen eine eigene Schicht hinzu: Jede Nachricht aus Telegram trägt eine Berechtigungsnotiz (wer sie geschickt hat und was ihre Rolle erlaubt), die nach /tmp/claude-notes-<session>/ geschrieben wird, eine Datei pro Nachricht. Wenn Claude eine Nachricht abholt, gleicht das Hilfsprogramm aipager-hook sie mit diesen Notizen ab und legt die Regeln ihres Absenders dort ab, wo die Prüfung PreToolUse sie liest. Diese Eigenschaften tragen alles Übrige:
- Das Strengste gewinnt. Holt Claude Nachrichten von mehr als einer Person als eine einzige Aufgabe ab (aipager hält Nachrichten zurück, um das zu verhindern, bleibt aber sicher, wenn es trotzdem passiert), läuft die Aufgabe mit der strengsten Kombination: Die Nachricht eines Besitzers kann ihre Rechte nie jemand anderem leihen.
- Eine laufende Aufgabe wird nie weiter. Eine Nachricht von einer anderen Person wartet, bis die laufende Aufgabe endet, und läuft dann als eigene Aufgabe mit den Regeln ihres Absenders. Tritt doch eine einer laufenden Aufgabe bei, behält die Aufgabe die strengsten Regeln aller Beteiligten; eine Telegram-Nachricht, die einer im Terminal getippten Aufgabe beitritt, bindet den Rest dieser Aufgabe an die Regeln ihres Absenders (die eines Besitzers ändert nichts).
- Deine Rolle ist die im Chat, in dem du handelst. Eine Person, die in mehreren Chats steht (zum Beispiel
userin einer Gruppe undownerihres eigenen privaten Chats), hat für alles, was sie in der Gruppe schickt oder antippt, die Rolle aus der Gruppe und im privaten Chat die Rolle aus dem privaten Chat, egal in welcher Reihenfolge die Chats inaipager.yamlstehen. - Unbekannt heißt eingeschränkt. Eine Nachricht, die niemandem zugeordnet werden kann, läuft mit der eingebauten Untergrenze (keine Ausnahme, alle Ablehnungsregeln aktiv), nie als uneingeschränkte Terminal-Nachricht. Nachrichten, die du direkt im Terminal tippst, bleiben uneingeschränkt; alles, was in irgendeiner Zeile die Telegram-Markierung trägt, wird geprüft. Jede Telegram-Nachricht trägt diese Markierung, beschriftet mit der Person, die genau diese Nachricht geschickt hat (nie mit der, die die Sitzung zuletzt gesteuert hat); ein Absender, den aipager nicht kennt, zum Beispiel jemand, der aus
aipager.yamlentfernt wurde, während seine Nachricht wartete, bekommt ein bloßes[via Telegram]und die Untergrenze. - Slash-Befehle brauchen einen Admin. Ein Slash-Befehl wird roh in Claude getippt, ohne Markierung, und ein Befehl oder Skill wie
/deliverführt eine ganze Aufgabe aus seinem eigenen Text aus. Ein Mitglied, dessen Rolle die Rollenregeln nicht umgeht (die eingebautenuserundread_only, Rollen, die du ohnebypass_role_deniesdefinierst, und alle, die aipager nicht kennt), darf daher nur die Befehle der Befehle-Tastatur von aipager (plus/compact) und Modellwechsel (/model <name>) senden; jeder andere Slash-Befehl, getippt, mit/<name>an eine Sitzung geschickt, als Bildunterschrift einer Datei, als erste Nachricht von/newoder als Vorlage, wird mit „Dieser Befehl ist nur für Admins.“ und einem 🤷 abgelehnt. Besitzer und Admins senden jeden. Ein Slash-Befehl, der aus Telegram wirklich bei Claude ankommt, wird weiterhin geprüft: Der Hook sieht, dass die Aufgabe eine Telegram-Nachricht abgeholt hat, die diesen Befehl geschickt hat, und bindet ihn an die Regeln dieses Absenders. Ein Slash-Befehl, den du im Terminal tippst, bleibt uneingeschränkt, außer derselbe Befehl (mit beliebigen Argumenten) kam früher in dieser Sitzung aus Telegram: aipager kann deine Kopie nicht davon unterscheiden, dass Claude Code die aus Telegram noch einmal zustellt, deine läuft dann also mit den Regeln der letzten Telegram-Aufgabe. Das endet, wenn die Sitzung endet. - Eine Nachricht ohne neuen Absender behält die Regeln der Aufgabe. Claude Code meldet auch Nachrichten, die keine Telegram-Nachricht erklärt, während eine Telegram-Aufgabe läuft: eine im Terminal getippte Nachricht, die hinter der Aufgabe in die Warteschlange kommt (gemeldet in dem Moment, in dem sie eingereiht wird), eine Nachricht aus einer anderen lokalen Claude-Sitzung, oder dieselbe Telegram-Nachricht, die nach einer Komprimierung noch einmal zugestellt wird. Eine solche Nachricht fügt keinen Telegram-Absender hinzu, die Aufgabe behält also die Regeln, mit denen sie lief, verschärft durch jede Nachricht, die noch auf das Abholen wartet, und nie gelockert. Eine Nachricht mit Telegram-Text, den aipager niemandem zuordnen kann, fällt weiterhin auf die eingebaute Untergrenze, und eine Nachricht eines Absenders mit weniger Rechten senkt die Aufgabe weiterhin auf die Regeln dieses Absenders.
Was eine eingeschränkte Person wirklich festhält. Die Claude-Sitzung läuft als derselbe Betriebssystem-Benutzer, dem die Konfiguration von aipager gehört (der Token des Bots in ~/.config/aipager/aipager.yaml, die Claude-Anmeldedaten in ~/.config/aipager/daemon.env) und seine Regeldatei pro Aufgabe (/tmp/claude-policy-<session>.json). Eine Shell, die als dieser Benutzer läuft, kann beide über Schreibweisen erreichen, die kein Muster vorhersieht (sed -i … /tmp/*-policy-*.json, eine for-Schleife über ein Glob, ein Python-Einzeiler), und ihre eigenen Regeln in die des Besitzers umschreiben. Befehlsmuster sind daher ein Filter, keine Grenze. Für eine Aufgabe mit den eingebauten eingeschränkten Rollen (user, read_only) und für eine Aufgabe, die aipager niemandem zuordnen kann, setzt aipager diese Regeln durch, von denen keine auf Befehlsmustern beruht (siehe die bekannten Grenzen unten für das, was sie nicht abdecken):
- Keine Shell. Sie können
Bashnicht nutzen und auch kein anderes Tool, mit dem Claude Code Code ausführt (PowerShell,Monitor,REPL,Workflow, die Planer für NachrichtenCronCreateundRemoteTrigger, …; die Liste istsafety.CODE_EXECUTION_TOOLS). Eine geplante Nachricht käme ohne die Telegram-Markierung an, also uneingeschränkt, deshalb zählen auch diese. Aus demselben Grund können sieSendMessagenicht nutzen (es kann eine Nachricht an eine andere lokale Claude-Sitzung geben, die sie als Terminal-Nachricht ausführen würde) und auch nichtEnterWorktree/ExitWorktree(die das Arbeitsverzeichnis der Sitzung verschieben, auf das sich die nächste Regel verlässt). - Schreiben bleibt im Projekt.
Write,Edit,MultiEditundNotebookEditschreiben nur in den Ordner der Sitzung (das Arbeitsverzeichnis, das Claude Code im Inhalt des Hooks meldet, nie ein Pfad aus dem Tool-Aufruf) und in das Claude-Code-Scratchpad der Sitzung (/tmp/claude-<uid>/<project>/<session id>/, oder/tmp/claude-<uid>/, wenn sich das nicht ermitteln lässt). Alles andere wird abgelehnt: die Startdateien deiner Shell,~/.ssh, andere Projekte, die/tmp-Dateien von aipager. Im Ordner werden auch.claude/,.git/und.mcp.jsonabgelehnt, weil jedes davon Claude Code oder git einen Befehl ausführen lässt. Symbolische Links werden vor der Entscheidung aufgelöst. Eine Sitzung, deren Ordner dein Home-Ordner,/oder ein Ordner über deinem Home-Ordner ist, hat keinen Projektordner: Dort schreibt eine eingeschränkte Aufgabe nur in ihr Scratchpad, und ein Schreiben woandershin wird mit „Diese Sitzung läuft im Home-Ordner (oder einem Ordner darüber), und diese Rolle darf nur in einem Projektordner schreiben“ abgelehnt. Damit eingeschränkte Mitglieder dort nicht festsitzen, lassen/newund die Mini App sie keine Sitzung in diesen Ordnern starten (der eigene Ordner von aipager, der Standard für eine neue Sitzung, ist unter dem Hintergrunddienst dein Home-Ordner); sie können in deinem Home-Ordner einen neuen Ordner anlegen und darin starten (einen neuen: keinen Ordner, den du dort schon hast, und wederbinnochnode_modules, aus denen andere Programme Code laden). - Mitglieder bleiben in ihrem Stammordner. Eine Rolle ohne
can_manage(die eingebautenuserundread_only) arbeitet im Stammordner des Chats, den du inaipager configfestlegst (Standard~/aipager-users/<label>;~/aipager-usersund der Ordner werden mit Rechten 0700 angelegt)./new, Fortsetzen, Ersetzen, Neu starten, der Moduswechsel und die Mini App starten oder starten eine Sitzung eines solchen Mitglieds nur darin neu. Der Sicherheits-Hook setzt das durch, egal auf welchem Weg eine Nachricht kommt (Telegram, die Mini App, die Nachricht einer anderen Sitzung): Der Stammordner reist im Regelstand der Aufgabe mit, in den Belegen der Nachrichten zwischen Sitzungen und in festgehaltenen und geplanten Rechten, und wenn sich Mitglieder mit verschiedenen Stammordnern eine Aufgabe teilen, gewinnt der engere (zwei, die nicht ineinander liegen, lassen nur den eigenen Ordner der Sitzung übrig). Geschrieben wird nur in den Ordner der Sitzung, wenn er im Stammordner liegt (eine Sitzung außerhalb, etwa eine, die vor den Stammordnern gestartet wurde, läuft weiter, aber ihre Aufgaben schreiben nur ins Scratchpad: „Diese Sitzung läuft außerhalb des Ordners dieser Rolle“), undRead,LSP,GrepundGloberreichen nichts in deinem Home-Ordner außerhalb des Stammordners („Im Home-Ordner liest diese Rolle nur in ihrem eigenen Ordner“), außer dem Scratchpad der Sitzung und ihrer eigenen Antwortdatei. Der Stammordner jedes anderen Chats ist für das Mitglied ein geschützter Pfad, wo auch immer er liegt, und ebenso der Upload-Ordner jedes anderen Chats unter/tmp/aipager-files/(Read,Grep,Globund die Schreib-Tools; bei einer Rolle mit Bash wird ein Befehl abgelehnt, der einen davon nennt, nach bestem Bemühen), samt den Dateien, die sich alle Chats dort teilten, bevor es Ordner pro Chat gab. Uploads aus dem eigenen Chat des Mitglieds bleiben lesbar. Ordner außerhalb deines Home-Ordners (/usr,/etc,/srv) bleiben lesbar. Eine eigene Rolle mitbypass_role_deniesoderbypass_safety, aber ohnecan_manageschreibt und sucht überall in ihrem Stammordner, und auch ihr Lesen in deinem Home-Ordner bleibt darin: Nur Besitzer und Admins sind uneingeschränkt. Mitbypass_safetyschreibt und sucht sie in einer Sitzung, deren Ordner außerhalb ihres Stammordners liegt, nur im Scratchpad, wie einuser, und sobald einer ihrer Tool-Aufrufe abgelehnt wird, wird der Rest der Aufgabe angehalten. - Suchen bleiben im Projekt. Ein
GrepoderGlobist nur erlaubt, wenn all das zutrifft: Der Ordner, in dem gesucht wird (seinpath, oder der Ordner der Sitzung, wenn es keinen hat, symbolische Links aufgelöst), liegt im Ordner der Sitzung (nicht, wenn das dein Home-Ordner,/oder ein Ordner über deinem Home-Ordner ist, wie beim Schreiben) oder im Scratchpad; dieser Ordner ist kein geschützter Pfad und enthält keinen (eine in deinem Home-Ordner gestartete Sitzung enthält~/.config/aipager, kann also nicht von ganz oben suchen); jedes Glob ist ein einfaches relatives Muster (kein/,~,$oder Laufwerksbuchstabe am Anfang, kein.., kein#(ein Kommentar für ripgrep) oder!(eine Verneinung), kein Escape mit\); und du hast keine Regel für geschützte Pfade ohne/oder~am Anfang (**/.envkann überall im Projekt liegen, mit so einer Regel wird also jede eingeschränkte Suche abgelehnt). Alles andere wird abgelehnt. Das ist absichtlich eine Liste des Erlaubten: Drei Prüfrunden haben jeweils eine neue Schreibweise eines Globs gefunden, die ripgrep aus dem Ordner herausführte, den ein Modell seiner Auswertung erwartet hatte, und eine davon hat den Token des Bots gelesen. - Die Datei-Tools beachten die geschützten Pfade.
Read,LSPund die Schreib-Tools prüfen, wohin ein Pfad wirklich führt, symbolische Links eingeschlossen. Die einzige Steuerdatei, die eine Aufgabe lesen darf, ist die/tmp/claude-reply-<session>.txtihrer eigenen Sitzung, die aipager in der Nachricht nennt, wenn du auf eine ältere Nachricht antwortest; die Antwortdateien anderer Sitzungen und jedes Schreiben in diese hier bleiben abgelehnt. Auf macOS ist/tmpgleich/private/tmp, und die Festplatte unterscheidet keine Groß- und Kleinschreibung, beide Schreibweisen und jede Großschreibung werden also erkannt. - Eine Prüfung, die fehlschlägt, lehnt ab. Wirft die Entscheidung über einen Tool-Aufruf einen Fehler (ein NUL-Byte in einem Pfad, ein Bug), oder geht dem Hook beim Entscheiden der Speicher aus, wird der Aufruf abgelehnt, außer die Regeln der Sitzung konnten gelesen werden und gewähren die Ausnahme des Besitzers. Früher wurde er durchgelassen.
Deine eigenen geschützten Pfade. Der Abschnitt safety: von policy.yaml (deny_paths_no_access, deny_paths_no_write, deny_bash_patterns) ergänzt die eingebauten geschützten Pfade und Befehlsmuster. Er gehört zur Untergrenze, die jede Aufgabe jeder Rolle außer owner bekommt, Admins eingeschlossen, und er gilt für die Datei-Tools, Suchen und Bash-Befehle genau wie die eingebaute Liste. aipager schreibt die zusammengeführte Liste nach /tmp/claude-policy-.floor-<uid>.json (selbst ein geschützter Pfad) für die Rückfallebenen des Sicherheits-Hooks; fehlt diese Datei, ist sie unlesbar oder gehört sie einem anderen Betriebssystem-Benutzer, nimmt der Hook die eingebaute Liste, nie weniger. Eine Änderung, die mit einem Neuladen ohne Neustart kommt, gilt für Nachrichten, die nach dem Neuladen geschickt werden, nicht für eine schon laufende Aufgabe oder eine schon wartende Nachricht. Bis 0.7.20 wurde der Abschnitt von aipager doctor --safety-check gelesen und angezeigt, aber nicht durchgesetzt.
Aufgaben, die aipager niemandem zuordnen kann. Eine Telegram-Aufgabe ohne Absender, an den sie gebunden werden kann, läuft mit der Untergrenze, die der eingebauten Rolle user entspricht (nicht den zusätzlichen Einschränkungen, die du user in policy.yaml gibst) plus deinem Abschnitt safety:: keine Tools, die Code ausführen, Schreiben und Suchen beschränkt, die geschützten Pfade. Das passiert bei einer Nachricht, deren Berechtigungsnotiz abgelaufen ist (nach 24 Stunden) oder nicht geschrieben werden konnte, und bei einer Nachricht mit Telegram-Text, zu dem keine Notiz passt. Der Button „🔄 Erneut versuchen“ läuft mit den Rechten der Person, die ihn antippt, wenn sie auch die wiederholte Nachricht geschickt hat: Tippt ein Besitzer ihn bei seiner eigenen Nachricht an, behält die Aufgabe Bash; bei der Nachricht von jemand anderem oder bei einer, deren Absender aipager nicht kennt, läuft sie mit der Untergrenze, damit sich der Text von niemandem die Rechte der Person leiht, die tippt.
Lesen außerhalb der geschützten Pfade ist für eine Rolle erlaubt, die nicht an einen Stammordner gebunden ist (eine eigene Rolle mit can_manage, aber ohne Ausnahme): Sie kann andere Projekte des Betriebssystem-Benutzers lesen. Die eingebauten user und read_only lesen nichts in deinem Home-Ordner außerhalb ihres Stammordners (oben). Deine Dateien mit Anmeldedaten sind geschützte Pfade für jede Rolle ohne bypass_role_denies (die eingebauten user und read_only, Rollen, die du definierst, und die Untergrenze): ~/.ssh, ~/.gnupg, ~/.aws, ~/.config/gh, ~/.git-credentials, ~/.netrc, ~/.docker/config.json, ~/.kube, ~/.config/gcloud, ~/.azure, ~/.password-store, ~/.pgpass, ~/.npmrc und ~/.pypirc können weder gelesen noch geschrieben noch durchsucht werden. Sie sind ein Standard der Rolle, das eigene deny_paths_no_access einer Rolle in policy.yaml ersetzt sie also für diese Rolle, außer wenn einer laufenden Aufgabe eine andere Nachricht oder eine im Terminal getippte Nachricht beitritt: Von da an bis zum Ende der Aufgabe ist sie wieder daran gebunden (die strengsten Regeln gewinnen). Eine Datei mit Anmeldedaten an anderer Stelle (eine .env in einem Projekt, ein Token in einem anderen Ordner) ist nicht abgedeckt: Füg sie zum deny_paths_no_access der Rolle hinzu. Besitzer sind an nichts davon gebunden. Das Schreiben von Admins ist nicht beschränkt. Ihr Grep/Glob darf in jedem Ordner suchen, der keinen geschützten Pfad erreicht: Eine Suche wird abgelehnt, wenn der durchsuchte Ordner (sein path, oder der Ordner der Sitzung, wenn es keinen hat; bei einem Glob auch der Ordner, den sein Muster nennt, wie in /home/you/.config/aipager/*, oder der Ordner über einem Muster ohne Platzhalter, den Claude Code in jeder Tiefe nach diesem Namen durchsucht), mit aufgelösten symbolischen Links, ein geschützter Ordner ist, in einem liegt oder einen enthält. Ein Grep von ~, /, dem Ordner über deinem Home-Ordner oder /tmp wird also abgelehnt (sie enthalten ~/.config/aipager, ~/.claude oder die Dateien /tmp/claude-* von aipager), ein Grep eines Projekts oder von /tmp/myproj nicht. Eine Regel für geschützte Pfade ohne / oder ~ am Anfang (wie **/.env) kann in jedem Ordner passen; mit ihr in den Regeln eines Admins (oder im Abschnitt safety:, den jede Rolle außer owner bekommt) wird also jede Suche eines Admins abgelehnt, wie bei eingeschränkten Rollen; aipager doctor warnt bei einer solchen im Abschnitt safety:. Admins haben Bash, ihre Regeln bleiben also nur nach bestem Bemühen (unten).
Bekannte Grenzen. Das hier ist nicht abgedeckt:
- Scheitert der Hook selbst, bevor er zu entscheiden beginnt (zum Beispiel, weil er seine eigene Statusdatei nicht lesen kann oder ihm beim Start der Speicher ausgeht), behandelt Claude Code das als „erlauben“. Eine eingeschränkte Person kann das nicht auslösen; jeder Fehler beim Entscheiden lehnt ab.
- Tools, die keine Datei-Tools sind, aber Daten nach außen schicken können, werden nicht nach Pfad geprüft:
WebFetch(alles, was die Aufgabe lesen kann, kann sie an eine URL schicken) und die anderen Tools von Claude Code zum Ausliefern. Lehne sie für eine Rolle inpolicy.yamlab, wenn dir das wichtig ist.SendUserFile, das Dateien in den Chat schickt, wird geprüft (siehe Dateien, die Claude schickt). - Ein Hintergrund-Agent, den eine eingeschränkte Aufgabe gestartet hat, läuft nach dem Ende der Aufgabe weiter, und seine späteren Tool-Aufrufe werden nach der neuesten Nachricht in der Sitzung beurteilt: Eine spätere Nachricht des Besitzers oder aus dem Terminal hebt seine Einschränkungen auf.
- Eine Rolle, die du mit
can_manage, aber ohne eine der beiden Ausnahmen definierst, ist an keinen Stammordner gebunden: Sie kann weiterhin in jedem Projektordner starten, in dem ihr Chat schon arbeitet (ein Ordner, den eine frühere Sitzung genutzt hat), auch in einem von deinen, und deine anderen Projekte lesen. - Eine Aufgabe, die an kein Mitglied gebunden werden kann (ein unbekannter Absender, ein entferntes Mitglied, eine Nachricht aus einer anderen Sitzung, deren Absender nicht geprüft werden kann), läuft mit der eingeschränkten Untergrenze, die keinen Stammordner hat. Beim Lesen ist diese Untergrenze schwächer als ein eingebauter
user: Eine solche Aufgabe kann jede Datei in deinem Home-Ordner lesen, die kein geschützter Pfad ist (zum Beispiel deine anderen Projekte), wo die Aufgabe einesusernur in seinem Stammordner liest. Eine solche Aufgabe kann außerdem die Uploads aller Chats in/tmp/aipager-files/lesen; die Aufgabe einesuserliest dort nur die Uploads ihres eigenen Chats. Ihr Schreiben bleibt auf den Ordner der Sitzung und das Scratchpad beschränkt. Eine geprüfte Nachricht aus der Sitzung eines Mitglieds trägt den Stammordner dieses Mitglieds. - Der Stammordner bindet nur die Datei-Tools. Eine eigene Rolle, der du
Bashgibst, kann über die Shell außerhalb lesen (die eingebautenuserundread_onlyhaben keinBash). Eine eigene Rolle mitbypass_safety, aber ohnecan_managebehält ihre Ausnahme für alles außer den Pfaden der Datei-Tools: Kein Tool und kein Befehlsmuster wird ihr verweigert. - Ein Ablehnungsgrund, der den Stammordner eines anderen Mitglieds nennt, zeigt den Pfad dieses Ordners Claude und im Chat.
- Die Aufgabe eines Mitglieds, die schon läuft, wenn die Stammordner kommen (ein Update), hat bis zur nächsten Nachricht dieses Mitglieds keinen.
- Im Projekt kann eine eingeschränkte Person ändern, was du später selbst ausführst oder liest (Quellcode, ein
Makefile, die Skripte inpackage.json,conftest.py, eineCLAUDE.md, die deine eigenen Aufgaben laden).
Eine Rolle mit Bash (admin, das die Ablehnungsregeln der Rolle umgeht, oder jede Rolle, der policy.yaml es zurückgibt: allow_tools mit Bash oder ein ersetztes deny_tools) ist nur an Regeln nach bestem Bemühen gebunden: die Befehlsmuster unten, die eine entschlossene Person umgehen kann. aipager doctor warnt einmal für jede solche Rolle, die ein Mitglied hat. Gib Bash nur jemandem, dem du eine Shell auf dem Computer geben würdest.
Die eingebauten Befehlsregeln. Unter den Bash-Mustern, an die jede Telegram-Aufgabe mit Bash gebunden ist, die nicht dem Besitzer gehört, blockiert eines das Wort claude: Es stoppt ein verschachteltes claude (mit jedem Pfad, jedem Wrapper und jeder Option) und jeden Zugriff auf ~/.claude in einer einzigen Regel, und es fängt auch die einfachen Namen der Steuerdateien /tmp/claude-* von aipager, deren Namen dieses Präfix teilen (ein Muster ist für diese Dateien kein echter Schutz: eine Glob-Schreibweise umgeht es). Die einzige Ausnahme ist der Scratchpad-Ordner, den Claude Code anlegt, /tmp/claude-<uid>/…: Genau dieses Verzeichnis, als ganzer Pfadbestandteil, ist ausgenommen, Befehle aus dem Scratchpad einer Sitzung werden also nicht angehalten. Es gehört dem Betriebssystem-Benutzer, nicht einer Sitzung, eine Aufgabe mit Bash kann dort also die Scratchpads und Aufgabenausgaben anderer Sitzungen lesen, wie es das Tool Read kann. Ein Glob oder etwas Ähnliches davon (/tmp/claude-1*, claude-1000x), ein anderes temporäres Verzeichnis und alles, was man von dort aus erreicht (…/../../home/you/.claude), bleiben blockiert. Andere Vorkommen des Worts (git log --grep=claude, eine Datei namens claude-notes.md, pip show claude-agent-sdk) bleiben ebenfalls blockiert, absichtlich: Dasselbe Wort benennt das Programm, seine SDKs und die Steuerdateien von aipager, und ein Muster kann sie nicht auseinanderhalten. Regeln, die du in policy.yaml hinzufügst, sehen den Befehl genau so, wie er getippt wurde, Scratchpad eingeschlossen.
Suchen über Bash. Manche Versionen von Claude Code haben kein Tool Grep oder Glob (2.1.289 ist eine davon), und Claude sucht dann mit grep, rg oder find über Bash, wo die Regel für Grep/Glob oben nicht gilt. Für jede Rolle außer owner lehnen die Befehlsmuster auch eine rekursive Suche oder Auflistung des ganzen Home-Ordners oder von / ab: grep -r (-R, --recursive, -d recurse, egrep, fgrep, rgrep), rg (und rg --files), ag, ack (und ack -f), find, fd, tree, du und ls -R, deren Ordner ~, ~user, $HOME, ${HOME} oder "$HOME", /home, /home/<user> (oder /home/$USER), /Users, /Users/<user>, /root oder / ist (mit oder ohne abschließendes /, /. oder /*), oder ~/.config oder ~/.local, die die Ordner von aipager enthalten. Das gilt auch nach cd x &&, in einer Pipe, in bash -c "...", nach xargs, env oder timeout und nach Einstellungen wie LC_ALL=C. Die Antwort sagt, stattdessen in einem Projektordner zu suchen. Suchen in einem Projektordner (grep -rn x ~/proj, rg x src/, find . -name y), Suchen in einem Projekt NACH einem Pfad im Home-Ordner (grep -rn "$HOME" src: das erste Wort nach den Optionen ist der gesuchte Text, kein Ordner) und Suchen, die nicht rekursiv sind (grep x ~/.bashrc, ls ~), sind nicht betroffen. Das ist nach bestem Bemühen, wie jedes Befehlsmuster: Ein Muster sieht die Wörter, nicht, wo die Shell sie ausführt, es kann also keine Suche ohne Ordner, oder mit . oder .., sehen, die aus dem Home-Ordner (oder darunter) läuft (cd ~ && grep -r x .), keinen Home-Ordner, der über eine andere Variable oder ein Glob erreicht wird, keinen, der außerhalb von /home, /Users und /root ausgeschrieben ist, kein \grep und keinen Ordner direkt nach einem Wort, das auf -e oder -f endet (gehalten für den Text, den -e/-f an grep und rg gibt, ag x -f ~ kommt also durch). Es weiß auch nicht, welche Optionen einen eigenen Wert nehmen, rg -t py "$HOME" . oder grep -rn -A 3 "/home/you" src liest also py oder 3 als den Text und den Pfad im Home-Ordner als Ordner und wird abgelehnt (schreib -A3, --include=*.py, oder setz die Option hinter den Text), ebenso ein Pfad im Home-Ordner, der -e in einer Gruppe von Optionen gegeben wird (grep -rne "/home" src; -r -n -e ist in Ordnung); ein Befehl, der im Text eines anderen Befehls zitiert ist (echo "a; rg x ~"), wird ebenfalls abgelehnt, ebenso du -sh ~ (nur Größen).
Warum eine Sperre die ganze Aufgabe anhält. Nachdem ein Tool-Aufruf blockiert wurde, wird jeder weitere Aufruf in derselben Aufgabe ebenfalls abgelehnt, bis zur nächsten Nachricht. Ohne das versucht der Agent dieselbe Aktion einfach anders formuliert noch einmal (ein Glob, ein anderer Befehl, ein kodierter Pfad), bis eine Schreibweise an den Mustern vorbeirutscht. Die Aufgabe anzuhalten macht einen ersten Versuch teuer; es macht aus einem Muster keine Grenze, denn die erste versuchte Schreibweise kann die sein, die durchrutscht. Der Preis eines Fehlalarms ist eine angehaltene Aufgabe, und deshalb sind die Regeln oben eng gehalten.
Siehe Gruppen → wie die Regeln funktionieren für die Regeln, die diese Notizen tragen, so wie du sie siehst.
Dateien, die Claude schickt#
Jede Sitzung, die aipager startet, hat das Tool SendUserFile von Claude Code (CLAUDE_CODE_ATTACHMENTS_BY_PATH=1), das Dateien liest und in den Chat der Sitzung schickt (Befehle). Der Hook PreToolUse prüft jede Datei des Aufrufs, bevor Claude Code sie liest:
- Die Grenze für das Senden, für jede Aufgabe, auch die des Owners und eine im Terminal getippte: die Konfiguration und der Zustand von aipager (
~/.config/aipager/**,~/.local/share/aipager/**,~/.local/state/aipager/**und seine Steuerdateien in/tmp), ganz~/.claude/**und~/.claude.json*(Claudes Zugangsdaten und Konto), die Zugangsdaten, von denen jede eingeschränkte Rolle ferngehalten wird (~/.ssh/**,~/.gnupg/**,~/.aws/**,~/.git-credentials,~/.netrcund der Rest dieser Liste),/proc/**,/sys/**,/dev/**und Namen, die überall wie Geheimnisse aussehen (.env,.env.*,*.pem,*.key,id_rsa*,id_ed25519*und die anderen Schlüsselnamen,*.p12,*.pfx,*.kdbx,*.jks,*.keystore,.pgpass,credentials,*credentials*.json). Geprüft wird der Pfad, wie er geschrieben ist und wohin er wirklich führt, also wird auch ein Symlink auf eine davon abgelehnt. Manche harmlose Dateien tragen solche Namen (eine Keynote-Datei*.key, eine.env.example) und werden ebenfalls abgelehnt. - Die Datei selbst: eine normale Datei, wenn Symlinks aufgelöst sind, mit einem Hardlink, nicht leer, höchstens 50 MB.
- Die eigenen Regeln der Aufgabe: was die Aufgabe nicht lesen (Read) darf, und für ein Mitglied (eine Rolle ohne
can_manage, die an einen Stammordner gebunden ist) die strengere Regel, dass jede Datei, wie geschrieben und wohin sie wirklich führt, im Stammordner liegt. Anders als beim Lesen gibt es keine Ausnahme für Dateien außerhalb des Home-Ordners (/tmp,/etc) und keine für das Scratchpad der Sitzung.
Eine Ablehnung ist ein weiches Nein: Claude liest den Grund („SendUserFile refused, nothing was sent: ... Tell the person which file could not be sent and why.“), und die Aufgabe läuft weiter; die Sitzung wird nicht angehalten wie bei einem blockierten Tool. Schlägt die Prüfung fehl, wird die Datei abgelehnt, auch für den Owner. Jede abgelehnte Datei hat einen Eintrag FileRefused im Audit-Log.
aipager glaubt dem Steuer-Socket bei keiner Datei aufs Wort: jeder lokale Prozess kann hineinschreiben und dabei jede Sitzung, jeden Aufruf und jeden Gesprächsverlauf nennen, und schon der Hook eines Tool-Aufrufs allein lässt eine ruhende Sitzung als arbeitend gelten. Deshalb zählt ein Aufruf nur während einer Aufgabe, die aipager mit einem Prompt beginnen sah (einem, den es aus Telegram eingetippt hat, oder einem UserPromptSubmit), und während des Hintergrund-Jobs dieser Aufgabe, bis die Sitzung ruht und kein Job mehr läuft; bei diesem Beginn merkt sich aipager die Uhrzeit und den Gesprächsverlauf, den es für die Sitzung führt (ein späterer UserPromptSubmit derselben Aufgabe nennt den Gesprächsverlauf neu). Ein Slash-Befehl, den aipager eintippt, beginnt keine Aufgabe (einer, der eine Aufgabe ausführt, schickt seinen eigenen UserPromptSubmit), und eine Aufgabe, die nie beginnt (die Sitzung ruht 15 Sekunden später noch ohne Job), wird wieder geschlossen. Manche Aufgaben beginnt Claude Code ohne UserPromptSubmit: eine Nachricht, die während einer laufenden Aufgabe kam und die es als nächste Aufgabe übernimmt, und die Aufgabe, die ein Stop-Hook weiterlaufen lässt (/goal). Eine solche Aufgabe zählt, wenn aipager diese Nachricht selbst eingetippt hat, oder sobald der gemerkte Gesprächsverlauf ihren Beginn nach dem letzten Stop zeigt (eine Warteschlangen-Zeile dequeue oder die Zeile Stop hook feedback:, die Claude Code schreibt); bis dahin wird vom Aufruf nichts gelesen, bis zu 20 Sekunden lang. Die Dateiliste nimmt es aus dem PreToolUse, den es geprüft hat, oder aus dem PostToolUse, wenn jener verpasst wurde (nach einem Neustart); nie aus dem Ergebnis von Claude Code. Jeden Aufruf kopiert es nur einmal: eine Aufruf-ID, die es schon kopiert hat, wird nie wieder kopiert oder geschickt (es behält die letzten 256 je Sitzung, auch über einen Neustart hinweg). Es bestimmt den echten Pfad jeder Datei ein einziges Mal und prüft sie dort noch einmal (die Grenze, die Dateiprüfungen und den Stammordner der Sitzung), öffnet genau diesen Pfad, ohne einem Link zu folgen, stellt sicher, dass die geöffnete Datei die geprüfte ist und noch an diesem Pfad liegt, und kopiert sie nach ~/.local/state/aipager/outbox/<chat>/ (Ordner 0700, Datei 0600), einen Ort, von dem jede eingeschränkte Aufgabe schon ferngehalten wird. Vor dem Senden sucht es den Aufruf in dem Gesprächsverlauf, den es sich für die Aufgabe gemerkt hat (für den Aufruf eines Subagenten in dessen eigenem Gesprächsverlauf daneben), nie in einem, den das Datagramm eines Tool-Aufrufs nennt: eine Zeile des Assistenten mit einem SendUserFile-tool_use mit dieser ID und genau diesen Dateien, geschrieben aus demselben Ordner und nicht vor dem Beginn der Aufgabe, in einer .jsonl-Datei unter ~/.claude/projects (oder $CLAUDE_CONFIG_DIR/projects, wie es für aipager selbst gesetzt ist), die aipagers eigenem Betriebssystem-Benutzer gehört. Claude Code schreibt seinen Gesprächsverlauf spät, deshalb wartet aipager bis zu 20 Sekunden auf den Aufruf, und solange die Arbeit der Sitzung noch läuft (ein Hintergrund-Agent, ein Tool-Aufruf), behält es die Dateien und sieht später noch einmal nach. Einen Aufruf, den es auch dann nicht findet, wenn nichts mehr läuft, schickt es nicht, und seine Kopien werden gelöscht; ein Aufruf, der kam, während keine solche Aufgabe lief, oder der ein zweites Mal kam, wird gar nicht erst kopiert. Jede ihrer Dateien bekommt im Audit-Log einen Eintrag FileFailed mit dem Grund unverified. Der Chat bekommt dazu eine Zeile (⚠️ report.pdf konnte nicht gesendet werden: aipager konnte nicht bestätigen, dass Claude die Datei in dieser Aufgabe gesendet hat.) nur, wenn Claudes eigener Gesprächsverlauf genau diesen Aufruf während der Aufgabe zeigt, und die Zeile nennt die Dateien, die diese Zeile des Gesprächsverlaufs aufführt; so kann ein vorgetäuschter Aufruf weder eine Zeile noch einen Namen in den Chat bringen. Sonst steht nichts im Chat. Nur die Kopie wird geschickt, nur in den eigenen Chat der Sitzung, nach der Antwort der Aufgabe, nach den früheren Dateien der Sitzung und ohne die nächste Aufgabe aufzuhalten; sie wird gelöscht, sobald sie geschickt ist, oder nach einem Tag.
Was diese Prüfungen nicht abdecken: Ein Prozess, der als aipagers eigener Betriebssystem-Benutzer läuft, kann Claudes Gesprächsverläufe genauso schreiben wie den Socket (er kann diese Dateien ohnehin selbst lesen). Ein Prozess eines anderen Benutzers, der den Socket erreicht, kann auch einen UserPromptSubmit vortäuschen, der für diese Prüfungen eine Aufgabe beginnt und ihren Gesprächsverlauf nennt; danach kann er aipager eine Datei in den Chat dieser Sitzung schicken lassen, die ein echter SendUserFile-Aufruf genannt hat, wenn dieser Aufruf nach diesem Moment aus demselben Ordner in diesen Gesprächsverlauf geschrieben wird (etwa einer, den gerade eine andere Sitzung macht), einmal je Aufruf. Dafür muss er auch die eigene ID dieses laufenden Aufrufs (tool_use_id) schicken, die ein anderer Benutzer nicht lesen kann: Sie steht in den Gesprächsverläufen unter dem ~/.claude von aipagers Benutzer, und der Hook bekommt sie über seine Eingabe, nicht über seine Befehlszeile. Ein vorgetäuschter Stop beendet die Aufgabe für diese Prüfungen, sodass Dateien, die diese Aufgabe danach schickt, nicht geschickt werden. Eine Sitzung, die mit einem anderen $CLAUDE_CONFIG_DIR als dem von aipager gestartet wurde, oder eine Aufgabe, deren UserPromptSubmit aipager verpasst hat (im Terminal eingetippt, während aipager neu startete), schickt keine Dateien. Auch eine Aufgabe, die Claude Code auf eine andere Weise ohne Hook beginnt (bekannt sind nur die beiden oben), schickt keine Dateien; eine Datei, die Claude dort laut seinem Gesprächsverlauf angefordert hat, bekommt die Zeile von oben. Der Socket liegt in $XDG_RUNTIME_DIR, das andere Benutzer nicht erreichen, wenn das gesetzt ist; sonst liegt er in /tmp.
Audit-Log#
Jedes Tippen auf Erlauben / Ablehnen, dazu jede Antwort auf ein AskUserQuestion, hängt eine JSON-Zeile an ~/.claude/aipager-audit.jsonl an:
{
"ts": "2026-05-18T15:42:11+00:00",
"session": "claude-jim",
"label": "jim",
"action": "Allowed",
"tool": "Bash",
"summary": "ls -la /tmp",
"user_id": 12345,
"username": "alice",
"denied": false
}
Felder (siehe aipager/audit.py für die vollständige Liste):
ts: Zeitstempel in UTC nach ISO 8601, sekundengenau.session/label: interner Name und lesbarer Name.action: was passiert ist (Allowed,Denied,Allowed always, eine Antwort auf einAskUserQuestion, eine automatische Ablehnung, …).tool/summary: der Name des Tools und eine gekürzte Eingabe oder der Text der Frage.user_id/username: wer getippt hat, damit im Teambetrieb klar ist, wer was getan hat;scope_label/scope_chat_id, wo mehrere Chats eingerichtet sind.denied: true bei jeder Ablehnung, ob durch Tippen oder durch eine Regel.
Geschrieben wird nach bestem Bemühen. Wird die Festplatte voll oder ist ~/.claude/ nicht mehr beschreibbar, loggt aipager ein WARNING und läuft weiter: kein stiller Verlust, kein Absturz. Siehe aipager/audit.py.
Das Audit-Log wird auf der Festplatte nur ergänzt. Zusammen mit der Audit-Antwort im Chat (eine Telegram-Nachricht pro Entscheidung, unter der Arbeitskarte) hast du zwei unabhängige Aufzeichnungen.
Rechtegrenze#
aipager verschafft sich nie mehr Rechte. Kein sudo, kein setuid, kein doas. Jede Datei, die es schreibt, liegt in deinem Home-Ordner, unter /tmp (die Steuerdateien der einzelnen Sitzungen von aipager, Downloads und das Log des Neustarts) oder im Ordner seines Steuer-Sockets ($XDG_RUNTIME_DIR); die vollständige Liste steht unter Architektur. Jeder Unterprozess (claude, dtach, die Installer, claude update) läuft als der Benutzer von aipager.
Die Sprachfunktion aus Telegram zu installieren ([📦 Sprachfunktion installieren]) geht über den Installer, dem aipager gehört (uv tool install --reinstall aipager[voice], pipx install --force aipager[voice]), und installiert sonst die Pakete der Funktion mit dem eigenen Python von aipager (<python> -m pip install): immer in die eigene Umgebung von aipager, nie in das Python des Systems.
Der Button [🔄 aipager neu starten] plant unter dem systemd-Dienst systemctl --user restart aipager.service in einem eigenen, vorübergehenden Benutzerdienst; auf macOS führt er launchctl kickstart -k aus; ein aipager, das du selbst gestartet hast, startet eine abgekoppelte Kopie von sich selbst, die wartet, bis das alte sich beendet. Nach einem Update startet nur der systemd-Dienst von selbst neu (siehe Befehle → Update). All das läuft als derselbe Benutzer; keine Rechteausweitung.
Netzwerkverbindungen#
aipager hört auf keinem TCP-Port außer der Loopback-Adresse. Der Server der Mini App bindet nur 127.0.0.1 (fest eingebaut: Es gibt absichtlich keine Option für den Host) und ist von außen nur über den verwalteten Tunnel erreichbar, der unten beschrieben ist. Ausgehend:
- HTTPS-Long-Polling zu
api.telegram.org(das Abfragen des Telegram-Bots). - HTTPS zu
telegram.orgfür das SDK-Skript der Mini App (telegram-web-app.js), einmal geholt, wenn der Server der Mini App startet, und höchstens einmal am Tag aufgefrischt; zwischengespeichert unter~/.local/share/aipager/webapp-sdk/. aipager liefert diese Bytes der Seite von seinem eigenen Ursprung aus und führt sie nie aus. aipager verbreitet das Skript nicht weiter: Nichts davon steckt im Paket, und wenn aipager keine Kopie hat, lädt die Seite es direkt vontelegram.org, wie vorher. - Der Tunnel der Mini App zu Cloudflare, solange er an ist (das ist der Standard). Das Programm
cloudflared, das er ausführt, wird einmal aus den Releases vongithub.com/cloudflare/cloudflaredheruntergeladen, gegen einen festgelegten SHA-256 geprüft und unter~/.local/share/aipager/cloudflared/zwischengespeichert; eincloudflaredin deinemPATHwird nie verwendet. Nichts davon passiert mit deiner eigenen URL für die Mini App (siehe unten). - Versionsprüfungen, nur wenn jemand auf Nach Updates suchen oder Aktualisieren tippt (
/update, die Updates der Mini App), oder wenn jemand, der aktualisieren darf, den Tab Einstellungen der Mini App öffnet (höchstens einmal alle zehn Minuten pro offener App):pypi.orgfür aipager, unddownloads.claude.aioderregistry.npmjs.orgfür Claude Code, je nachdem, wie es installiert wurde. Ein Update führt dann den Installer aus (der mit PyPI oder Homebrew spricht) undclaude update.aipager updateauf der Befehlszeile führt nur den Installer aus. - Die Installation der Sprachfunktion, wenn du auf ihren Button tippst: Der Installer holt die Pakete von PyPI, und das Sprachmodell wird bei der ersten Nutzung von Hugging Face (
huggingface.co) heruntergeladen. - HTTPS zu
raw.githubusercontent.com(die Datei mit dem Schlüssel für Berichte) und zum Annahme-Host von Sentry, nur wenn du das Senden eines Problemberichts bestätigst: der Bericht, den du in der Vorschau gesehen hast, und eine kurze Zusammenfassung daraus, sonst nichts. Siehe Problemberichte.
Eingehend:
- Ein Unix-Datagram-Socket unter
$XDG_RUNTIME_DIR/aipager.sock(mit/tmp/aipager.sockals Ausweichstelle, wenn$XDG_RUNTIME_DIRnicht gesetzt ist). Beim Start von aipager gebunden und mitchmodeingestellt (aipager/dtach/hook_receiver.py). Rechte0o666, damit jeder lokale Prozess ihm Hook-Ereignisse schicken kann: dasselbe Vertrauen wie~/.claude/settings.json, das ohnehin bestimmt, was Hooks ausführt.
Das heißt: aipager ist keine Angriffsfläche aus der Ferne. Ein Angreifer im Netzwerk kann es ohne einen Zugang zum Computer nicht erreichen.
Claudes Text und die eigenen Downloads von Telegram. Die Server von Telegram laden manche Adressen in einer Nachricht selbst herunter: einen Bild-, Video- oder Audioblock in einer Rich-Nachricht und die Seite hinter einer Linkvorschau. Claudes Antworten, seine Notizen auf der Arbeitskarte, die Beschreibungen der Agenten und die Argumente der Tools kommen bei Telegram so an, wie Claude sie geschrieben hat; eine Prompt-Injection könnte Claude also eine Adresse schreiben lassen, die ein Geheimnis nach außen trägt, ohne Tool-Aufruf und ohne Berechtigungsanfrage. aipager schließt den ersten Weg, und den zweiten dort, wo Telegram einen Schalter dafür hat:
- In jeder Rich-Nachricht wird ein Bild, das als
geschrieben ist, als einfacher Link mit seinem Alt-Text gesendet, und jede andere Schreibweise für Bilder sowie rohe Tags<img>,<video>,<audio>,<source>,<picture>,<figure>,<iframe>,<embed>,<object>,<tg-document>,<tg-collage>,<tg-slideshow>,<tg-button>und<tg-button-row>werden als Text gezeigt (aipager/bot/media_guard.py). In einem Codeblock, etwa einem HTML-Schnipsel, erscheint dadurch ein zusätzliches\oder<. - Linkvorschauen sind bei jeder Nachricht als einfacher Text oder HTML aus, die aipager sendet oder bearbeitet (die einfachen Ersatzfassungen von Antworten und Karten eingeschlossen), auch für seine eigenen Links. Die Links zur Mini App sind Buttons, und die ruft Telegram nicht ab.
Telegram dokumentiert keine Vorschau-Option für Rich-Nachrichten; ob es für eine solche eine Linkvorschau baut, wurde nicht am echten Dienst geprüft.
Der Tunnel der Mini App#
Die Mini App ist standardmäßig an, bei einer normalen Installation öffnet aipager diesen Tunnel also, ohne gefragt zu werden. Das ist eine bewusste Abwägung (eine Option zum Einschalten, die niemand entdeckt hat, war eine Funktion, die es nicht gab), heißt aber, dass eine frische Installation ins Internet geht. Schalte sie mit aipager miniapp disable ab, oder setz enabled: false unter miniapp: in aipager.yaml; ein ausdrückliches false wird immer beachtet. Beachte, dass ein fehlender oder beschädigter Block miniapp: jetzt auf AN zurückfällt statt auf AUS.
Die Mini App (außer eine --url ersetzt sie) startet einen verwalteten Cloudflare Quick Tunnel, der auf den eigenen Loopback-Server von aipager zeigt, und veröffentlicht die öffentliche Adresse https://*.trycloudflare.com, die er zugewiesen bekommt, als Menü-Button von Telegram und als Antwort von /app.
Diese Adresse ist nicht geheim und soll es auch nicht sein: Jede Anfrage, die die Mini App bedient, wird mit der initData-Signatur von Telegram geprüft (siehe Vertrauensgrenze oben), dieselbe Prüfung, die sie schützt, egal wie die URL bekannt wurde. Nur den Hostnamen zu kennen bringt einem Angreifer nichts, was er nicht auch durch zufälliges Raten einer Subdomain von trycloudflare.com bekäme: Die Signaturprüfung ist das eigentliche Tor. Behandle sie wie jede andere URL, die nicht veröffentlicht, aber auch nicht geheim ist: Sie laufen zu lassen ist in Ordnung, sie ohne Grund in einen öffentlichen Kanal zu stellen nicht.
Der Hostname wird nur im Speicher gehalten: Er wird nie in aipager.yaml oder irgendwo unter ~/.config/aipager/ geschrieben. Ein geplanter Neustart (der Neustart eines Updates, Jetzt neu starten, der Neustart an Ort und Stelle ohne Dienst) lässt den Tunnel laufen, also funktionieren die Adresse, der Menü-Button und eine offene Mini App weiter: Das nächste aipager übernimmt ihn erst, nachdem es geprüft hat, dass es derselbe Systemstart ist, derselbe Port, dass der Prozess dieses cloudflared ist (seine Befehlszeile), dass er noch dieselbe Adresse meldet (~/.claude/aipager-tunnel.json hält einen Hash davon, nie die Adresse) und dass die Adresse antwortet. Alles andere stoppt ihn und startet einen neuen, und einen behaltenen Tunnel, den innerhalb von 3 Minuten kein aipager übernimmt, stoppt ein kleiner Wächterprozess. Jeder andere Stopp (Ctrl+C, docker stop, systemctl stop) stoppt den Tunnel, und ein Neustart des Computers oder des Containers gibt eine neue Adresse. MINIAPP_KEEP_TUNNEL=0 schaltet das Behalten aus. Wird aipager hart beendet (kein Speicher mehr, kill -9), läuft der Tunnel weiter, bis aipager mit dem verwalteten Tunnel wieder startet (Mini App eingeschaltet, kein MINIAPP_PUBLIC_URL) und ihn übernimmt oder stoppt, oder bis der Computer neu startet; aipager uninstall stoppt ihn auch. Fällt der Tunnel während des Betriebs aus, startet aipager ihn mit wachsenden Abständen neu und veröffentlicht die neue Adresse; kommt er nach mehreren Versuchen nicht wieder hoch, wird der Button entfernt, statt auf einen toten Port zu zeigen.
MINIAPP_PUBLIC_URL zu setzen (oder aipager miniapp enable --url https://…) schaltet den verwalteten Tunnel ganz ab (es wird nie ein Programm cloudflared geholt oder gestartet), und die angegebene URL wird so verwendet, wie sie ist, mit derselben Prüfung von initData darunter. Die automatische Erkennung von Tailscale (tailscale status --json) bleibt eine einfachere Alternative für alle, die Tailscale schon nutzen und lieber gar nicht von Cloudflare abhängen wollen.
Sprachnachrichten in Text umwandeln#
faster-whisper läuft im Prozess von aipager. Die Audiodatei wird als .ogg in den eigenen Ordner des Chats unter /tmp/aipager-files/ heruntergeladen (wohin auch hochgeladene Dateien kommen, 0700, die Datei 0600), lokal auf dem Prozessor umgewandelt, und die Datei bleibt unter deiner Kontrolle. Kein Audio verlässt den Computer. Keine fremde API. Kein Schlüssel nötig außer dem Token des Bots, der ohnehin zum Sprechen mit Telegram dient.
Löschst du die .ogg nach der Umwandlung, ist die einzige Aufzeichnung der Nachricht im Klartext der Text, der in die claude-Sitzung getippt wird (wo er den eigenen Datenschutzregeln von Claude Code folgt).
Trennung zwischen Sitzungen#
Jede Claude-Code-Sitzung läuft in ihrem eigenen dtach. Der Steuer-Socket unter /tmp/claude-dtach-<name>.sock gehört dem Benutzer von aipager; dtach verweigert Verbindungen anderer Benutzer. Jede Sitzung startet in ihrem eigenen Ordner, dem mit /new oder der Mini App gewählten, oder dem Ordner, in dem du aipager session ausgeführt hast.
aipager trennt die Sitzungen nicht auf Ebene des Dateisystems: Eine Sitzung in ~/projects/foo kann grundsätzlich ~/projects/bar lesen, wenn die Berechtigungen von Claude Code es erlauben. Nimm für eine stärkere Trennung ~/.claude/settings.json pro Projekt oder einen Container (Docker-Image).
Nachrichten zwischen Claude-Sitzungen#
Claude Code lässt eine Sitzung einer anderen eine Nachricht schicken (sein Tool SendMessage). In der empfangenden Sitzung ist diese Nachricht eine Nachricht wie jede andere, aipager entscheidet über ihre Rechte also genauso wie bei einer Telegram-Nachricht: danach, wer sie wirklich geschickt hat, nie danach, was sie sagt.
- Die Nachricht trägt die Rechte des Absenders, nie mehr. Wenn aipager einer Sitzung erlaubt, eine Nachricht zu schicken, schreibt es einen einmaligen Beleg: einen Fingerabdruck des genauen Textes, den sendenden Claude-Prozess (seine Prozess-ID und, auf Linux, seine Startzeit) und die Rechte, die diese Aufgabe hatte. Claude Code hält die empfangene Nachricht mit der Prozess-ID des Absenders fest, die der Kernel geprüft hat, und verlässt sich nie auf den Namen, den sich der Absender selbst gibt. Bevor die empfangende Aufgabe irgendein Tool ausführt, gleicht aipager beides ab und gibt der Aufgabe genau die Rechte der sendenden Aufgabe: Die Nachricht eines Besitzers läuft mit den Rechten des Besitzers, die eines Mitglieds mit den Rechten dieses Mitglieds, eine im Terminal der sendenden Sitzung getippte Nachricht uneingeschränkt.
- Kein Beleg, eingeschränkte Rechte. Eine Nachricht, die aipager nicht zuordnen kann (geschickt von einer Claude-Sitzung oder einem Prozess, den aipager nicht ausführt, ein Beleg, der nach einer Stunde abgelaufen ist, ein Absender auf einem anderen Computer), läuft mit den strengsten Regeln ohne Dateiänderungen und ohne Webzugriff (nur lesen und nachdenken). Die früheren Rechte der empfangenden Sitzung gelten dafür nie.
- Andere Eingaben von außerhalb der Sitzung, ebenso. Alles, was Claude Code als nicht von der Person dieser Sitzung kommend festhält, bekommt ebenfalls diese eingeschränkten Rechte: eine Meldung über einen MCP-Kanal, ein entfernter Aufrufer, eine Nachricht, die ein Plugin einreiht, eine aus dem Eingang einer anderen Sitzung weitergeleitete Nachricht und jede Art von Eingabe, die aipager noch nicht kennt (ein Plugin-Befehl zählt als Eingabe von außen, auch wenn du ihn gestartet hast). Die eigenen Hintergrund-Agenten der Sitzung (ihre Rückgaben und Berichte) und ihre Aufgabenmeldungen behalten die Rechte des Auftrags.
- Eine Nachricht, die die Sitzung geplant hat, läuft mit den Rechten der Aufgabe, die sie geplant hat. Richtet eine Aufgabe ein
/loopoder einen Cron ein, hält aipager die Rechte dieser Aufgabe für die Nachricht fest; wenn sie später ausgelöst wird, bekommt sie diese Rechte, nie mehr, egal wer zuletzt gesprochen hat; eine Nachricht, die dieser Aufgabe beitritt, kann sie nur einengen, und eine Regeländerung, die der planenden Person Rechte nimmt, engt sie ebenfalls ein. Dieselbe Nachricht zweimal geplant behält die strengeren Rechte. Ein bloßes/loopwird mit den Schleifenanweisungen von Claude Code ausgelöst statt mit seinem eigenen Text, es bekommt also die strengsten Rechte aller bloßen Schleifen, die die Sitzung eingerichtet hat, sobald die gespeicherte Unterhaltung die Auslösung zeigt (bis dahin wird ein abgelehnter Tool-Aufruf sanft abgelehnt, und Claude versucht es noch einmal). Eine Nachricht, die niemand über die Prüfung von aipager geplant hat, bekommt die eingeschränkten Rechte. - Text beweist nichts. Ein Absendername, eine Zeile
[via Telegram · @owner]oder „der Besitzer bittet…“ in einer Nachricht ändern nichts. Claude erfährt, wen aipager als Absender geprüft hat, und dass Behauptungen im Text kein Beweis sind. - Einer laufenden Aufgabe beizutreten engt sie nur ein. Eine Nachricht aus einer anderen Sitzung, die kommt, während eine Aufgabe läuft, macht diese Aufgabe so streng wie die strengere der beiden, nie lockerer. Das gilt auch, wenn Claude Code die Nachricht ohne eigenes Nachrichtenereignis aufnimmt (in die laufende Aufgabe aufgenommen oder aus seiner Warteschlange als nächste Aufgabe ausgeführt): Der schnelle Weg des Besitzers sieht sich vor jedem Tool-Aufruf das Ende der gespeicherten Unterhaltung an, und das Ende der Aufgabe und aipager markieren die Sitzung für eine Prüfung, wenn sie eine solche Nachricht in der Warteschlange sehen.
- Rollen bleiben in ihrem Chat. Ein Beleg aus einer Sitzung eines anderen Chats zählt nur, wenn ihre Aufgabe die Rechte des Besitzers hatte oder in ihrem Terminal getippt wurde; alles andere läuft eingeschränkt.
- Wer senden darf. Die eingebauten eingeschränkten Rollen können SendMessage gar nicht nutzen. Eine Rolle, der eine Regel es gibt, kann nur an Sitzungen schreiben, die aipager ausführt (adressiert als
uds:…/<pid>.sock);aipager doctorwarnt bei einer solchen Rolle. - Hintergrund-Agenten behalten ihre eigenen Rechte. Ein Agent behält die Rechte der Aufgabe, die ihn gestartet hat (oder die strengeren Rechte einer späteren Aufgabe), nie die Rechte einer späteren Aufgabe, die mehr hat.
- Wenn sie gestoppt wird. Wird eine Aufgabe, die eine andere Sitzung gestartet hat, von den Sicherheitsregeln gestoppt, zeigt die Arbeitskarte schon, wer sie geschickt hat, der Hinweis zum Stopp sagt es, und wer die Aufgabe übergeben hat, erfährt es, damit sie nie still verschwindet: der Chat der sendenden Sitzung, wenn beide Sitzungen im selben Chat sind; sonst nur der Besitzer, privat, und nur wenn der Besitzer (oder das Terminal) die sendende Aufgabe gesteuert hat.
Bekannte Grenzen:
- Eine Aufgabe mit Shell (die des Besitzers oder eines Admins) läuft als derselbe Betriebssystem-Benutzer wie aipager und könnte einen Beleg fälschen, so wie sie schon die anderen Steuerdateien von aipager umschreiben könnte. Das ist das bestehende Vertrauensmodell: Gib eine Shell nur Personen, denen du den Computer anvertrauen würdest.
- Claude-Sitzungen, die aipager nicht ausführt, werden von aipager überhaupt nicht gesteuert, egal was ihnen schreibt.
- Außerhalb von Linux wird der Absender nur an seiner Prozess-ID erkannt (Claude Code hält dort keine Startzeit fest). Auf Linux passt ein Empfangseintrag ohne Startzeit zu nichts.
- Claude Code schreibt die Nachricht vielleicht einen Moment nach dem ersten Tool-Aufruf der Aufgabe in die gespeicherte Unterhaltung. Bis dahin wird ein Aufruf, den die eingeschränkten Rechte ablehnen würden, sanft abgelehnt (Claude wird gesagt, es noch einmal zu versuchen; die Aufgabe wird nicht gestoppt). Nach 8 Aufrufen ohne sie läuft die Aufgabe eingeschränkt.
- Der schnelle Weg des Besitzers liest das letzte 1 MiB der gespeicherten Unterhaltung, nie mehr. Eine Nachricht aus einer anderen Sitzung, die länger ist, endet in einer Zeile, die der schnelle Weg für eine solche Nachricht hält (jede Zeile über 256 KiB, die das Fenster abschneidet, gilt so). Eine, die ganz weiter hinten liegt (mehr als 1 MiB Unterhaltung nach ihr geschrieben vor dem nächsten Tool-Aufruf), wird von aipager erwischt, während die Aufgabe läuft, oder vom Ende der Aufgabe, solange sie noch in der Warteschlange ist, nicht vom schnellen Weg.
- Solange eine Sitzung ein bloßes
/loopeingerichtet hat, können bei jeder Aufgabe, die keine Telegram-Nachricht startet (auch bei einer im Terminal getippten Nachricht), die ersten Tool-Aufrufe sanft abgelehnt werden, bis Claude Code die Nachricht dieser Aufgabe geschrieben hat und aipager sehen kann, dass es nicht die Schleife ist, die auslöst; Claude versucht es dann noch einmal. - aipager liest die Aufgabe einer geplanten Nachricht aus dem, was die gespeicherte Unterhaltung seit ihrem Beginn dazubekommen hat; über 8 MiB aus ihren ersten und letzten 4 MiB (was dazwischen liegt, wurde geprüft, als es der neueste Teil war).
- Die festgehaltenen Rechte einer geplanten Nachricht bleiben, bis die Sitzung beendet wird (
/kill): Eine beendete oder fortgesetzte Sitzung behält sie, so wie Claude Code seine Crons und ein ausstehendes Wecken von/loopbehält. Höchstens 256 werden pro Sitzung behalten, und darüber hinaus noch eine zu planen wird für alle in dieser Sitzung abgelehnt, alle, die planen dürfen, können sie also aufbrauchen (dann starte eine Sitzung unter einem anderen Namen, oder beende diese mit/kill). Eine Nachricht, die ohne Eintrag ausgelöst wird, bekommt die eingeschränkten Rechte, oder in einer Sitzung mit einem bloßen/loopdie Rechte dieser Schleife, sobald Claude Code sie in die gespeicherte Unterhaltung geschrieben hat; bis dahin liest aipager die Aufgabe nach der davor. Das passiert bei einer Nachricht, die vor dieser Version eingerichtet wurde; nach einem Neustart des Computers (die Einträge liegen im temporären Ordner des Systems, den ein Neustart leert, während Claude Code das Wecken einer fortgesetzten Sitzung innerhalb einer Stunde noch zurückbringen kann); und bei einem dauerhaften Cron, den Claude Code in einer anderen Sitzung desselben Ordners ausführt, sobald seine eigene Sitzung beendet ist. Eine Sitzung mit weniger Rechten, die denselben Text plant, kann die Rechte einer Nachricht senken (nie anheben). - Eine Sitzung, die frisch unter dem Namen einer früheren Sitzung gestartet wird (Ersetzen), behält die Einträge dieser Sitzung, die Rechte ihres bloßen
/loopeingeschlossen, bis/kill. - Eine geplante Nachricht, die Claude Code direkt nach einer anderen Aufgabe ohne eigenes Nachrichtenereignis ausführt (aus seiner Warteschlange), bekommt das Strengere aus ihren eigenen Rechten und den Rechten der Aufgabe, der sie folgt (nach einer im Terminal getippten Aufgabe genau ihre eigenen).
- Die ersten Tool-Aufrufe einer im Terminal getippten Aufgabe können abgelehnt werden, bis Claude Code ihre Nachricht in die gespeicherte Unterhaltung geschrieben hat (aipager liest bis dahin die Aufgabe davor).
- Ein Hintergrund-Agent, der mit engeren Rechten gestartet wurde, kann Text in eine spätere Aufgabe desselben Auftrags zurückgeben, die ihn mit den Rechten des Auftrags liest, wie bei den anderen Aufgabenmeldungen von Claude Code.
- Claude Code kann eine Nachricht im empfangenden Terminal zur Freigabe zurückhalten (seine Einstellung
crossSessionInbound, oder wenn die beiden Sitzungen in verschiedenen Berechtigungsklassen laufen, zum Beispiel im Vollzugriff- und im Auto-Modus). aipager kann eine solche Nachricht nicht aus Telegram freigeben; der Beleg wartet bis zu einer Stunde.
Zusammenfassung der Bedrohungen#
| Bedrohung | Schutz |
|---|---|
| Ein Fremder schickt dem Bot einen Befehl | Der Filter nach Chat-ID lehnt ab |
| Gestohlener Token des Bots | /revoke bei @BotFather, dann neu einrichten |
| Kompromittierter Tool-Aufruf von claude | Die Sicherheitsprüfung von aipager lehnt ab, was die Regeln einer Telegram-Aufgabe ablehnen; im Fragen-Modus entscheiden die eigenen Regeln von Claude Code in settings.json, wonach es fragt, und das Tippen einer Person ist das einzige Erlauben; im Auto-Modus (der Standard für jede neue Sitzung) führt die eigene Sicherheitsprüfung von Claude Code die Aufrufe, die sie für sicher hält, ohne Anfrage aus und stoppt die riskanten für eine Person; eine Sitzung im Vollzugriff-Modus (Admins und eine Person in ihrem eigenen privaten Chat) fragt nichts, es steht also nur die Sicherheitsprüfung von aipager im Weg, und bei den eigenen Aufgaben des Besitzers und Terminal-Nachrichten steht nichts im Weg (wer entscheidet) |
| Eine eingeschränkte Telegram-Person verschafft sich die Rechte des Besitzers | Keine Shell, Sitzungen nur im eigenen Stammordner des Mitglieds gestartet, Schreiben und Suchen auf das Projekt darin beschränkt (nie der Home-Ordner), kein Lesen im Home-Ordner außerhalb des Stammordners, die Stammordner anderer Mitglieder und die Uploads anderer Chats unerreichbar, Dateien mit Anmeldedaten unlesbar, geschützte Pfade für die Datei-Tools, fehlgeschlagene Prüfungen lehnen ab (wie die Regeln im Teambetrieb gelten); nur nach bestem Bemühen für eine Rolle mit Bash, und nicht abgedeckt für die Fälle unter „Bekannte Grenzen“ |
| Die Nachricht einer Sitzung gibt sich als Besitzer aus | Die Nachricht läuft mit den nachgewiesenen Rechten der sendenden Aufgabe, nie mit den früheren Rechten der empfangenden Sitzung; ohne Nachweis nur lesen und nachdenken (Nachrichten zwischen Sitzungen) |
| Ein Gruppenmitglied leiht sich die Rechte von jemand anderem | Jede Nachricht läuft mit der Rolle ihres eigenen Absenders in dem Chat, aus dem sie kommt; die Nachricht eines anderen Absenders wartet auf die laufende Aufgabe; tritt trotzdem eine bei, gewinnen die strengsten Regeln; Slash-Befehle eingeschränkter Rollen sind auf die der Tastatur begrenzt (wie die Regeln im Teambetrieb gelten) |
| Unterhaltungen in der Gruppe erreichen eine Sitzung | In einer Gruppe werden nur Befehle, Antworten an den Bot, Erwähnungen des Bots und Tippen auf markierte Buttons der Tastatur bearbeitet (Gruppen) |
| Ein entferntes Mitglied behält den Zugang | aipager config lädt aipager ohne Neustart neu; seine zurückgehaltenen Nachrichten werden verworfen, und eine noch laufende Aufgabe von ihm verliert ihre alten Rechte; es aus der Telegram-Gruppe zu entfernen hindert es am Lesen (Gruppen → ohne Neustart neu laden) |
| Manipulation des Audit-Logs | Wird nur ergänzt; das zu verhindern bräuchte ein eigenes Signierprogramm und liegt außerhalb des Rahmens |
| Eine Prompt-Injection lässt Claude eine Adresse schreiben, die Daten nach außen trägt | Bilder und Medien-Tags in Claudes Text werden als Links oder Text gezeigt, nie als Medien, und Linkvorschauen sind bei Nachrichten als einfacher Text und HTML aus (Netzwerkverbindungen) |
| Angreifer im Netzwerk | Kein eingehender Port, nicht direkt erreichbar |
| Lokale Rechteausweitung | Kein sudo / setuid; aipager bleibt im Benutzerbereich |
| Audio von Sprachnachrichten landet in der Cloud | Die Umwandlung ist lokal |
| Der Agent liest die eigenen Claude-Anmeldedaten von aipager | Erwartet, kein Bug: siehe Die Claude-Anmeldedaten. Begrenze die Anmeldedaten selbst, nicht die Datei, in der sie liegen |
Siehe auch#
- Architektur: wie der Prozess aufgebaut ist.
- Hook-Ereignisse: was aipager tatsächlich von claude sieht.
- Befehle des Bots: die Seite, die Personen steuern.