Skip to main content
  1. Documentation/
  2. Plugins/

Tools Modules

Table of Contents
Tools modules execute directly from the teamserver against remote services without deploying an agent.

Interface
#

Tools modules use AgentlessModuleBase with class-level metadata and Command class attributes for operations. The base class auto-discovers commands and dispatches execute() to handle_<operation> methods — no metadata() or execute() override needed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from tantoc2.server.agentless_base import (
    AgentlessModuleBase,
    AgentlessTarget,
    AgentlessResult,
)
from tantoc2.server.command_schema import Command

class MyToolModule(AgentlessModuleBase):
    name = "my_service"
    description = "Interact with MyService"
    author = "Your Name"
    protocol = "my_protocol"
    mitre_attack = ["T1021"]
    supports_shell = False
    dependencies = ["some-lib>=1.0"]

    exec = (
        Command("exec", "Execute a command on the target")
        .arg("command", type="str", desc="Command to run", required=True)
    )

    def handle_exec(
        self,
        targets: list[AgentlessTarget],
        options: dict,
        *,
        credentials: dict | None = None,
        proxy: dict | None = None,
    ) -> list[AgentlessResult]:
        results = []
        for target in targets:
            try:
                # ... connect, execute, return result ...
                results.append(AgentlessResult(target=target, success=True, data={"output": "..."}))
            except Exception as exc:
                results.append(AgentlessResult(target=target, success=False, error=str(exc)))
        return results

See Building Tool Plugins for the full step-by-step guide including credential handling, proxy support, and tests.

AgentlessTarget
#

1
2
3
4
5
@dataclass
class AgentlessTarget:
    host: str
    port: int | None = None
    credential_id: str | None = None  # references credential store

AgentlessResult
#

1
2
3
4
5
6
7
8
@dataclass
class AgentlessResult:
    target: AgentlessTarget
    success: bool
    data: dict[str, Any] = field(default_factory=dict)
    credentials: list[ExtractedCredential] = field(default_factory=list)
    error: str | None = None
    raw_output: str | None = None

Return one AgentlessResult per target — even on failure. Never raise from a handler.

Return ExtractedCredential objects in credentials to auto-populate the engagement’s credential store with anything the module discovers.

Key Concepts
#

Credential Integration
#

When targets have a credential_id, the manager decrypts and passes credentials to handle_<operation> via the credentials dict (credential_id → {"username", "secret", "cred_type", "domain", ...}). Always check if credentials is None before subscripting.

Proxy Support
#

When a proxy is configured, a dict is passed via the proxy parameter containing proxy_type, host, port, and optionally username. Use PySocks to route connections. Declare PySocks as an optional dependency.

Multi-Target Execution
#

handle_<operation> receives all targets as a list. Modules handle their own parallelism. Results must be returned in the same order as the input targets.

Deployment
#

Standalone Package (Recommended)#

1
2
[project.entry-points."tantoc2.agentless_modules"]
my_tool = "tantoc2_tool_mytool.module:MyToolModule"
1
pip install ./my-tool-package

File Drop
#

1
2
cp my_tool.py tantoc2/plugins/agentless/
tantoc2> tools refresh