Strona główna / Blog / Skyvern: drugi render bez sandboxa

Blog · case study

Sandbox działał — tylko dane zostały wyrenderowane drugi raz już bez sandboxa: Skyvern pokazuje, jak security control ginie pomiędzy etapami pipeline

CVE-2026-82447 w Skyvern to świetny przykład podatności, której nie widać, jeśli review zatrzyma się na pojedynczym komponencie. Pierwsza część pipeline wyglądała bezpiecznie — prompt w TextPromptBlock był renderowany przez sandboxed Jinja environment. Można było powiedzieć: untrusted template data → sandboxed Jinja → safe rendering. Problem polegał na tym, że wynik tego renderowania trafiał później do drugiego renderowania — tym razem przez zwykły jinja2.Environment, bez sandboxa.

workflow parameter / upstream output ↓ first render: SANDBOXED ✓ ↓ rendered string still contains Jinja syntax ↓ second render: UNSANDBOXED ✗ ↓ server-side template injection ↓ arbitrary code execution

CVE ma CVSS v4 8.7 High i obejmuje Skyvern >= 0.2.1, < 1.0.45; fix w 1.0.45 usuwa drugie, niesandboxowane renderowanie. Ale najważniejsza lekcja brzmi: security control może działać poprawnie na jednym etapie i zostać całkowicie unieważniony przez następny etap pipeline.

Co robił TextPromptBlock?

Skyvern buduje workflow'y, w których wartości pochodzą z parametrów workflow, wyników poprzednich bloków albo danych pobieranych podczas automatyzacji, a następnie są wstawiane do promptów. Pierwsze renderowanie wykorzystywało mechanizm ograniczający niebezpieczne możliwości Jinja. To ważne, bo Jinja nie jest zwykłym string formatterem — niewłaściwie użyty template engine staje się interpreterem kodu. Dlatego sandbox jest naturalnym security control.

Ale dane potraktowano jak template drugi raz

VulnCheck opisuje problem precyzyjnie: first render: sandboxed, second render: unsandboxed. Drugi etap korzystał z PromptEngine, który używał zwykłego jinja2.Environment. Wartość, która w pierwszym kroku była tylko danymi, mogła po pierwszym renderowaniu utworzyć nową składnię Jinja: parameter value → contains characters that become template syntax → first pass inserts them literally → second pass interprets them. To klasyczny second-order injection.

Pierwszy sanitizer może działać idealnie i nadal przegrać

Wyobraźmy proces Input → sanitizer → safe value → serialization → parser → execution. Security review sprawdza sanitizer i potwierdza „works correctly”. Ale jeśli downstream ponownie interpretuje wartość w innym kontekście, bezpieczeństwo pierwszej warstwy może nie mieć znaczenia. W Skyvern pierwsze renderowanie nie musiało być zepsute — problemem było, że rendered output became template source again. Wartość zmienia role w pipeline: Stage 1: DATA → Stage 2: OUTPUT → Stage 3: TEMPLATE SOURCE → Stage 4: CODE. Jeśli model zagrożeń śledzi tylko pierwszy etap, uzna ją za bezpieczną. Podobne klasy błędów są w SQL budowanym etapami, Markdown → HTML, YAML → templating, generated code, shell wrappers, LLM prompt pipelines i workflow engines.

Dlaczego to szczególnie ciekawe w systemach AI/agentowych?

W systemach jak Skyvern dane często pochodzą z nieoczywistych miejsc — nie tylko z formularza użytkownika, ale z poprzedniego bloku workflow, strony internetowej, odpowiedzi modelu, wyniku automatyzacji albo zewnętrznego API. Atakujący nie zawsze musi mieć bezpośredni dostęp do pola „prompt” — może kontrolować wcześniejsze źródło danych, które później przepłynie przez workflow:

external content → workflow block output → TextPromptBlock parameter → second Jinja render → RCE

Dlatego agentic systems wymagają data-flow-aware security.

CVSS mówi PR:L — ale kto naprawdę kontroluje dane?

CVSS v4 dla CVE-2026-82447 ma PR:L, co odpowiada scenariuszowi wymagającemu wpływu na workflow parameters lub upstream output. Ale środowiskowy risk zależy od źródła danych. Deployment A: workflow values only from trusted admins. Deployment B: workflow ingests external web/API content — tam realna ścieżka jest znacznie bardziej osiągalna. Znów: same CVE, same version, same CVSS != same environmental risk.

Fix usuwa etap, a nie dodaje kolejny filtr

Commit naprawczy ma wymowny tytuł: remove non-sandboxed second render in TextPromptBlock. Kod usuwa ponowne renderowanie w send_prompt(), a komentarz jasno mówi, że prompt jest już wcześniej w pełni wyrenderowany i wartości parametrów powinny pozostać literalne. To dobra zasada secure design: po wyznaczeniu granicy data/code nie interpretuj ponownie danych jako kodu — lepsze niż dokładanie kolejnej denylisty. Patch dodaje też testy sprawdzające, że dane zawierające {{ }} po pierwszym renderze trafiają dalej literalnie. Dobry regression test nie sprawdza tylko „PoC X już nie działa”, lecz właściwość: user-controlled values are rendered exactly once — invariant bezpieczeństwa.

„Mamy sandbox” to nie dowód

Security questionnaire często pyta „czy template rendering jest sandboxed?”. Dla podatnej wersji Skyvern odpowiedź brzmiałaby „Tak” — i jednocześnie byłaby niewystarczająca. Prawidłowe pytanie: „czy wszystkie interpretacje template data odbywają się w sandboxie i czy po bezpiecznym renderze wynik nie jest ponownie interpretowany?”. To pokazuje ograniczenie checkbox security. Zamiast myśleć „we use sandbox”, lepiej zdefiniować właściwość: untrusted workflow data can never reach an unsandboxed template evaluator — i sprawdzać ją przez cały data flow.

Jakie compensating controls mają sens?

Permanent remediation: upgrade to 1.0.45+. Jeżeli upgrade musi poczekać, można ograniczać: kto może tworzyć/edytować TextPromptBlock, jakie źródła dostarczają workflow parameters, czy nieufny upstream output trafia do tego bloku, uprawnienia procesu Skyvern, network egress i dostęp do sekretów. Ale uwaga: sandbox pierwszego renderowania nie jest skutecznym compensating control dla tego CVE, skoro problemem jest drugi render. To bardzo ważne dla risk acceptance.

Wniosek

Skyvern pokazuje coś ważniejszego niż samo SSTI. Security control może być obecny, poprawnie skonfigurowany i skuteczny na swoim etapie — a mimo to cały system pozostaje podatny. Dlaczego? Bo bezpieczeństwo pipeline jest właściwością całego przepływu danych, nie pojedynczej funkcji. Najważniejsze pytanie nie brzmi „czy używamy sandboxa?”, tylko: czy dane po opuszczeniu sandboxa mogą zostać ponownie potraktowane jako kod?

Powiązane na blogu

Źródła

  • VulnCheck — Skyvern before 1.0.45 Sandbox Escape via TextPromptBlock / CVE-2026-82447 (29.08.2026) — vulncheck.com
  • Skyvern fix commit — remove non-sandboxed second render in TextPromptBlock — github.com
  • Skyvern vulnerable block.py (v1.0.44) — github.com

Nota redakcyjna: stan informacji 30 sierpnia 2026, 10:21 CEST. CVE-2026-82447 obejmuje Skyvern od 0.2.1 do wersji wcześniejszych niż 1.0.45. Fix usuwa niesandboxowane drugie renderowanie promptu. Artykuł używa określenia „second-order injection” jako analitycznego opisu data flow; formalne CWE w advisory to CWE-1336.

Zapamiętaj jedno

Security control może być obecny, poprawnie skonfigurowany i skuteczny na swoim etapie — a system nadal podatny. Bezpieczeństwo pipeline to właściwość całego przepływu danych. Pytaj: czy dane po sandboxie mogą znów zostać potraktowane jako kod?