Uruchamianie kodu Pythona w piaskownicy za pomocą MicroPython i WASM

Uruchamianie kodu Pythona w piaskownicy za pomocą MicroPython i WASM


Uruchamianie kodu Pythona w piaskownicy za pomocą MicroPython i WASM

6 czerwca 2026 r

Od kilku lat eksperymentuję z różnymi podejściami do uruchamiania kodu w piaskownicy, ale moja ostatnia próba wydaje mi się, że w końcu może mieć wszystkie cechy, których szukałem. Wydałem go jako pakiet alfa o nazwie micropython-wasm i używam go do wtyczki piaskownicy do wykonywania kodu dla agenta Datasette o nazwie datasette-agent-micropython.

Dlaczego chcę piaskownicę?

Wszystkie moje kluczowe projekty open source — Datasette, LLM, a nawet sqlite-utils — obsługują wtyczki.

Bardzo lubię wtyczki jako mechanizm rozbudowy oprogramowania. Starannie zaprojektowany system wtyczek zmniejsza ryzyko związane z próbowaniem nowych rzeczy prawie do zera — nawet najśmielsze pomysły nie pozostawią trwałego wpływu na samą podstawową aplikację. W moim oprogramowaniu można dodać nową funkcję z dnia na dzień i nie muszę nawet przeglądać żądania ściągnięcia!

Jest jedna poważna wada: wszystkie moje systemy wtyczek używają Pythona i Pluggy, a kod wtyczki jest wykonywany z pełnymi uprawnieniami w moich aplikacjach. Błędna lub złośliwa wtyczka może wszystko zepsuć lub spowodować wyciek prywatnych danych.

Bardzo chciałbym móc uruchamiać kod w stylu wtyczki w środowisku, w którym nie można odczytać niezatwierdzonych plików, połączyć się z siecią lub ogólnie działać w sposób ryzykowny lub szkodliwy dla reszty aplikacji lub komputera użytkownika.

Moje zainteresowania obejmują nie tylko wtyczki. Szczególnie w przypadku Datasette jest wiele funkcji, które chciałbym obsługiwać, gdy przydatne byłoby wykonanie dowolnego kodu. Eksperymentowałem już z tym w przypadku wzbogacania zbiorów danych, gdzie można użyć kodu do przekształcenia wartości przechowywanych w tabeli. Bardzo chciałbym zbudować mechanizm, w którym można uruchamiać kod zgodnie z harmonogramem, który pobiera kod JSON z zatwierdzonej lokalizacji, uruchamia niewielki fragment kodu w celu sformatowania go na listę słowników, a następnie wstawia je jako wiersze w tabeli bazy danych SQLite.

Czego chcę od piaskownicy

Moim celem jest bezpieczne wykonywanie kodu w moich własnych aplikacjach w języku Python. Oto czego potrzebuję:

  • Zależności to zainstaluj czysto z PyPIw tym w razie potrzeby koła binarne na wielu platformach. Nie chcę, aby osoby korzystające z mojego oprogramowania musiały podejmować jakiekolwiek dodatkowe kroki poza bezpośrednią instalacją mojego pakietu Pythona.
  • Wykonywany kod musi podlegać obu pamięć I Procesor limity. nie chcę while True: s += "longer string" spowodować awarię mojej aplikacji lub komputera użytkownika.
  • Dostęp do plików musi być ściśle kontrolowany. Albo w ogóle nie mam dostępu do systemu plików, albo mogę dokładnie zdefiniować, które pliki można czytać, a w których można zapisywać.
  • Dostęp do sieci jest również kontrolowany. Kod w trybie piaskownicy nie powinien być w stanie komunikować się z niczym bez przejścia przez warstwę, którą w pełni kontroluję.
  • Wsparcie interakcji z funkcje gospodarza. Piaskownica nie na wiele się przyda, jeśli nie mogę dokładnie udostępnić wybranych funkcji platformy kodowi, na którym jest ona uruchomiona.
  • To musi być solidny, wspierany i jasno udokumentowany. Straciłem rachubę, ile projektów piaskownicy widziałem w repozytoriach z ostrzeżeniami, że nie są one aktywnie utrzymywane!

WebAssembly wygląda tutaj naprawdę obiecująco

Przeglądarki internetowe działają w najbardziej nieprzyjaznym środowisku, jakie można sobie wyobrazić, jeśli chodzi o złośliwy kod. Ich zadaniem jest pobieranie i wykonać niezaufany kod z Internetu przy prawie każdym ładowaniu strony.

Biorąc to pod uwagę, silniki JavaScript powinny być doskonałymi kandydatami na piaskownice. Niestety te silniki są również niezwykle skomplikowane i nie są przeznaczone do łatwego osadzania w innych projektach. Większość projektów v8 w Pythonie, które widziałem, jest rzadko konserwowana i zawiera ostrzeżenia, aby nie używać ich z całkowicie niezaufanym kodem.

WebAssembly to dużo lepiej kandydat. Został zaprojektowany od początku tak, aby obsługiwał wszystkie funkcje, na których mi zależy i był testowany w przeglądarkach przez prawie dekadę. Biblioteka wasmtime Pythona jest aktywnie utrzymywana i ma koła binarne.

MicroPython w WebAssembly

Silniki WebAssembly, takie jak wasmtime, uruchamiają pliki binarne WebAssembly. Niektóre języki programowania, takie jak Rust, można łatwo skompilować bezpośrednio do WebAssembly. Języki dynamiczne, takie jak JavaScript i Python, są trudniejsze — obsługują prymitywne języki, takie jak eval()co oznacza, że ​​potrzebują pełnego interpretera dostępnego w czasie wykonywania.

Aby uruchomić Python, potrzebujemy pełnego interpretera Pythona skompilowanego do WebAssembly, podłączonego w sposób ułatwiający wprowadzenie kodu, podłączenie funkcji hosta i dostęp do wyników.

Pyodide oferuje znakomity pakiet do uruchamiania Pythona przy użyciu WebAssembly w przeglądarce, ale używanie Pyodide w Pythonie po stronie serwera nie jest obsługiwane. Najnowsza rada, jaką udało mi się znaleźć, pochodzi z października 2024 r. i stwierdza, że ​​„Pyodide jest zbudowany przez zestaw narzędzi Emscripten i można go uruchomić tylko w przeglądarce lub Node.js”.

Któregoś dnia zdecydowałem się przyjrzeć MicroPythonowi jako opcji do tego. Witryna MicroPython mówi:

MicroPython to uproszczona i wydajna implementacja języka programowania Python 3, która zawiera niewielki podzbiór standardowej biblioteki Pythona i jest zoptymalizowana do działania na mikrokontrolerach oraz w ograniczonych środowiskach.

WebAssembly z pewnością wydaje mi się ograniczonym środowiskiem!

Budowanie pierwszej wersji

Poprosiłem GPT-5.5 Pro o wykonanie pewnych badań, co spowodowało, że pojawił się ten PR przeciwko MicroPython autorstwa Yamamoto Takahashi, zatytułowany „Eksperymentalna obsługa WASI dla portów/unix”.

Następnie wygenerował ten dokument research.md, więc pozwoliłem na swobodne korzystanie z Codex Desktop i GPT-5.5, aby zobaczyć, co się stanie:

read the research.md document and build this. You will probably need to write a script that compiles a custom WASM version of MicroPython as part of this project - fetch the MicroPython code to a /tmp directory for this as part of that script.

To zadziałało. Miałem teraz prototypową bibliotekę Pythona, która mogła wykonywać kod Pythona w piaskownicy WebAssembly!

Najtrudniejszym problemem do rozwiązania był trwały stan interpretera. Kompilacja WASM, której tutaj używamy, udostępnia pojedynczy punkt wejścia, który uruchamia interpreter, uruchamia kod, a następnie zatrzymuje interpreter na końcu.

Działa to dobrze w przypadku jednorazowych skryptów, ale w przypadku agenta Datasette chcę, aby zmienne i funkcje pozostawały rezydentne w pamięci, aby móc ich ponownie używać w wielu wywołaniach wykonawczych kodu.

Zaletą pracy z agentami zajmującymi się kodowaniem jest to, że można szybko przejść od pomysłu do sprawdzenia koncepcji. podpowiedziałem:

For keeping variables resident: what if we ran code inside micropython itself which called a host function get_next_python_code() and then passed that to eval() - and that host function blocked until new code was available, maybe by running in a thread with a queue? Could that or a similar idea help here?

Po kilku iteracjach dotarliśmy do wersji, która działa! W kodzie Pythona możesz teraz to zrobić:

from micropython_wasm import MicroPythonSession

with MicroPythonSession() as session:
    print(session.run("x = 10\nprint(x)").stdout)
    print(session.run("x += 5\nprint(x)").stdout)
    print(session.run("print(x * 2)").stdout)

W skrócie uruchamia to wątek, konfiguruje kolejkę żądań, a następnie wysyła wiadomości do tej kolejki w celu session.run() polecenie, za każdym razem oczekując w kolejce odpowiedzi na wynik tego wykonania. Wewnątrz WASM interpreter MicroPython blokuje oczekiwanie na plik __session_next__() host, aby zwrócić następny wiersz kodu, który uruchamia eval() włączone przed zadzwonieniem __session_result__({"id": request_id, "ok": True}) gdy każdy blok został pomyślnie wykonany.

Innym elementem złożoności była obsługa funkcji hosta, więc moja biblioteka Pythona mogła selektywnie udostępniać funkcje, które można następnie wywołać za pomocą kodu uruchomionego w MicroPythonie.

Codex rozwiązał ten problem za pomocą 78 linii C, które ostatecznie zostały skompilowane w blob WebAssembly o wielkości 362 KB, który dystrybuuję wraz z pakietem.

W żadnym wypadku nie jestem programistą C, ale czytałem C i miałem do czynienia z dwoma różnymi modelami, które mi to wyjaśniły (tutaj jest wyjaśnienie Claude’a), po czym poddałem go masie testów.

Wspaniałą rzeczą w pracy z WebAssembly jest to, że jeśli C okaże się fatalnie wadliwy, najgorsze, co może się zdarzyć, to wykonanie WebAssembly zakończy się niepowodzeniem, z wyjątkiem wyjątku. Mogę żyć z tym ryzykiem.

Limity pamięci są bezpośrednio obsługiwane przez wasmtime. Limity procesora są nieco większe: wasmtime oferuje koncepcję „paliwa”, która ogranicza liczbę operacji, które może wykonać wywołanie WebAssembly, i to jest właściwe rozwiązanie tego problemu, ale trudno określić jednostki. Eksperymentuję teraz z domyślnym ustawieniem „paliwa” na poziomie 20 milionów, ale nie jestem pewien, czy jest to najodpowiedniejsza wartość.

Spróbuj sam

The micropython-wasm alpha jest teraz dostępna na PyPI.

Możesz wypróbować to z własnego kodu Pythona, jak opisano w pliku README. Dodałem także prosty tryb CLI w wersji 0.1a2, co oznacza, że ​​możesz spróbować go użyć uvx bez uprzedniej instalacji w ten sposób:

uvx micropython-wasm -c 'print("Hello world")'
# To see it run out of fuel:
uvx micropython-wasm -c 's = ""; while True: s += "longer"'
# Outputs: micropython-wasm: guest exited with code 1

Możesz także wypróbować to w agencie Datasette w następujący sposób:

uvx llm keys set openai
# Paste in an OpenAI key, then:
uvx --with datasette-agent \
  --with datasette-agent-micropython \
  --prerelease allow \
  datasette --internal internal.db \
    -s plugins.datasette-llm.default_model gpt-5.5 \
    --root -o

Następnie przejdź do http://127.0.0.1:8001/-/agent i uruchom monit:

show me some micropython

Zrzut ekranu przedstawiający interfejs aplikacji do czatu z ciemnoniebiesko-szarym odczytem nagłówka "dom" po lewej stronie i "źródło" z ikoną menu hamburgera po prawej stronie. Poniżej znajduje się wiersz nawigacyjny z "← Powrót" I "Pogawędzić" po lewej stronie i "EKSPORT" przycisk po prawej stronie. Pojawi się niebieski dymek z wiadomością dla użytkownika "pokaż mi jakiegoś mikropytona". Poniżej znajduje się zwinięta sekcja dotycząca myślenia "▸Myślenie: … aby wyraźnie pokazać wynik. Potem mogę zakończyć krótkim wyjaśnieniem!" następnie a "▶ Narzędzie: wykonaj_micropython" etykieta. Blok kodu następuje: "# Mały przykład MicroPythona: logika w stylu migania + Fibonacci" / "zdecydowanie fib(n):" / "    a, b = 0, 1" / "    na zewnątrz = ()" / "    dla _ w zakresie (n):" / "        out.append(a)" / "        a, b = b, a + b" / "    wróć" / 'drukuj("Witamy z MicroPythona!")

Czy powinieneś zaufać mojej piaskownicy z kodem wibracyjnym?

Po narzekaniu na niedojrzałe, luźno utrzymywane biblioteki piaskownicy, zakrawa na głęboką ironię losu, że teraz zbudowałem własne!

Celowo umieściłem na nim wersję alfa i nie jestem gotowy polecać jej nikomu, kto nie chce podjąć znaczącego ryzyka.

Poddałem go wystarczającej liczbie testów, więc mogę go używać samodzielnie. Wysłałem moją pierwszą wtyczkę, która z niej korzysta, datasette-agent-micropython. Zablokowałem także GPT-5.5 xhigh we wtyczce Datasette Agent i rzuciłem mu wyzwanie, aby wyrwał się z piaskownicy, ale jak dotąd nie udało się.

Mam nadzieję, że ta implementacja przekona niektóre firmy z profesjonalnymi zespołami ds. bezpieczeństwa i poważnymi problemami do zaangażowania się w używanie Pythona w WebAssembly jako podejścia typu sandbox i open source do własnych rozwiązań.



Source link

Postagens Similares

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *