[{"data":1,"prerenderedAt":447},["ShallowReactive",2],{"blog-de-der-daemon-ist-kein-backdoor-er-ist-nur-ein-weiterer-ape":3,"header-blog-translations-/de/blog/der-daemon-ist-kein-backdoor-er-ist-nur-ein-weiterer-ape":444},{"id":4,"title":5,"author":6,"body":7,"date":426,"description":427,"draft":428,"extension":429,"image":430,"meta":431,"navigation":432,"path":433,"seo":434,"stem":435,"tags":436,"translationKey":442,"__hash__":443},"blog_de/blog/de/der-daemon-ist-kein-backdoor-er-ist-nur-ein-weiterer-ape.md","Der Daemon ist kein Backdoor, er ist nur ein weiterer Ape","Patrick Hofmann",{"type":8,"value":9,"toc":419},"minimark",[10,23,45,50,66,69,73,86,97,104,108,111,129,303,309,316,334,338,347,367,371,374,381,384,387,415],[11,12,13,14,18,19,22],"p",{},"Der Nest-Daemon ist ein Control-Plane: ein langlebiger Prozess, der pro Maschine läuft, Agenten supervidiert und Spawn-Intents entgegennimmt. Ich habe ihm einen WebSocket-Handler gebaut, der sich beim ",[15,16,17],"code",{},"troop","-SP anmeldet, damit der Server Spawn-Befehle pushen kann statt alle fünf Minuten gepollt zu werden. Der Handler rief ",[15,20,21],{},"ensureFreshIdpAuth()"," auf — hol das frische IdP-Token, schick es über den Socket. In der Plan-Phase sah das sauber aus. Beim ersten echten Start lief es nie.",[11,24,25,26,29,30,33,34,37,38,40,41,44],{},"Der Daemon läuft als ",[15,27,28],{},"_openape_nest",", ein versteckter Service-User, ",[15,31,32],{},"HOME=/var/openape/nest",". Da liegt kein ",[15,35,36],{},"auth.json",". ",[15,39,21],{}," hatte nichts zu holen, weil dort nie jemand ",[15,42,43],{},"apes login"," ausgeführt hat. Es gibt keinen Menschen in diesem HOME.",[46,47,49],"h2",{"id":48},"wie-es-vorher-gedacht-war","Wie es vorher gedacht war",[11,51,52,53,57,58,61,62,65],{},"Die Annahme im Plan: der Daemon ist ",[54,55,56],"em",{},"meine"," Infrastruktur. Ich starte ihn, also operiert er mit meiner Identität. Das alte Poll-Modell hat genau so funktioniert — nur dass es das Problem umging, ohne dass mir das auffiel. Der 5-Minuten-Sync rief pro Agent ",[15,59,60],{},"apes run --as \u003Cagent>"," auf. Jeder Agent hat eine eigene DDISA-Identity in seinem eigenen HOME. Der setuid-Sprung in den Agent-User ",[54,63,64],{},"war"," die Auth. Der Daemon selbst brauchte nie ein Token, weil er nie selbst gesprochen hat — er hat immer nur einen Agenten reden lassen.",[11,67,68],{},"Der WebSocket-Pfad hat das nicht. Eine persistente Verbindung zum Server gehört dem Daemon, nicht einem Agenten. Der Daemon muss selbst sprechen. Und in dem Moment, in dem er selbst spricht, braucht er eine Identität — und ich hatte im Plan stillschweigend angenommen, das sei meine. WS-Auth ist ein anderes Modell als der per-agent Sync, nicht eine Variante davon. Diesen Schnitt habe ich übersehen.",[46,70,72],{"id":71},"warum-die-naheliegende-lösung-falsch-ist","Warum die naheliegende Lösung falsch ist",[11,74,75,76,78,79,81,82,85],{},"Der Reflex ist offensichtlich: ",[15,77,43],{}," als ich, das ",[15,80,36],{}," nach ",[15,83,84],{},"/var/openape/nest/"," kopieren, fertig. Funktioniert auch. Genau deshalb ist es gefährlich.",[11,87,88,89,92,93,96],{},"Der Daemon ist langlebig, läuft als systemnaher Service, supervidiert andere Prozesse. Das ist die Stelle in der Architektur, an der ein Owner-Token am wenigsten verloren gehen darf. Und selbst wenn es nicht leakt: jede Aktion, die der Daemon dann ausführt, trägt ",[15,90,91],{},"act:human"," mit meinem Subject. Wenn der Daemon einen Agenten spawnt, steht im Audit-Log, ",[54,94,95],{},"ich"," hätte das getan. Das Log lügt dann nicht aus Böswilligkeit, sondern weil die Identität falsch modelliert ist.",[11,98,99,100,103],{},"Ein Control-Plane-Daemon, der das Token seines Owners hält, ist per Definition ein Backdoor. Nicht weil jemand ihn dazu macht — sondern weil die Trust-Annahme ",[54,101,102],{},"\"dieser privilegierte Prozess handelt als ich\""," genau das ist, was eine Sicherheits-Architektur nicht tragen sollte.",[46,105,107],{"id":106},"der-shift","Der Shift",[11,109,110],{},"Der Daemon ist nicht meine Infrastruktur mit meinen Credentials. Er ist selbst ein Ape.",[11,112,113,115,116,118,119,121,122,125,126,128],{},[15,114,28],{}," bekommt eine eigene DDISA-Agent-Identity, eigenes Ed25519-Keymaterial, ein eigenes ",[15,117,43],{}," in seinem eigenen HOME. Der WS-Handler von ",[15,120,17],{}," akzeptiert diese Identität — ",[15,123,124],{},"act:agent"," neben ",[15,127,91],{},", nicht statt:",[130,131,136],"pre",{"className":132,"code":133,"language":134,"meta":135,"style":135},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// troop WS-Handler: beide Akteure sind erstklassig,\n// keiner ist ein Sonderfall des anderen\nconst claim = verifyActClaim(token);\nif (claim.act === \"human\") {\n  // Owner verbindet sich aus dem Browser / der CLI\n} else if (claim.act === \"agent\") {\n  // der Nest-Daemon verbindet sich mit seiner eigenen DDISA-Identity\n} else {\n  return reject(socket, \"unknown actor\");\n}\n","ts","",[15,137,138,147,153,178,213,219,250,256,266,297],{"__ignoreMap":135},[139,140,143],"span",{"class":141,"line":142},"line",1,[139,144,146],{"class":145},"sHwdD","// troop WS-Handler: beide Akteure sind erstklassig,\n",[139,148,150],{"class":141,"line":149},2,[139,151,152],{"class":145},"// keiner ist ein Sonderfall des anderen\n",[139,154,156,160,164,168,172,175],{"class":141,"line":155},3,[139,157,159],{"class":158},"spNyl","const",[139,161,163],{"class":162},"sTEyZ"," claim ",[139,165,167],{"class":166},"sMK4o","=",[139,169,171],{"class":170},"s2Zo4"," verifyActClaim",[139,173,174],{"class":162},"(token)",[139,176,177],{"class":166},";\n",[139,179,181,185,188,191,194,197,200,204,207,210],{"class":141,"line":180},4,[139,182,184],{"class":183},"s7zQu","if",[139,186,187],{"class":162}," (claim",[139,189,190],{"class":166},".",[139,192,193],{"class":162},"act ",[139,195,196],{"class":166},"===",[139,198,199],{"class":166}," \"",[139,201,203],{"class":202},"sfazB","human",[139,205,206],{"class":166},"\"",[139,208,209],{"class":162},") ",[139,211,212],{"class":166},"{\n",[139,214,216],{"class":141,"line":215},5,[139,217,218],{"class":145},"  // Owner verbindet sich aus dem Browser / der CLI\n",[139,220,222,225,228,231,233,235,237,239,241,244,246,248],{"class":141,"line":221},6,[139,223,224],{"class":166},"}",[139,226,227],{"class":183}," else",[139,229,230],{"class":183}," if",[139,232,187],{"class":162},[139,234,190],{"class":166},[139,236,193],{"class":162},[139,238,196],{"class":166},[139,240,199],{"class":166},[139,242,243],{"class":202},"agent",[139,245,206],{"class":166},[139,247,209],{"class":162},[139,249,212],{"class":166},[139,251,253],{"class":141,"line":252},7,[139,254,255],{"class":145},"  // der Nest-Daemon verbindet sich mit seiner eigenen DDISA-Identity\n",[139,257,259,261,263],{"class":141,"line":258},8,[139,260,224],{"class":166},[139,262,227],{"class":183},[139,264,265],{"class":166}," {\n",[139,267,269,272,275,279,282,285,287,290,292,295],{"class":141,"line":268},9,[139,270,271],{"class":183},"  return",[139,273,274],{"class":170}," reject",[139,276,278],{"class":277},"swJcz","(",[139,280,281],{"class":162},"socket",[139,283,284],{"class":166},",",[139,286,199],{"class":166},[139,288,289],{"class":202},"unknown actor",[139,291,206],{"class":166},[139,293,294],{"class":277},")",[139,296,177],{"class":166},[139,298,300],{"class":141,"line":299},10,[139,301,302],{"class":166},"}\n",[11,304,305,306,308],{},"Der Daemon spricht jetzt als er selbst. Spawn-Intents, die über diesen Socket reinkommen, sind nicht ",[54,307,56],{}," Aktionen, die der Daemon durchreicht — sie sind Aktionen des Daemons, mit seinem Subject im Audit-Trail.",[11,310,311,312,315],{},"Bleibt eine Frage: ein Spawn passiert ",[54,313,314],{},"für mich",". Der Daemon hat sein eigenes Token, aber meines ist auf der Maschine nicht erreichbar — und soll es auch nicht sein. Wie handelt der Daemon on-behalf-of-human, ohne mein Token zu haben?",[11,317,318,319,322,323,325,326,329,330,333],{},"Über einen Delegation-Grant. RFC 8693 Token-Exchange ist eigentlich strikt: ",[15,320,321],{},"subject_token"," ist Pflicht, weil der typische Fall ein confidential Client ist, der beide Tokens hat. Unser Fall ist ein anderer — der Daemon hat sein eigenes Token, meines ist unerreichbar. Pragmatik: ",[15,324,321],{}," wird optional, wenn eine ",[15,327,328],{},"delegation_grant_id"," da ist; der IdP leitet den Delegator aus dem Grant ab. Nicht mehr strikt RFC, aber derselbe Effekt — und der HITL-Punkt bleibt da, wo er hingehört: im ",[15,331,332],{},"escapes","-Grant-Flow, nicht in einem kopierten Token. Ich approve eine Delegation einmal, der Daemon agiert in ihrem Rahmen, jeder Schritt ist server-seitig auditbar.",[46,335,337],{"id":336},"was-weggefallen-ist","Was weggefallen ist",[11,339,340,342,343,346],{},[15,341,21],{}," im Daemon-Pfad. Der ganze ",[54,344,345],{},"\"hol Patricks Token, schick es weiter\"","-Codeweg. Es gibt kein durchgereichtes Owner-Token mehr, also gibt es auch keine Stelle, an der es leaken kann.",[11,348,349,350,352,353,355,356,359,360,362,363,366],{},"Stattdessen: einmal ",[15,351,43],{}," als ",[15,354,28],{},", der ssh-Keypair wird bei der Migration zum Service-User mitkopiert, damit die IdP-Identität gleich bleibt — kein Re-Enroll, alle Delegations und Grants persistieren. Ein Stolperstein dabei: das 1h-Token-Expiry trifft, und der Auto-Refresh kann am stale ",[15,357,358],{},"key_path"," im migrierten ",[15,361,36],{}," scheitern. Lösung: nach der Migration explizit ",[15,364,365],{},"apes login --key"," triggern. Pattern ist wiederverwendbar für jeden anderen OpenApe-Daemon, der denselben Weg geht.",[46,368,370],{"id":369},"der-schnitt","Der Schnitt",[11,372,373],{},"Ich wollte dem Daemon mein Token geben. Es wurde: der Daemon kriegt eine eigene Identität.",[11,375,376,377,380],{},"Das ist nicht nur ein Auth-Fix. Es ist ein Hinweis, den ich jetzt überall wiedererkenne. Immer wenn eine Komponente ",[54,378,379],{},"\"privilegierte Infrastruktur\""," sein will — die mit den Owner-Credentials läuft, die als der Mensch handelt, die einen Sonderpfad in der Auth braucht — ist das fast immer ein Symptom, nicht ein Designziel. Es heißt: diese Komponente hat noch keine eigene Identität, und ich habe die Lücke mit meiner gestopft.",[11,382,383],{},"Gib ihr eine eigene, und die Sonderbehandlung verschwindet. Der Daemon ist dann kein Backdoor mehr, der mein Token hält. Er ist nur ein weiterer Ape — gleiches Protokoll, eigenes Schlüsselmaterial, HITL über denselben Grant-Flow wie alle anderen.",[385,386],"hr",{},[11,388,389],{},[54,390,391,392,399,400,403,404,406,407,410,411,414],{},"Code: ",[393,394,398],"a",{"href":395,"rel":396},"https://github.com/openape-ai/openape",[397],"nofollow","github.com/openape-ai/openape",", MIT-lizenziert. Der Nest-Daemon lebt in ",[15,401,402],{},"@openape/nest",", der WS-Handler in der ",[15,405,17],{},"-SP. Der Delegation-only Token-Exchange-Pfad lebt im IdP-Auth-Code (",[15,408,409],{},"exchangeWithDelegation"," in ",[15,412,413],{},"@openape/cli-auth",").",[416,417,418],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":135,"searchDepth":149,"depth":149,"links":420},[421,422,423,424,425],{"id":48,"depth":149,"text":49},{"id":71,"depth":149,"text":72},{"id":106,"depth":149,"text":107},{"id":336,"depth":149,"text":337},{"id":369,"depth":149,"text":370},"2026-05-12","Ich hatte den WS-Handler des Nest-Daemons gebaut, als müsste er mein IdP-Token benutzen. Der Daemon läuft aber als eigener Service-User ohne Zugriff darauf. Die Lösung war nicht, ihm mein Token zu geben — sondern ihm eine eigene Identität.",false,"md",null,{},true,"/blog/de/der-daemon-ist-kein-backdoor-er-ist-nur-ein-weiterer-ape",{"title":5,"description":427},"blog/de/der-daemon-ist-kein-backdoor-er-ist-nur-ein-weiterer-ape",[437,438,439,440,441],"OpenApe","AI Agents","Infrastructure","Security","Building in Public","the-daemon-is-just-another-ape","imPG7w9TQQUxx5iffswLKpQ4QG_ZzpHATjOkQyJxqgs",{"de":445,"en":446},"/de/blog/der-daemon-ist-kein-backdoor-er-ist-nur-ein-weiterer-ape","/en/blog/the-daemon-is-just-another-ape",1779001885440]