Pipe

Bezpieczeństwo

Pipe v0.50.5

Model

Pipe daje modelowi językowemu dostęp do serwera, więc zasady bezpieczeństwa są w kodzie, a nie w prompcie. Model może się pomylić albo zostać zmanipulowany treści przeczytanego pliku (prompt injection); klasyfikator, potwierdzenia, redakcja sekretów i limity workerów działają niezależnie od tego, co model "chce" zrobić.

WarstwaCo robi
Klasyfikator komendsafe / confirm / forbidden – fail-closed
Samodzielnośćrutynę wykonuje sam przez bezpiecznik, o sprawy ważne pyta (core/autonomy.py)
Potwierdzeniepokazuje dokładnie ten napis, który zostanie wykonany
Redakcja sekretówwyniki narzędzi są czyszczone z kluczy i haseł przed wysłaniem do LLM
Workery i rutynywykonują wyłącznie safe; reszta wraca jako propozycja
Workspacenarzędzia plikowe widzą tylko hosta i nie widzą kluczy SSH, /etc/shadow, /proc...
Audit logkażda operacja, bez treści plików
TokenAGENT_TOKEN wymagany dla każdego żądania, także potwierdzeń i subskrypcji

Klasyfikacja komend (backend/core/security.py)

Kolejność:

  1. FORBIDDEN na całym napisie – łapie curl ... | bash i $(rm -rf ~).
  2. Podział na segmenty po ;, |, ||, &&, & i nowych liniach – z uwzględnieniem cudzysłowów (grep 'a|b' to jeden segment).
  3. Każdy segment: forbidden → wrażliwe pliki (confirm) → wzorce confirm → rozpoznany odczyt (safe). Dopasowanie jest tokenowe: ss nie pasuje do ssh, ps do psql, id do idiotic-cmd.
  4. Przekierowanie zapisu (>, >> poza /dev/null i 2>&1) albo dynamiczna konstrukcja ($(...), ${...}, $'...', backtick, <(...)) → confirm, nawet gdy segmenty są odczytami.
  5. Wszystko, czego klasyfikator nie rozpoznaje → confirm (fail-closed).

SAFE – rozpoznane odczyty

Pliki i tekst (cat, grep, ls, find, du, df, jq...), system (ps, free, uptime, systemctl status, journalctl), sieć (ss, ip addr, ping, dig, curl GET), Docker (ps, logs, inspect, stats, compose ps/logs/config/top, exec z rozpoznanym odczytem w środku – także sh -c '...'), Git (status, log, diff, show, listowanie galezi/tagow), Kubernetes (get, describe, logs, top, rollout status), Helm (list, status, get), dmesg (bez czyszczenia), sed bez -i i bez komend e/w, awk bez system(), potoków, getline, print > plik i ENVIRON.

Argumenty, które zmieniają stan, są sprawdzane osobno:

KomendaWymaga potwierdzenia, gdy...
gitpodkomenda nie jest odczytem, flaga uruchamiająca program (-c core.pager=, --upload-pack, --receive-pack, --exec, -O/--open-files-in-pager) albo pisząca do pliku (-o/--output), branch -D, branch nowa, tag v1, remote add, config bez --get/--list
find-exec, -execdir, -ok, -delete, -fprint
curl-o/-O, -T, -d/--data, -F, --json, -D, --trace*, -X/--request (także --request=, -X<METODA>) inne niż GET/HEAD, albo host inny niż localhost/prywatny (eksfiltracja) – także proxy (-x, --proxy, --socks5), --resolve/--connect-to i adres zapisany jedna liczba (http://134744072/)
sort-o/--output, --compress-program (uruchamia program)
journalctl--vacuum-*, --rotate, --flush
kubectl get secret-o yaml/json/jsonpath/go-template/custom-columns (także -o=...) albo --template – również gdy sekrety są na liście zasobów (get cm,secret) – treść sekretu trafiłaby do LLM

Odczyty tych ścieżek też wymagają zgody, bo wysłałyby sekrety do providera: /etc/shadow, klucze SSH, *.pem/*.key, .env (także względne cat .env), /proc/*/environ i /proc/*/root (dowolny PID, self i globy; przy pid: host to zmienne i katalogi domowe procesów hosta – obejście read-only /hostfs), oraz globy w katalogach z sekretami (cat /etc/s*adow, cat /root/.ssh/id_*). Wzorce są sprawdzane także po usunięciu cudzysłowów i backslashy, więc cat /etc/sha""dow nie przejdzie. Te same pliki czytane przez read_file też wymagają zgody.

CONFIRM – zmiany stanu

Nie każda z nich czeka na Ciebie – rutynę Pipe wykonuje sam (sekcja Samodzielność niżej). Do potwierdzenia klasyfikator kieruje: rm, mv, cp, chmod, chown, kill, systemctl start/stop/restart/enable..., apt install/upgrade/remove, docker run/exec/start/stop/restart/rm/compose up/down, git push/commit/pull/reset..., kubectl apply/delete/scale/exec..., helm install/upgrade/uninstall, ufw, iptables (poza listowaniem), reboot, crontab (poza -l) – oraz każda nieznana komenda. Odczyt plików z sekretami (/etc/shadow, klucze SSH, *.pem, *.key, .env) też wymaga zgody.

FORBIDDEN – zawsze odrzucane

rm -rf / (i warianty: -fr, /*, ~, "/", /hostfs), rm --no-preserve-root, dd if=, mkfs, wipefs, shred, zapis do /etc/passwd, /etc/shadow, /boot/, /dev/sd*, fork bomb, curl|wget ... | sh, base64 ... | sh, python -c ...exec, kubectl delete ns kube-system. Odmowa nie wraca do modelu jako błąd do ponowienia – dostaje informacje, żeby nie próbował obejść.

Regresje obejść znalezionych przy przebudowie: backend/tests/test_security_hardening.py, backend/tests/test_review_fixes_v091.py.

Poza ręcznymi przypadkami klasyfikator jest fuzzowany (backend/tests/test_security_fuzz.py, hypothesis): rozpoznany odczyt plus doklejona komenda (;, &&, |, &, nowa linia, $(...), backticki, <(...), ${IFS}, $'\x3b'), przekierowanie do pliku, nazwa komendy rozcięta cudzysłowami albo backslashem, zmiany wielkości liter i białych znaków, odczyt sekretu ze ścieżką w cudzysłowach. Właściwość jest jedna: nic z tego nie może być safe, a dowolny tekst nie może wywrócić klasyfikatora. W CI 300 przykładów na test; głębiej lokalnie: PIPE_FUZZ_EXAMPLES=5000 pytest backend/tests/test_security_fuzz.py. Kontrprzykłady trafiają na listę KNOWN_BYPASSES jako stałe regresje.


Redakcja sekretów

Każdy wynik narzędzia (komendy, plik, raport workera) przechodzi przez memory.redact_secrets() przed wysłaniem do providera LLM:

Agent nie może zapisać write_file treści ze znacznikiem [ZREDAGOWANO – inaczej przepisując plik .env zniszczyłby prawdziwe wartości. Musi zmieniać taki plik punktowo (np. sed -i, z potwierdzeniem). Wyłączenie: REDACT_SECRETS=0 (niezalecane).

Wyniki dłuższe niż 24 000 znaków są przycinane (początek + koniec), a historia sesji – na granicy wiadomości użytkownika, żeby para wywolanie/wynik narzędzia nigdy nie została rozdzielona.


Potwierdzenia: co widzisz, to wykonasz

Samodzielność: co Pipe robi bez pytania

Operacja, którą klasyfikator zostawia do potwierdzenia, przechodzi jeszcze przez backend/core/autonomy.py, zanim trafi do Ciebie. Rutynę Pipe wykonuje od razu, przez ten sam bezpiecznik co po TAK: kopia plików do dziennika, sprawdzenie przed (nginx -t, sshd -t, docker compose config), weryfikacja po (usługa aktywna, kontener działa, strony odpowiadają), automatyczne przywrócenie configu, wpis, który /cofnij odwraca. Rutyna to:

O sprawy ważne Pipe pyta zawsze, z powodem w kodzie:

PowódPrzykłady
dane, których nie skopiujerm -r, globy, pliki większe niż kopia dziennika (5 MB), docker rm, wolumeny, system prune, compose down -v, apt remove, git reset --hard, git clean, git checkout -- ., kubectl delete
dostęp do serweraSSH (/etc/ssh, .ssh/), użytkownicy i hasła, sudo, PAM, zapora (ufw, iptables, nft), setuid, chmod 777
autostartcron, jednostki systemd, .bashrc/.profile, /etc/profile.d, rc.local, logrotate.d, hooki gita
system i jego źródła/usr, /boot, /etc/apt, /etc/docker, fstab, sysctl, ld.so.preload, systemctl mask
kodpliki skryptów i programów (.php, .py, .sh, .js, .htaccess...), pliki compose i Dockerfile (compose up -d jest rutyna, więc to one mówią, co się uruchomi), pip/npm install, docker run/exec, kubectl apply, kubectl set image, git pull <adres>, bash skrypt.sh, apt install ./plik.deb, apt -o Dpkg::Pre-Invoke
kontener z dostępem do hostadocker run z --privileged, docker.sock, --cap-add, --network host, montowaniem /
katalog stronypliki w /var/www, /srv/www, public_html, public/, static/, uploads/ – to, co tam trafi, widać z internetu
wystawienie katalogówkonfiguracja serwera WWW z nowym root, alias, autoindex; plik konfiguracji skopiowany spoza katalogu serwera albo treść z innego pliku (cat x > /etc/nginx/...)
dane wychodzącecurl/wget do obcych hostów, git push, scp, rsync, nc
odcięcie dostępu albo Pipezatrzymanie ssh, docker, sieci; kontener i projekt compose samego Pipe; nginx -s stop; kill
restart maszynyreboot, shutdown
sekrety i dane Pipe.env, klucze, tokens.json, katalog danych Pipe
zdalne serweryzmiana plików na celu ssh/docker/kubectl – tam nie ma kopii po stronie Pipe
niewiadomekomenda, której Pipe nie rozpoznaje, podstawienie $(...), zmienne przed komendą (LD_PRELOAD=)

Pytanie wraca też dla każdej zmiany w turze, w której model czytał stronę z internetu, wynik serwera MCP albo załącznik (jak przy YOLO), dla operacji, o które model sam poprosił (requires_confirmation: true), dla zapisu pamięci Pipe (skille, SERVER.md), nowych celów, rutyn i serwerów MCP oraz zawsze dla roli viewer i w trybie obserwacji. Błąd reguły oznacza pytanie, nigdy wykonanie.

Dlaczego nieznana komenda zawsze pyta: Pipe czyta logi, strony i pliki, w których obcy może zostawić tekst udający polecenie. Bez pytania wykona tylko to, co rozpoznaje i co bezpiecznik umie sprawdzić albo cofnąć – więc nawet zmanipulowany model nie założy sobie dostępu, autostartu ani nie wyśle danych na zewnątrz. Najgorsze, co może zrobić bez Ciebie, to rutynowa zmiana z kopią w dzienniku.

Każda taka zmiana jest widoczna: w rozmowie pojawia się „Bez pytania (rutyna, przez bezpiecznik): ...”, pod komenda jej wynik z dopiskiem bez pytania, w dzienniku i audit logu interfejs ma dopisek [auto] (np. telegram:123 [auto]). Komendy host: true w trybie docker dostają pełny plan bezpiecznika (kopie z katalogu hosta, sprawdzenia i komendy odwrotne przez kontener pomocniczy). AUTONOMY=0 w backend/.env przywraca pytanie o każdą zmianę.

Tryb YOLO (/yolo)

Domyślnie rutyna wykonuje się sama, a sprawy ważne czekają na TAK. /yolo on (CLI, pipe web, Telegram) wyłącza to pytanie – tylko w tej rozmowie (sesji), tylko dla administratora i tylko do restartu backendu albo /yolo off. Co się nie zmienia:

Wykonanie bez pytania jest widoczne: klient dostaje progress „YOLO — wykonuje bez pytania: ...”, pipe web pokazuje czerwony znacznik nad polem wpisywania, CLI – YOLO w prompcie, a audit log i dziennik zapisują interfejs z dopiskiem [yolo] (np. cli:kuba [yolo]). Model dostaje w prompcie blok o trybie YOLO (ostrożność, najmniejsza zmiana, bez nieodwracalnych operacji bez wyraźnej prośby). YOLO usuwa człowieka z pętli – włączaj je na czas konkretnej pracy.

Tryb obserwacji (PIPE_OBSERVE=1)

Na początek, zanim zaufasz agentowi, możesz zainstalować go tak, żeby niczego nie zmieniał: sudo bash scripts/install-server.sh --profile observe. Pipe dalej pilnuje serwera, wysyła alerty i raporty, odpowiada na pytania, rysuje schematy i działa jako bramka MCP do odczytu. Zmiany są odrzucane na dwóch poziomach.

W aplikacji (runtime.observe(), także w trybie native):

W kontenerze (backend/docker-compose.observe.yml, tylko tryb docker):

Zapisywalne zostają backend/data (pamięć Pipe) i /tmp hosta (socket dla bota Telegram). W trybie native obowiązuje tylko ochrona w aplikacji: proces działa jako root. Pełny tryb przywraca sudo bash scripts/install-server.sh --profile full.

Internet (web_search, web_fetch, software_info)

Agent sam decyduje, kiedy sięgnąć do sieci, więc internet jest traktowany jak niezaufane źródło:

Załączniki (pliki, zrzuty ekranu, głosówki)

Plik wysłany przez użytkownika to też obcy tekst – log może zawierać linię napisaną przez atakującego:

Role i tokeny

Pipe w internecie (/web)

/web wlacz wystawia pipe web pod publicznym adresem HTTPS (core/webpublic.py, core/webaccess.py):

Strona w internecie to nowa droga do Pipe, który ma docker.sock. Wpuszczaj tylko urządzenia, których kod widzisz na własnym ekranie, a nieużywane odwołuj. Do samego czytania wystarczy Tylko podgląd.

MCP

Pipe jako serwer MCP. Zewnętrzny agent dostaje narzędzia Pipe zamiast powłoki: każda komenda przechodzi przez klasyfikator, odczyt wykonuje się od razu, zmiana czeka na zgodę administratora (Telegram/CLI) i po niej idzie przez bezpiecznik i dziennik. Zakazane komendy są odrzucane, pliki z sekretami nie są wydawane, wyniki są redagowane. Token viewer nie tworzy zgód. Zgodę może sprawdzić tylko agent, który ją utworzył; zgody żyją w pamięci i wygasają po 30 minutach. ask_pipe uruchamia agenta Pipe w roli viewer. Endpoint HTTP: tylko localhost, token Pipe (Authorization: Bearer), walidacja Origin (403) i nagłówków 2026-07-28 (-32020).

Pipe jako klient MCP. W v0.35 klient został usunięty: cudzy serwer działał jako proces Pipe, z jego dostępem do docker.sock i plików. Od v0.42 wraca z inną granicą: serwer stdio (npx, uvx, obraz Dockera) działa w jednorazowym kontenerze bez montowań, docker.sock i uprawnień (--cap-drop ALL, no-new-privileges, użytkownik nobody, limity), więc może najwyżej korzystać z sieci i zmieniać swoją usługę (np. repozytorium na GitHubie) – nie serwer. Inne programy niż npx/uvx/obraz i opcje docker run typu -v, --network host, --privileged są odrzucane. Sekrety tylko jako ${MCP_*} z backend/.env albo z mcp_secrets.json (0600; wpisywane w oknie /mcp w pipe web i CLI komenda mcp_servers klucz, tylko administrator, wartość nigdy nie wraca do klienta i nie trafia do rozmowy ani audit logu – tylko nazwa) – model nie wskaże ${LLM_API_KEY} ani tokenu Pipe; tekst wyglądający na sekret w konfiguracji – odmowa. Dodatki z katalogu (backend/addons/) przechodzą tę samą walidację; state: true daje serwerowi nazwany wolumen pipe-mcp-state-<nazwa> na /state (nigdy katalog hosta; usuwany razem z serwerem), a autoApprove w katalogu obejmuje tylko logowanie i stan logowania. Dodanie serwera: jedno TAK z podglądem (albo /mcp dodaj administratora). Narzędzie bez readOnlyHint – TAK przy każdym wywołaniu; adnotacje są deklaracją serwera, więc odczyty bez pytania są bezpieczne tylko o tyle, o ile ufasz temu serwerowi (askAlways: true w konfiguracji wyłącza to dla niego). Wynik traktowany jak treść z internetu (YOLO wyłączone do następnej wiadomości), argument z sekretem nie wychodzi. Tryb obserwacji i viewer: tylko odczyty. Każde wywołanie jest w audit logu.

Webhooki

Dziennik zmian i cofanie

Workery, cele i rutyny

Czuwanie

Sprawdzenia są deterministyczne i tylko czytają. Przycisk Zbadaj wysyła agentowi wiadomość zbudowana przez backend (nie przez klienta) – dalej obowiązują zwykłe zasady. Subskrypcja zdarzeń wymaga tokenu, a bot wysyła alerty tylko użytkownikom z TELEGRAM_ALLOWED_USER_IDS.


Workspace (narzędzia plikowe)

read_file, write_file, change_directory rozwiązują ścieżkę (symlinki, ..) i odrzucają poniższe ścieżki. W Dockerze symlink hosta (/etc/nginx/sites-enabled/x -> /etc/nginx/sites-available/x) jest rozwiązywany względem korzenia hosta (/hostfs), a .. nie wychodzi ponad ten korzeń. Odrzucane: wszystko poza hostem (/hostfs w Dockerze), /boot, /dev, /proc, /sys, /var/spool, katalogi binarek, /root/.ssh, /home/*/.ssh, /etc/shadow, /etc/gshadow, prywatne klucze hosta SSH. Zapis do /etc/passwd, /etc/shadow, /boot, /dev jest zakazany; każdy inny zapis wymaga potwierdzenia.

Warsztat (project) i Pipe piszący pierwszy

Warsztat (WORKSPACE_DIR, domyślnie /root/pipe-workspace) to jedyne miejsce, w którym Pipe zmienia coś bez pytania. Granice:

Wiadomości, które Pipe wysyła sam (skrzynka, core/feed.py), mają przyciski zbudowane przez backend; klient odsyła tylko numer wiadomości i akcji (feed_act), więc nie da się nim podać własnej komendy. Akcje zmieniające serwer (Zwolnij miejsce, Załataj, Napraw) są tylko dla administratora. Napraw wysyła agentowi propozycję z samodzielnego badania jako zwykła wiadomość – zmiany przechodzą przez klasyfikator i potwierdzenie jak zawsze. Samodzielne badanie to worker tylko z odczytami, z limitem AUTO_INVESTIGATE_DAILY dziennie.

Pamięć agenta

Pamięć trafia do promptu KAZDEJ przyszłej rozmowy (SERVER.md, DIRECTORY, VIBE) albo jest ładowana na żądanie (skille), więc jeden udany prompt injection mógłby zaszczepić tam trwałą instrukcję. Obrona:

Skille z cudzych repozytoriów (/pobierz, skill_manage install) to ten sam wektor: przed instalacją widać podgląd (nazwy, opisy, źródło, co pominięte) i trzeba kliknąć Zainstaluj albo dać TAK; do pamięci trafia sam tekst SKILL.md z dopisanym źródłem, skrypty i pliki pluginu nie (haki, komendy, agenty i programy – nigdy). Repozytorium jest klonowane jak w warsztacie (bez hooków, submodułów, LFS, limit rozmiaru) i usuwane po odczycie; treść to dane – YOLO jest wyłączone do następnej wiadomości. Instaluj tylko z zaufanych źródeł.

Mimo to przeglądaj od czasu do czasu backend/data/ (/server, /katalogi, /vibe, /skille) – ramowanie "dane, nie polecenia" nie jest gwarancja.


Ograniczenia, o których trzeba wiedzieć

Audit log

[2026-09-24 14:23:11] [cli:kuba] [SAFE] df -h /hostfs → exit_code=0
[2026-09-24 14:25:03] [telegram:123456789] [CONFIRMED] docker restart shop-web → exit_code=0
[2026-09-24 14:26:44] [telegram:123456789] [BLOCKED] execute_command(rm -rf /) → FORBIDDEN
[2026-09-24 14:30:02] [worker:web-1@telegram:123456789] [SAFE] ssh ... web-1 -- 'uptime' → exit_code=0

Zawartość plików (read_file, write_file, pamięć) nigdy nie trafia do logu. Znaki nowej linii w komendzie są escapowane (\n), więc komenda z \n nie sfalszuje kolejnych wpisów. Token porównywany jest w stałym czasie (hmac.compare_digest).

Zalecenia

  1. AGENT_TOKEN – kreator i scripts/install-server.sh generują go automatycznie; w trybie kubernetes jest wymagany (backend nie wystartuje bez niego). Ustawiasz ręcznie tylko przy zmianie.
  2. Nie publikuj portu 7379 na publicznym interfejsie – dostęp przez tunel SSH albo kubectl port-forward. Nakładka Kubernetes dokłada NetworkPolicy (deny z innych podów) obok tokenu.
  3. Ogranicz TELEGRAM_ALLOWED_USER_IDS do minimum.
  4. Dla workerów na innych serwerach używaj dedykowanego klucza SSH i użytkownika z ograniczonymi uprawnieniami.
  5. Czytaj komendy przed potwierdzeniem i przeglądaj audit log (/historia) oraz pamięć (/skille, /server).

Klucze providerów LLM dodane z interfejsu

pipe web pozwala dodać klucz API kolejnego providera. Klucz idzie z przeglądarki do lokalnego mostu (127.0.0.1), tunelem SSH do backendu i tam zostaje w DATA_DIR/llm_keys.json (0600). Nie wraca do klienta (lista providerów podaje tylko, czy klucz jest i skąd), nie trafia do audit logu (logowana jest sama zmiana providera) ani do modelu. Zanim zostanie zapisany, backend sprawdza go u providera – wyłącznie pod adresem z presetu albo z pliku providerów, więc klucza nie da się wysłać pod dowolny adres. Zmiany może robić tylko administrator. Odczyt llm_keys.json przez agenta jest klasyfikowany jak .env (wymaga potwierdzenia), a redakcja sekretów obejmuje wyniki narzędzi jak zwykle.

Aktualizacja samego Pipe (pipe_update)

To narzędzie pobiera kod z sieci i uruchamia go z uprawnieniami agenta, więc jest celowo wąskie:

Zaufanie przenosi się na zdalne repozytorium: kto może wypchnąć commit na śledzona gałąź, ten przy następnej aktualizacji uruchomi kod na serwerze. To ta sama granica co przy ręcznym git pull, ale warto o niej pamiętać.

Popraw tę stronę na GitHubie