Skip to main content
  1. Documentation/
  2. Developer Guide/

Plugin Packaging & Distribution

Table of Contents
How to package, distribute, and deploy your TantoC2 plugins — from simple file drops to installable Python wheels.

Deployment Methods
#

Method 1: File Drop (Simplest)
#

Drop a .py file in the appropriate directory:

Plugin TypeDirectory
Server modulemodules/
Transportplugins/transports/
Tools moduleplugins/agentless/
Agent packageplugins/agent_packages/

Then trigger a refresh:

1
2
tantoc2> modules refresh        # Server modules
tantoc2> tools refresh          # Tools modules

No packaging needed — the framework discovers your plugin on the next scan.

Method 2: Plugin Inbox
#

Drop a .py or .whl file into the plugin inbox directory (plugin_inbox_dir config):

  • .py files: Auto-detected by type and moved to the correct plugin directory
  • .whl files: Pip-installed into the running environment, then registered via entry points

The inbox is scanned every bg_plugin_watcher_interval seconds (default: 30).

If a .py file fails to load, it’s moved to inbox/failed/ with an error log.

Method 3: Python Wheel (Recommended for Distribution)#

Package your plugin as a standard Python wheel with entry points.

Packaging as a Wheel
#

Project Structure
#

1
2
3
4
5
6
my-tantoc2-plugin/
  pyproject.toml
  src/
    my_plugin/
      __init__.py
      module.py           # Your plugin implementation

pyproject.toml
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-tantoc2-plugin"
version = "1.0.0"
description = "My custom TantoC2 plugin"
requires-python = ">=3.11"
dependencies = [
    "tantoc2",
    # Any additional dependencies your plugin needs
]

[tool.hatch.build.targets.wheel]
packages = ["src/my_plugin"]

Entry Points by Plugin Type
#

Server Module
#

1
2
3
4
# No entry points needed — file drop or inbox is sufficient
# But if you want wheel-based discovery:
[project.entry-points."tantoc2.modules"]
my_module = "my_plugin.module:MyModule"

Transport Plugin
#

1
2
[project.entry-points."tantoc2.transports"]
my_transport = "my_plugin.transport:MyTransport"

Tools Module
#

1
2
[project.entry-points."tantoc2.agentless_modules"]
my_tool = "my_plugin.tool:MyToolModule"

Agent Package (Three Entry Points)
#

Agent packages require three separate entry points — one for each component:

1
2
3
4
5
6
7
8
[project.entry-points."tantoc2.agent_packages"]
my_agent = "my_plugin.package:MyAgentPackage"

[project.entry-points."tantoc2.crypto_providers"]
my_crypto = "my_plugin.crypto:MyCryptoProvider"

[project.entry-points."tantoc2.protocol_codecs"]
my_codec = "my_plugin.codec:MyProtocolCodec"

Entry point names (left side of =) must match the plugin_name() return value.

Building
#

1
2
3
pip install hatch
hatch build
# Output: dist/my_tantoc2_plugin-1.0.0-py3-none-any.whl

Installing
#

1
2
3
4
5
# Direct install
pip install dist/my_tantoc2_plugin-1.0.0-py3-none-any.whl

# Or drop in the plugin inbox
cp dist/my_tantoc2_plugin-1.0.0-py3-none-any.whl /path/to/plugin_inbox/

Hot-Reload
#

All plugin types support hot-reload without server restart:

File-Based Plugins
#

The framework tracks file modification times (mtime). When a file changes:

  1. The old plugin class is unregistered
  2. The file is re-imported
  3. New plugin classes are discovered and registered

Trigger manually:

1
2
tantoc2> modules refresh
tantoc2> tools refresh

Or wait for the plugin watcher (runs every bg_plugin_watcher_interval seconds).

Wheel-Based Plugins
#

Re-install the updated wheel:

1
pip install --force-reinstall my_tantoc2_plugin-2.0.0-py3-none-any.whl

Then trigger a refresh — the framework re-scans entry points.

Plugin Naming
#

  • plugin_name() must be globally unique across all plugins of the same type
  • Use lowercase with underscores: my_module, dns_transport, shinobi_agent
  • For agent packages, ensure consistency between package name, crypto provider name, and codec name

Dependencies
#

Declared Dependencies
#

Modules can declare pip dependencies in their metadata:

1
2
3
4
ModuleMetadata(
    ...,
    dependencies=["paramiko", "impacket>=0.11"],
)

Missing packages are auto-installed at discovery time.

Failed Dependencies
#

If installation fails, the module is marked as unavailable. Query unavailable modules:

1
2
# Programmatic
manager.unavailable_modules  # {"my_module": "Failed to install: impacket"}

The module won’t appear in list_modules() but its failure reason is tracked.

Testing Your Plugin
#

Unit Testing
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pytest
from my_plugin.module import MyModule

def test_metadata():
    meta = MyModule.metadata()
    assert meta.name == "my_module"
    assert "str" in [o.type for o in meta.options_schema.values()]

def test_execute():
    module = MyModule()
    result = module.execute(
        agent_id="test-agent-id",
        options={"path": "/etc/passwd"},
        session=mock_session,
    )
    assert result.success

Integration Testing
#

1
2
3
4
5
6
7
# Install in dev mode
pip install -e ./my-tantoc2-plugin

# Start teamserver and verify plugin appears
tantoc2> modules list          # Should show your module
tantoc2> tools list            # Should show your tool
tantoc2> listeners create my_transport --port 8888  # Should work

Distribution Checklist
#

  • plugin_name() is unique and descriptive
  • plugin_type() returns the correct type string
  • Entry point names match plugin_name() return values
  • Dependencies are declared in both pyproject.toml and metadata
  • Plugin works after pip install from wheel
  • Plugin works via file drop into appropriate directory
  • Plugin works via inbox drop (both .py and .whl)
  • Hot-reload works (modify file, refresh, verify changes)