Tuoni Script Engine (tuoni-se)
Here Be Dragons
Tuoni-SE is currently in experimental mode. Expect rough edges, breaking changes between releases, and the occasional fire-breathing bug. If something misbehaves, a server restart usually tames it.
Overview
Tuoni provides a server-side scripting engine that allows you to extend its behavior through custom Python scripts. Scripts run inside the Tuoni Server process using GraalPy Community — a Java-based Python runtime.
With server-side scripts you can:
- Create dynamic aliases — custom commands that chain existing commands, pre-process configuration, and post-process results
- Manage agents — query, filter, and modify agent metadata programmatically
- React to events — register callbacks for agent connections, command results, discovery changes, and more
- Populate discovery — add hosts, services, and credentials from script logic
- Send commands directly — queue commands to agents from event handlers or standalone scripts
The Python standard library is available. Pip packages are supported via the configured virtual environment. Native C extensions are not supported — only pure-Python packages work.
Getting Started
1. Enable Scripting
In the Tuoni UI, go to Settings and enable "Enable EXPERIMENTAL server-side scripting".
Or via the API:
2. Add a Script
Place a .py file in the scripts directory (default: /srv/tuoni/data/scripts).
Tuoni watches this directory and automatically executes new or modified scripts.
File Change Detection
File changes are debounced (default 3 seconds). After saving a file, Tuoni waits for the debounce period before re-executing the script. Modifying a running script will stop the current execution and restart it.
3. Check Script Output
Script print() output and errors are logged to ${tuoni.scripting.logs-dir}/<script-name>.log
(default: /srv/tuoni/data/script_logs/).
Configuration Properties
All properties are set in the Tuoni server configuration (tuoni.yml or via CLI arguments).
| Property | Default | Description |
|---|---|---|
tuoni.scripting.scripts-dir |
${tuoni.data-dir}/scripts |
Directory watched for .py scripts |
tuoni.scripting.logs-dir |
${tuoni.data-dir}/script_logs |
Directory for script stdout/stderr logs |
tuoni.scripting.file-change-debounce-time |
3s |
Delay before re-executing after file change |
tuoni.scripting.python-home |
graalvm |
GraalPy home directory |
tuoni.scripting.python-venv |
venv |
Python virtual environment directory |
Permissions & Sandboxing
Scripts run in an isolated GraalVM context with configurable permissions.
These are configured in your tuoni.yml under the tuoni.scripting.permissions section:
File System Access (fs-access)
| Mode | Value | Read Access | Write Access |
|---|---|---|---|
| Sandbox (default) | "sandbox" |
Scripts dir, Tuoni files dir, GraalPy home, venv | /tmp/tuoni-script-<name> only |
| Read-Only | "read-only" |
Everything in Sandbox + real filesystem | /tmp/tuoni-script-<name> only |
| Read-Write | "read-write" |
Full filesystem (scripts/files dirs remain read-only) | Full filesystem |
Network & Environment Access
| Property | Default | Description |
|---|---|---|
allow-net-access |
false |
Allow outbound network connections |
allow-env-access |
false |
Allow reading environment variables |
Security Warning
Combining fs-access: "read-write" with allow-net-access: true effectively disables the sandbox.
Only use this configuration in trusted environments. Scripts with these permissions have the same access level as the Tuoni server process.
Example: Enabling Network Access
To use libraries like urllib or requests (via pip), enable network access in your tuoni.yml:
Experimental Feature
Server-side scripting is currently in an experimental state. If you encounter unexpected behavior, restarting the Tuoni server can help resolve transient issues while we work out all the kinks.
Script Lifecycle
- Detection — Tuoni's file watcher detects a new or modified
.pyfile - Initialization — The
tuonimodule is injected into the script's Python environment - Execution — The script runs top-to-bottom in its own isolated context
- Background Tasks — If the script registers event listeners, the script stays alive
- Termination — The script terminates when:
- All code has finished and no background tasks are active
- All background tasks are explicitly stopped via
task.stop() - The script file is deleted or the server shuts down
Background Tasks
A script that only registers event listeners will remain running indefinitely.
Call task.stop() inside a callback to allow the script to terminate.
See Event System for details.
Minimal Example
A simple alias that runs whoami on a Windows shellcode agent:
After saving this file to the scripts directory, a new script-whoami command appears in the Tuoni UI and can be sent to any active Windows shellcode agent.
Next Steps
- Dynamic Aliases — In-depth guide to creating custom commands
- API Reference — Complete reference for the
tuonimodule - Event System — React to agent connections, command results, and more
- Examples — Real-world scripts for common use cases