Core concepts
This page explains the data model that the rest of the flowgraph documentation assumes you understand. Read it once and the recipes become readable at a glance.
The shape of a flowgraph
A flowgraph is a JSON document with three lists: nodes, edges
and (optionally) notes. Each node is an instance of a block
type registered in
octopwn/enterprise/flowgraph/registry.py.
classDiagram
class Flowgraph {
+id
+name
+description
+nodes
+edges
+notes
}
class NodeInstance {
+id
+block_type_id
+params
+position
+disabled
}
class EdgeInstance {
+id
+from_node
+from_port
+to_node
+to_port
}
class BlockTypeDef {
+id
+category
+description
+ports
+params
+output_schema
+attack_techniques
}
Flowgraph "1" o-- "many" NodeInstance
Flowgraph "1" o-- "many" EdgeInstance
NodeInstance --> BlockTypeDef : instance of
You will rarely write this JSON by hand. The UI is the authoring
surface, and the JSON is what gets saved, version-controlled and fed
into loadfile for replay runs.
Blocks, ports and items
A block is a unit of behaviour — open a session, run a scanner, filter a stream, save to disk. Each block exposes:
- Ports — typed, named input / output channels. A port is either
direction=inputordirection=output, has atype_name(the wire type), and may be markedoptional=true(rendered dashed in the editor and not required for a node to run). - Parameters — static configuration values set per node from the
config panel. Examples: the wordlist path for a
HASHCAT_WORDLIST, the LDAP filter for a custom query, theportslist forSCANNER_PORTSCAN. - Output schema — for blocks that emit dict-shaped items, the registry documents the fields each output port produces. The frontend uses this to populate FILTER autocomplete.
- Attack techniques — optional list of MITRE ATT&CK technique IDs, surfaced in the kill-chain report.
Items flowing along a wire are normal Python dicts. Most carry a small set of well-known keys:
| Key | Meaning |
|---|---|
__tid |
Target store ID — populated whenever the item is anchored to a stored target. |
__cid |
Credential store ID — populated whenever the item is anchored to a stored credential. |
__session_id |
Session reference ID — present on session_* items emitted by OPEN_SESSION_*. |
__jid |
Journal ID — a monotonically increasing integer that links the item back to the journal entry that produced it. Used by killchain. |
__source_flowgraph_hid |
History ID of the run that produced the item — supports cross-run provenance. |
Block descriptions in the block reference tell you which keys each block writes onto its outputs.
Wire types and matching rules
The engine refuses to connect output ports to input ports unless their
type_name is compatible. A few key rules:
- Exact type match always connects (
scan_result → scan_result). - The
anytype matches anything in either direction — used for generic sinks (TERMINATOR_SINK,FILE_SINK,TAP_SINK). raw_targetconnects into ports that acceptraw_targetorscan_result— most scanners accept either, with__tidresolved from the dict at run time.- The
credential_*family of types (one per protocol) flows out ofCREDMUXand into matching scanner / session / attack ports. The full list lives in typing & wiring. session_<client>types are 1:1 withOCTOPWN_CLIENT_TABLE— for every client there is a correspondingOPEN_SESSION_<CLIENT>andsession_<client>wire type.
How the engine runs
flowchart TB
start([Click Run]) --> ev[Engine validates the graph]
ev --> src[Source nodes start as long-lived asyncio tasks]
src --> wait[Non-source nodes wait until all required inputs have at least one item]
wait --> run[Run block.run inputs params]
run --> fan[Fan outputs onto every downstream port queue]
fan --> wait
fan --> done{All non-source nodes done?}
done -->|no| wait
done -->|yes| pass[Pass complete]
pass --> rerun{rerun_requested?}
rerun -->|yes| src
rerun -->|no| finish([Run complete])
Two consequences worth internalising:
- Sources never block. They emit items continuously (or in a
one-shot burst for non-
*_NEWsources). Downstream blocks queue the items and consume them when they have what they need. - The graph runs to completion every pass. Pass-driven design means a flowgraph that is "correct" for a single host stays correct for ten thousand hosts — the engine simply runs more times.
Snapshot vs. new-only sources
Two variants exist for every source that pulls from a store (credentials, targets, sessions):
SOURCE_<X>emits the full snapshot of the store at the moment the source starts. Use in single-shot runs or whenever you need every item every iteration.SOURCE_<X>_NEWemits only items not yet seen this runloop, plus anything pushed into the matching<X>_QUEUEsink. This is how feedback loops are wired:
flowchart LR
src[SOURCE_CREDENTIALS_NEW] --> attack[CMD_DCSYNC]
attack -->|result| q[CREDENTIAL_QUEUE]
q -.->|next pass| src
CREDENTIAL_QUEUE is a sink. The credentials it receives become
the input for SOURCE_CREDENTIALS_NEW on the next engine pass,
bypassing the "already seen" filter.
SOURCE_CREDENTIALS_NEW in a single-shot run behaves exactly like
SOURCE_CREDENTIALS — the "new-only" filter only kicks in once there
is a prior pass to compare against.
Cross-run state (iter_state)
When you call run, continuous or runloop the executor allocates an
iter_state dict that persists across passes. Three flavours of state
live in it:
- Seen ID sets —
seen_cred_ids,seen_target_ids. Populated by*_NEWsources every time they emit an item. - Pending queues —
cred_queue,target_queue,session_queues,named_target_queues. Filled by*_QUEUEsinks, drained by*_NEWsources on the following pass. - Execution journal — a per-pass list of every block invocation with its inputs, outputs (summarised), parameters and timestamp. This is what killchain walks.
resetstate clears everything. Use it whenever you want a clean
slate without restarting the FLOWGRAPH util session.
Categories at a glance
The registry organises blocks into categories that map onto the pages in the block reference:
| Category | Role |
|---|---|
SOURCE |
Emits items (credentials, targets, sessions, raw strings) into the graph. |
PROMPT_SOURCE |
Same as SOURCE but pops an input dialog before running. Tutorial-friendly. |
QUEUE |
Feedback sinks — hold items for the next runloop iteration. |
SINK |
Terminators — discard, write to disk, or trigger a rerun. |
TAP |
Pass-through probe; lets you inspect a wire from the results panel. |
CONSOLE |
Pass-through logger — formats and prints each item. |
CREDMUX |
Routes a credential stream to protocol-typed output ports. |
FILTER |
Conditional routing, set membership, gates, port gates, deduplication. |
SCANNER |
One block per entry in OCTOPWN_SCANNER_TABLE. |
SESSION |
OPEN_SESSION_* plus the ID splitter helpers. |
COMMAND |
CMD_* — run any client command on a live session. |
ATTACK |
Curated wrappers around OCTOPWN_ATTACK_TABLE. |
ENUMERATION |
Stream LDAP datasets (users / computers / templates / trusts). |
TRANSFORM |
Convert one credential type to another (PFX→NT, hashcat cracking). |
SCRIPT |
User-authored Python coroutine. |
BOUNDARY |
Input / output boundary blocks inside composite inner graphs. |
COMPOSITE |
User-saved composites — facades over a nested flowgraph. |
That is the whole conceptual surface. Everything else in the docs is either UI mechanics, opsec knobs, or example wirings of these pieces.