Extensions
Extension API Reference
Complete reference for building custom InterceptSuite Python extensions.
Extension API Reference
Complete reference for developing custom InterceptSuite Python extensions.
Extension Class Requirements
Every extension must define a class named InterceptSuiteExtension. InterceptSuite only loads files that contain this class -it is required.
class InterceptSuiteExtension:
def register_interceptor_api(self, interceptor):
interceptor.set_extension_name('My Extension')
interceptor.set_extension_version('1.0.0')
Required Methods
register_interceptor_api(self, interceptor)
This method is called by InterceptSuite when the extension loads. It must be present in InterceptSuiteExtension.
Parameters:
| Parameter | Type | Description |
|---|---|---|
interceptor |
object | The InterceptSuite API object |
Required API Calls
Within register_interceptor_api, you must call both of the following:
interceptor.set_extension_name('My Extension') # Required
interceptor.set_extension_version('1.0.0') # Required
Both methods accept a string parameter. Without these calls, the extension will fail to load.
Extension Logging
To write messages to the Extension Logs tab inside InterceptSuite, import and use ExtensionLogger:
from InterceptSuite.Extensions.APIs.Logging import ExtensionLogger
ExtensionLogger.Log("Extension loaded successfully!")
Log messages appear in the extension's own output pane: open the Extension tab, select the extension in the table, and read its output underneath (the Clear button empties it). Each extension has its own output, so two extensions never mix their lines. Output that cannot be tied to an extension, such as Python start-up problems, goes to the application Logs tab. Any string is accepted.
Full Registration Example
from InterceptSuite.Extensions.APIs.Logging import ExtensionLogger
class InterceptSuiteExtension:
def register_interceptor_api(self, interceptor):
interceptor.set_extension_name('My Custom Extension')
interceptor.set_extension_version('1.0.0')
ExtensionLogger.Log("Extension registered successfully!")
Custom Data Viewer Tabs
Extensions can add tabs to the packet viewer alongside the built-in Raw and Hex tabs. This is done by registering a handler class with AddDataViewerTab.
Registering a Custom Tab
interceptor.AddDataViewerTab("Base64", MyHandlerClass())
Parameters:
| Parameter | Type | Description |
|---|---|---|
| Tab Name | string | The label shown on the tab |
| Handler Instance | object | An instance of your handler class |
Rules for registering a tab:
- Call
set_extension_nameandset_extension_versionfirst.AddDataViewerTabraises an error if either is missing. - The tab name must not be empty.
- One extension can register several tabs, each with its own handler.
- Tabs are removed automatically when the extension is disabled, removed or the app closes.
Handler Class Requirements
Your handler class has three methods. Only fetchdata is required; the other two are optional:
| Method | Required | If it is missing |
|---|---|---|
fetchdata(data) |
Yes | AddDataViewerTab raises an error, the failure is logged and the extension does not load. |
should_show_tab(data) |
No | The tab is shown for every packet. |
updatedata(data) |
No | Edits made in the tab are not applied: the packet stays as it was. |
class MyHandlerClass:
def should_show_tab(self, data):
"""Return True to show the tab for this packet, False to hide it."""
return True
def fetchdata(self, data):
"""Process packet data and return the content to display in the tab."""
raw = data.get("data", "")
return raw # Return processed string
def updatedata(self, data):
"""Handle user edits made in the tab. Return updated data or None."""
edited = data.get("edited_data", "")
if edited:
return edited # Return the updated packet content
return None
Where Tabs Appear
A registered tab is offered in every place that shows packet data. The tool_type key tells you which one:
tool_type |
Where | Editable |
|---|---|---|
proxy_history |
Proxy History | No |
intercept |
Intercept (held packet) | Yes |
replay |
Replay (payload to send, and received packets) | Compose only |
should_show_tab is honoured in all of them, so a tab appears only when it applies to the packet.
The data Dictionary
The data parameter passed to all handler methods is a dictionary:
{
"ip": "23.215.0.136", # str - Source IP address
"destination_ip": "127.0.0.1", # str - Destination IP address
"source_port": 0, # int - Source port
"destination_port": 443, # int - Destination (server) port
"direction": "Client->Server", # str - "Client->Server" or "Server->Client"
"length": 1452, # int - Packet length in bytes
"data": "payload", # str - Payload as a Latin-1 string
"raw_data": bytearray(b"..."), # bytearray - Payload bytes
"type": "Text", # str - Data type
"timestamp": "2026-10-04 12:35:26",# str - Timestamp
"connection_id": 1, # int - Connection identifier
"packet_id": 42, # int - Packet identifier
"host": "example.com", # str - Domain the client used ("" if it connected by IP)
"alpn": "h2", # str - Negotiated ALPN ("" if none)
"protocol": "TLS", # str - TCP, TLS, UDP, DTLS or QUIC
"tool_type": "proxy_history", # str - proxy_history, intercept or replay
"is_editable": False, # bool - True where the user can edit
"editable_data": "payload", # str - The packet's current editable content (Latin-1 string)
"edited_data": "..." # str - Only in updatedata(): the tab's edited content
}
Notes on the values:
dataandedited_dataare Latin-1 strings: one character per byte (0 to 255), so binary payloads survive unchanged. Useraw_datawhen you want real bytes.hostis the real server name from the client's TLS SNI, or the domain a SOCKS5 client asked for. It is an empty string when the client connected by IP; InterceptSuite never invents one.alpnis empty when no ALPN was negotiated, for example for plain TCP or UDP.typeis the data type label shown in Proxy History, such asTextorBinary.
The direction uses the same wording everywhere. In Replay, what you send is Client->Server and what comes back is Server->Client.
Because protocol, alpn and host are provided, one extension can tell, for example, HTTP/3 over QUIC from plain TCP on the same port, without guessing from the bytes.
Method Reference
should_show_tab(self, data) -> bool
Controls visibility of your custom tab for each packet.
- Return
Trueto show the tab - Return
Falseto hide the tab
Use this to show your tab only when the packet data is relevant:
def should_show_tab(self, data):
raw = data.get("data", "")
return "HTTP" in raw # Only show for HTTP packets
fetchdata(self, data) -> str
Processes the packet data for display in your tab.
- Receives the full
datadictionary - Returns a string to display in the tab
def fetchdata(self, data):
import base64
raw = data.get("data", "")
try:
return base64.b64decode(raw).decode("utf-8", errors="ignore")
except Exception:
return "Invalid base64 data"
updatedata(self, data) -> str | None
Handles user edits made in your custom tab.
data.get("edited_data")contains what the user typed in your tab- Return the updated packet content to replace the original payload
- Return
Noneif no update should be applied - The string you return is turned back into bytes with Latin-1, one character per byte. For binary results build the string with
bytes_value.decode("latin-1"); do not return UTF-8 text for binary data
def updatedata(self, data):
import base64
edited = data.get("edited_data", "")
if edited:
try:
return base64.b64encode(edited.encode("utf-8")).decode("utf-8")
except Exception:
return None
return None
Errors and Defaults
An exception in your handler never crashes InterceptSuite. It is logged to your extension's output and the app carries on:
| Method | If it raises an error | If it returns None |
|---|---|---|
should_show_tab |
The tab is shown. | Treated as False: the tab is hidden. |
fetchdata |
The tab shows Error processing data: <message>. |
Converted to text. |
updatedata |
The original packet is kept unchanged. | "No change": the packet is left as it was. |
Cleanup When an Extension Unloads
Optionally add a cleanup method to InterceptSuiteExtension. InterceptSuite calls it, with no arguments, when the extension is disabled, removed, or the app closes, so you can close files, sockets or threads.
class InterceptSuiteExtension:
def register_interceptor_api(self, interceptor):
interceptor.set_extension_name("My Extension")
interceptor.set_extension_version("1.0.0")
def cleanup(self):
ExtensionLogger.Log("Goodbye")
Keep cleanup short. Unloading waits for it, and a slow one is logged with how long it took.
How Extensions Are Loaded
InterceptSuiteExtensionis created once, with no arguments, when the extension loads.register_interceptor_apiis called right after.- Extensions run inside the Python you configured in Extension > Settings, so any package installed there can be imported. The application folder is added to
sys.path. - All extension code runs one call at a time, so extensions never run concurrently and you need no locking.
- A file that has no
InterceptSuiteExtensionclass, or whose class has noregister_interceptor_api, is not loaded, and the reason is logged.
Checking Scope
Call is_in_scope(host, protocol) on the interceptor object to ask whether a target is in the user's Scope. Pass the same host string Proxy History shows and one of TCP, TLS, UDP, DTLS, QUIC. With no Scope entries configured, everything counts as in scope.
class MyHandler:
def __init__(self, interceptor):
self.interceptor = interceptor
def should_show_tab(self, data):
host = data.get("host") or data.get("destination_ip")
host = f"{host}:{data['destination_port']}"
return self.interceptor.is_in_scope(host, data["protocol"])
Application Version
interceptor.get_interceptsuite_version() returns the InterceptSuite version as major.minor.build, for extensions that need a minimum version.
Threading
Handlers for read-only views (Proxy History, received Replay packets) run on a background thread, so a slow extension shows "Processing..." and the UI stays responsive. Handlers for editable views run immediately so your updatedata result is ready when the user forwards or sends. Extension code never runs concurrently, so you do not need your own locking.
Complete Example: Base64 Viewer Extension
import base64
from InterceptSuite.Extensions.APIs.Logging import ExtensionLogger
class Base64HandlerClass:
def should_show_tab(self, data):
raw = data.get("data", "")
try:
base64.b64decode(raw, validate=True)
return True
except Exception:
return False
def fetchdata(self, data):
raw = data.get("data", "")
try:
return base64.b64decode(raw).decode("utf-8", errors="ignore")
except Exception:
return "Unable to decode as Base64"
def updatedata(self, data):
edited = data.get("edited_data", "")
if edited:
try:
return base64.b64encode(edited.encode("utf-8")).decode("utf-8")
except Exception:
return None
return None
class InterceptSuiteExtension:
def register_interceptor_api(self, interceptor):
interceptor.set_extension_name("Base64 Viewer")
interceptor.set_extension_version("1.0.0")
interceptor.AddDataViewerTab("Base64", Base64HandlerClass())
ExtensionLogger.Log("Base64 Viewer tab registered.")
Example Extensions Repository
For more working examples and starter templates, see the official repository:
github.com/InterceptSuite/Extension
The repository includes:
- Working examples for common extension patterns
- Starter templates for custom tab implementations
- Real-world examples following best practices
- Data format examples (Base64, JSON, XML, binary)
