Embedding user code in your app using Extism
Every application I love has some kind of power-user mode where I can add my own code or scripting to make it useful to me. Simple examples are Firefox with its addons or VLC with its plugins. Ideally, any significant or valuable application should have such a feature.
But as a developer or builder, it’s not easy to build such a feature securely. I have tried it before with embedding Lua with some restrictions. But it isn’t great in sandboxing. I’ve always had an eye on the WASM-based implementation because browsers have decent sandboxing. Very recently, when I was updating Navidrome, I came across its plugin system, which uses Extism and is based on WebAssembly. Ideally, at some point, even WordPress can do this, allowing us to sandbox plugins.
So I wrote a simple plugin to try out this infrastructure. I wrote a plugin with internet access to a given URL. Its only goal is to test the plugin’s ability to make web API calls in a controlled way. And to explore how plugins work. The language I chose for the plugin is Python, but there are many better supported languages. I tested it using CLI. And it’s not difficult to call it from a host language.
I installed and used Extism CLI, version 1.6.2. Direct binary download from the repository. I wrote a simple Python script that, as a plugin, expects a JSON input containing URL and payload for a web post. There are other ways to pass the data to the plugin, but JSON seemed most flexible.
Inside the plugin, I read the JSON input, parse the parameters, and make a web POST request using Existims Http. Remember, you need to use the Python PDK (Plugin Development Kit). Those PDKs can have some limitations of their own. So be mindful when choosing a plugin language.
Also as you can see, a plugin can have many functions. The ones annotated with @extism.plugin_fn can be called from the host environment. The code is easy to understand.
#webhook.py
import json
import extism
from extism import Http
@extism.plugin_fn
def post_json():
params = extism.input_json()
url = params["url"]
payload = params["payload"]
response = Http.request(
url,
meth="POST",
headers={"Content-Type": "application/json"},
body=json.dumps(payload),
)
result = {
"status": response.status_code,
"body": response.data_str(),
}
extism.output_str(json.dumps(result))
To call this plugin first I need to build it. To build use the extism-py which is installed as part of Python PDK. Then run
extism-py webhook.py webhook.wasm
That will produce webhook.wasm, the plugin code that one would probably ship. You can call or invoke it via the CLI for testing. As you can see, I have to grant internet access to the WASM by allowing access to the url webhook.site.
extism call webhook.wasm post_json \
--allow-host "webhook.site" --wasi \
--input '{ "url": "https://webhook.site/0b757518-7120-4919-a12f-252d3dfbc8b5", "payload": { "name": "Thejesh from inside the sandbox", "seq": 1 } }'
But in real world you would be calling it from host code/program. Let’s say your host language is also Python, then you can call this WASM plugin using the following piece of code. Again, it’s not that difficult.
# host.py
# /// script
# dependencies = ["extism"]
# ///
import json
import extism
wasm_file = "webhook.wasm"
input_data = {
"url": "https://webhook.site/dc895b92-0b21-46db-a9c0-766dd87e8b0f",
"payload": {
"name": "Thejesh from inside the sandbox",
"seq": 1,
},
}
manifest = {
"wasm": [{"path": wasm_file}],
"allowed_hosts": ["webhook.site"],
}
with extism.Plugin(
manifest,
wasi=True,
) as plugin:
result = plugin.call("post_json", json.dumps(input_data).encode())
print(result.decode())
All of a sudden, safe plugin implementation doesn’t sound that difficult, isn’t it?


