Skip to content

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:

1
2
3
4
curl -k -X PATCH https://<tuoni-server>:8443/api/v1/settings \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{"server":{"scripting.enabled":"ENABLE_SCRIPTING"}}'

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.

1
2
3
import tuoni

print("Hello from Tuoni script engine!")

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:

1
2
3
4
5
6
7
tuoni:
  # EXPERIMENTAL
  scripting:
    permissions:
      fs-access: "sandbox"
      allow-net-access: false
      allow-env-access: false

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:

1
2
3
4
5
tuoni:
  scripting:
    permissions:
      fs-access: "sandbox"
      allow-net-access: true

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

  1. Detection — Tuoni's file watcher detects a new or modified .py file
  2. Initialization — The tuoni module is injected into the script's Python environment
  3. Execution — The script runs top-to-bottom in its own isolated context
  4. Background Tasks — If the script registers event listeners, the script stays alive
  5. 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:

import tuoni


class WhoamiAlias:

    def __init__(self):
        self.name = "script-whoami"
        self.description = "Run whoami on a Windows agent"

    def configuration_schema(self):
        return """{
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "type": "object",
            "properties": {},
            "required": []
        }"""

    def can_send_to_agent(self, agent):
        return (
            agent.type == "SHELLCODE_AGENT"
            and agent.get_latest_metadata().os == "WINDOWS"
        )

    def validate_config(self, config, agent):
        pass

    def execute(self, ctx, config, agent):
        cmd = ctx.queue_command("cmd", {"command": "whoami"})
        cmd.wait_for_finish()

        if cmd.is_failed():
            ctx.fail(cmd.get_error_message())
            return

        ctx.set_result(cmd.get_result())
        ctx.finish()


tuoni.commands.register_dynamic_alias(WhoamiAlias())

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 tuoni module
  • Event System — React to agent connections, command results, and more
  • Examples — Real-world scripts for common use cases