Dynamic Aliases
Dynamic aliases are custom commands defined in Python scripts. They appear alongside built-in commands in the Tuoni UI and terminal, and can be sent to agents just like any native command.
Aliases can:
- Chain multiple built-in commands into a single operation
- Pre-process configuration before sending commands
- Post-process and combine results from multiple commands
- Interact with discovery (add hosts, services, credentials)
- Modify agent metadata without sending any commands to the agent
Alias Class Contract
An alias is a Python class that implements four methods:
| Method | Parameters | Returns | Called When |
|---|---|---|---|
configuration_schema() |
— | str (JSON Schema) |
UI/API requests the schema |
can_send_to_agent(agent) |
agent: Agent |
bool |
Before showing the command as available |
validate_config(config, agent) |
config: Configuration, agent: Agent |
None |
Before execution, raise on invalid |
execute(ctx, config, agent) |
ctx: AliasContext, config: Configuration, agent: Agent |
None |
When the command is executed |
Optional Properties
Set these in __init__ to provide metadata:
Registration
Register an alias instance (not the class) with tuoni.commands.register_dynamic_alias:
Pass an Instance
register_dynamic_alias expects an instance of your class, not the class itself.
Use MyAlias() with parentheses, not MyAlias.
You can register multiple aliases from a single script file:
The alias is available in the UI/API as script:global:<name> (e.g. script:global:myalias).
Configuration Schema
The configuration_schema() method returns a JSON Schema string. Tuoni extends standard JSON Schema with two additional fields:
files
Declares file upload fields. Keys are field names, values are empty objects:
positional
Maps schema properties to positional arguments in the terminal:
This allows terminal usage like: myalias 192.168.1.10 8080 instead of defining explicit arguments.
Full Example
Agent Compatibility
The can_send_to_agent method receives an Agent object. Return True if the alias supports this agent, or raise an exception with a message:
Common agent types: SHELLCODE_AGENT, GENERIC_SHELL_AGENT, EXTERNAL_AGENT
Common OS values: LINUX, WINDOWS
Common architecture values: X64, X86
Queuing Sub-Commands
Inside execute(), use ctx.queue_command() to send commands to the agent:
Command Template IDs
You can reference built-in commands using short or fully-qualified names:
| Short Form | Fully Qualified | Description |
|---|---|---|
"sh" |
"shelldot.commands.native:global:sh" |
Shell command |
"ps" |
"shelldot.commands.native:global:ps" |
Process list |
"download" |
"shelldot.commands.native:global:download" |
Download file from agent |
"upload" |
"shelldot.commands.native:global:upload" |
Upload file to agent |
"bof" |
"shelldot.commands.native:global:bof" |
Execute BOF |
The format is <plugin>:<scope>:<command>. Plugin commands use their plugin ID.
Configuration Formats
There are three ways to pass configuration to a sub-command:
Simple dict — Treated as JSON-only configuration:
Structured dict with json and/or files keys:
Reusing file IDs from previous results:
File Passing Alternatives
There are multiple ways to pass files in configuration:
open("path", "rb")— File-like object (script must manage closing){"file_id": "uuid-string"}— Reference an existing file in Tuoni by IDresult_file— Pass aFileobject directly (has a.file_idproperty)io.BytesIO(bytes_data)— In-memory bytes as a file-like object
Execution Configurations
By default, commands execute in the agent's current process context (SelfExecutionConfiguration).
You can override this by passing an execution configuration as the third argument:
tuoni.NewExecutionConfiguration
| Parameter | Type | Description |
|---|---|---|
executable |
str \| None |
Path to executable for the new process |
suspended |
bool \| None |
Start the process suspended |
ppid |
int \| None |
Parent process ID for spoofing |
username |
str \| None |
Run as this user |
password |
str \| None |
Password for the specified user |
All parameters are optional keyword arguments and can also be set as properties after creation.
Result Handling
Setting Results
Use ctx.set_result(dict) to provide the command output. Keys are result names, values can be:
str— Text resultbytes— Binary result- File-like object — Stored as a downloadable file
{"file_id": "uuid"}— Reference to an existing Tuoni file- Object with
.file_idproperty — Same as above
Intermediate Results
You can call ctx.set_result() multiple times. Each call updates the result shown to the user. This is useful for providing progress updates during long-running aliases.
Finishing
Every alias must call either ctx.finish() or ctx.fail():
Always Terminate
If execute() returns without calling ctx.finish() or ctx.fail(), the alias will appear as perpetually running. Always ensure one of these is called, including in error paths.
Error Handling Pattern
This is the standard pattern used throughout Tuoni scripts for handling sub-command failures:
For multiple chained commands:
Aliases Without Agent Commands
Aliases don't have to send commands to agents. You can manipulate agent metadata directly:
Custom Properties
agent.metadata.custom_properties is an observable dict — changes are persisted immediately to the server. No explicit save call is needed. See AgentMetadata for all writable properties.