Forge
Forge is how the library generates traffic: one-shot sends, persistent forge sessions, captured-session replay, ordered playbooks, and injection into live proxy sessions.
One-shot send¶
send_frame() opens a connection, sends bytes, collects the reply, and
returns a SendResult (sent_bytes, received_bytes, response_packets,
success, error).
result = await api.send_frame(host="10.0.0.1", port=9090, data=b"\x01\x00\x05admin")
print(result.received_bytes.hex())
for pkt in result.response_packets: # individual framed reply chunks
print(pkt.hex())
send_frame() can also reuse an existing forge or proxy session instead
of opening a fresh connection — pass source_session_id=<id>. Works for TCP,
SOCKS5, and UDP.
Persistent forge sessions¶
Keep a connection open across multiple sends:
sid = await api.open_forge_session(host="10.0.0.1", port=9090, tls=False)
r1 = await api.send_on_forge_session(sid, data=b"HELLO\r\n")
r2 = await api.send_on_forge_session(sid, data=b"LIST\r\n")
await api.terminate_session(sid)
Replaying a captured session¶
forge_session() re-sends a captured session's client→server frames against
a target and returns a ForgeResult wrapping a new replayed Session.
result = await api.forge_session(session_id)
print(result.success, result.error)
for frame in result.frames_sent():
print("sent: ", frame.raw_bytes.hex())
for frame in result.frames_received():
print("received:", frame.raw_bytes.hex())
With a protocol definition loaded you can replay while overriding specific fields — length fields are recomputed automatically:
result = await api.forge_session_with_field_edits(
session_id,
field_edits={"LoginRequest": {"username": "admin", "password": b"\x00" * 16}},
server_host="newhost.example.com", # optional: replay against a different target
)
Playbooks¶
A Playbook is an ordered list of PlaybookFrames. It either opens a fresh
connection (host/port/tls/transport) or reuses an existing proxy
session (source_session_id). run_playbook() executes the frames in order
and auto-manages the connection.
from protopoke.forge.models import Playbook, PlaybookFrame
playbook = Playbook.create(
label="Login sequence",
host="10.0.0.1",
port=9090,
transport="tcp", # "tcp" (default) or "udp"
)
playbook.frames = [
# raw_hex is a space-separated hex string; may contain {{VAR}} tokens.
PlaybookFrame.create(label="Login", raw_hex="01 00 05 61 64 6d 69 6e"),
PlaybookFrame.create(label="List users", raw_hex="03"),
]
run = await api.run_playbook(playbook) # -> PlaybookRun
Each PlaybookFrame also has a direction — "client_to_server" (send
toward the server) or "server_to_client" (inject toward the client).
Variables¶
Playbook frames support {{VARIABLE}} placeholders resolved at runtime from
the shared variable store. The store is shared across every pipeline
(intercept, forge, playbooks), so a value captured by a
script replace rule flows straight into a
playbook frame.
api.variables["TOKEN"] = "aabbccdd" # hex-encoded byte string
frame = PlaybookFrame.create(label="Auth with token", raw_hex="02 {{TOKEN}}")
Injecting into live sessions¶
Inject a frame into an active proxy session (TCP, SOCKS5, or UDP):
await api.inject_to_server(session_id, data=b"\x01\x02\x03")
await api.inject_to_client(session_id, data=b"\x04\x05\x06")
This is what makes half-open sessions useful. When the client
disconnects, the session moves to ONLY_SERVER but the upstream stays open,
so inject_to_server() keeps working; likewise inject_to_client() after
the server drops. The session reaches CLOSED only once both sides are gone
or you call terminate_session().
Next¶
- Custom Replace Scripts — populate
{{VARIABLE}}values - User Interface — Forge — the same, in the TUI