HOVEL // chapter 12
Chapter 12 Part 03 / Operator Experience

Operator Workflows

The operation is the root object of operator work. A chain is the throw path inside that operation: it is the shared workspace where clients collaborate, add modules, validate readiness, and throw against operation-owned targets.

An entity is any actor connected to hoveld: a human operator in the implemented cli, an MCP client, a future TUI session, an AI agent, or another authorized user. Multiple entities may attach to the same operation. Each attachment has its own active chain, while chain state, operation target inventory, target sets, configuration, steps, and logs are daemon-owned shared state.

Operation Collaboration

Every chain owns an operation-scoped log topic:

operation/<operation>/chain/<chain>/logs

Every entity attached to the same operation and chain subscribes to the same topic. A log statement for a chain change must be visible to every connected CLI client and, once implemented, every TUI and MCP client attached to that chain, but clients working other chains should not receive it by default.

Client-local attachment state:

  1. Attached operation.
  2. Active chain within that operation.
  3. Current prompt mode.
  4. Current log cursor.

Daemon-owned shared state:

  1. Operation records.
  2. Chain records, steps, and chain config.
  3. Operation target inventory, target config, and target sets.
  4. Throw records.
  5. Installed payload records and state-change history.
  6. Structured events, rendered logs, artifacts, and findings.

Required chain log events:

  1. Chain created, renamed, inspected, deleted, or selected.
  2. Entity attached or detached from an operation or chain.
  3. Module added, removed, reordered, or configured.
  4. Target added, removed, updated, grouped, or configured.
  5. Chain configuration set or unset.
  6. Validation started, completed, or failed.
  7. Throw planned, started, progressed, completed, or failed.
  8. Survey facts discovered.
  9. Payload provider output selected.
  10. Installed payload created, connected, marked unreachable, cleaned up, or manually removed.
  11. Exploit step started, completed, or failed.
  12. Finding, artifact, structured event, or throw result emitted.

The CLI should render this as a live transcript. The future TUI should render the same topic in a log panel. Future MCP should expose the same stream through a tool or resource optimized for agents.

Throw Transcript

A throw should look like a live operator transcript, not a JSON blob. The transcript is a terminal rendering of structured log events; JSON, RPC, MCP, files, the CLI, and the eventual TUI must consume the same typed event model rather than parsing terminal text.

Each log event should carry stable structure:

id, time, topic, kind, level, source, message
chain_id, chain_name, run_id, target, module_id
elapsed_seconds
fields
attributes

For throw logs, elapsed_seconds is seconds since the throw started. The terminal renderer displays it inside the label as fixed-width 000.00 seconds, highlighted separately from the purple label background.

Minimum CLI render:

HOVEL//THROW op/redteam-lab chain/ssh-memory

operation    redteam-lab
chain        ssh-memory
entities     operator:will, agent:planner
targets      3
steps        survey:2 exploit:1 payload_provider:1
status       validating

┃ :: validate 000.01 checking chain configuration
┃ ++ validate 000.02 global config complete
┃ ## validate 000.03 target router-01 missing ssh.username
┃ :: chain    000.04 added module mock-survey as step step-1
┃ :: target   000.05 added mock://router-01
┃ >> stage    000.06 throw started
┃ :: survey   000.07 router-01 os=linux arch=x86_64
┃ $$ payload  000.08 selected payload mock-payload-x86_64-linux
┃ ++ exploit  000.09 router-01 completed mocked exploit flow
┃ ++ throw    000.10 completed 1/1 target(s)

The eventual TUI live throw view should split the same state into:

  1. Chain header.
  2. Attached entity presence.
  3. Step timeline.
  4. Target matrix.
  5. Configuration validation panel.
  6. Chain log stream.
  7. Findings and artifacts panel.

Module Database

Hovel needs a daemon-owned module database. The initial module types are:

survey
exploit
payload_provider

The type set must be expandable later without changing the chain model.

A module database record contains:

  1. Module ID.
  2. Name.
  3. Module type.
  4. Version.
  5. Summary and description.
  6. Tags.
  7. Runtime kind.
  8. Author or source.
  9. Required and optional chain configuration schema.
  10. Required and optional target configuration schema.
  11. Output schema.
  12. Safety labels.
  13. Enabled or disabled state.

Implemented commands:

module available
module available --type survey
module installed
module installed --type survey
module inspect <module-id>
module search <query>    # compatibility alias for local availability search
Direct terminal demo inspecting mock survey and mock exploit module metadata before running a saved chain.
Demo: mock module discovery before execution

Chain Steps

Chains contain ordered steps. A step usually references a module from the module database.

Implemented commands:

chain add <module-id>
chain inspect

Adding a module logs to the chain topic. Removing or reordering a step is a planned editing capability, not a current registry command. When step editing lands, those mutations must also log to the chain topic. Step IDs must be stable so validation errors, logs, and UI selection can point at a specific item.

Payload providers must also be visible as chain steps. Operators should add Squatter explicitly with chain add squatter.bind or chain add squatter rather than configuring low-level payload file paths on every target. For the current MS17-010-to-Squatter TCP bind slice, that step contributes chain-scoped Squatter settings such as squatter.bind_port and optional squatter.remote_path. During planning Hovel derives the MS17-010 install inputs for each selected target from that step, including the staged local payload path, optional fixed remote path, bind port, payload transport, and SMB transport port. When no fixed remote path is configured, the MS17-010 exploit generates a fresh target path during execution and records it in the result artifact. During execution, a Squatter bridge step means the MS17-010 exploit installs the staged payload and Hovel immediately connects any recorded Squatter installed payload through the provider, so a successful handoff produces a normal session visible in session list. An MS17-010-only chain may still run a command such as calc.exe, but it does not create a Squatter session.

Configuration

Configuration is a typed key-value dictionary.

Scopes:

chain config       applies globally to the active chain
target config      applies to one operation-owned target across chains

Every survey, exploit, payload provider, or future chain item can declare:

  1. Required chain configuration keys.
  2. Optional chain configuration keys.
  3. Required per-target configuration keys.
  4. Optional per-target configuration keys.

Built-in value types:

string
bool
int
float
enum
duration
url
host
port
cidr
path
list<string>
map<string,string>

Each key definition supports:

  1. Name.
  2. Type.
  3. Required flag.
  4. Default.
  5. Description.
  6. Allowed values for enums.
  7. Validation rule.
  8. Scope: chain or target.

Implemented commands:

config set <key> <value>
config unset <key>
config list
config interactive

target config set <target> <key> <value>
target config unset <target> <key>
target config list <target>

target bind <target>
target unbind <target>

target set create <name>
target set add <name> <target>
target set remove <name> <target>
target set list
target set inspect <name>
target group create <name>
target group add <name> <target>
target group remove <name> <target>
target group list
target group inspect <name>

config interactive is the active-chain alias for the CLI-only chain config interactive wizard. It is implemented inside the existing go-prompt loop and uses the same session mutation path as chain config set and target config set. It first renders the current chain and target configuration as a numbered menu, then changes the prompt into config-selection mode with completions for editable items, continue, and cancel. When the operator continues, Hovel changes the prompt into config-value mode, offers type-aware completions where possible, walks the remaining required chain and per-target keys, validates each typed value as it is entered, and repeats until all required configuration is set or an unfixable validation issue remains.

Validation

chain validate checks whether the active chain is ready to throw.

Validation must evaluate:

  1. Active chain exists.
  2. Chain has at least one step.
  3. The selected throw target source has at least one target.
  4. Every step references an enabled module in the module database.
  5. Required chain configuration keys are set.
  6. Required target configuration keys are set for every target.
  7. Values parse into their declared types.
  8. Enum values are valid.
  9. Sensitive operator-controlled values are validated like any other configured value.
  10. Payload provider requirements are satisfiable.
  11. Every chain step has a stable ID.

Validation output must be human-first and scriptable with --json.

Targets are operation assets, not chain-local records. Existing commands such as target add and target config set continue to work in the active operation, but target add only binds to a chain when the caller is in chain context. target bind and target unbind adjust active-chain bindings explicitly. A throw selects targets from one of three sources: an explicit --target, an explicit --target-set/--target-group, or the active chain's bound targets. Target sets/groups are convenience views over the same target records; they do not duplicate target config.

When a target set contains mixed readiness, Hovel plans and throws against the compatible targets and reports skipped targets with the validation issues that made them incompatible. If no compatible targets remain, planning fails. Explicit --target remains strict: an incompatible explicit target blocks the throw so a typo or missing config is not silently ignored.

Hovel CLI demo listing missing required chain and target config.
Step 1 list missing required config
Hovel CLI demo setting target and chain values and listing resolved config.
Step 2 set values and verify config
Hovel CLI demo validating and saving the configured chain.
Step 3 validate and save the configured chain

Payload Commands

The payloads command suite is the operator surface for payload discovery, stamped generated artifacts, installed payload inventory, reconnect, refresh, and cleanup. It is intentionally separate from session: payload inventory commands answer what is available or installed; session commands answer what is connected now. Both suites can also expose typed provider-owned actions through their commands/call subcommands.

Implemented commands:

payloads available
payloads available --json
payloads installed
payloads installed --all
payloads installed --state installed|connected|unreachable|removed
payloads inspect <payload-id> [--events] [--json]
payloads connect <payload-id> [--json]
payloads refresh <payload-id> [--json]
payloads cleanup <payload-id> [--reason <reason>] [--json]
payloads mark-removed <payload-id> [--reason <reason>] [--json]
payloads register-squatter <target> [--host <host>] [--port <port>] [--json]
payloads commands <payload-id> [--json]
payloads capabilities <payload-id> [--json]
payloads call <payload-id> <capability> [--arg <value> ...] [--set <key=value> ...] [--json]
payloads getfile <payload-id> <remote-path> [--json]
payloads putfile <payload-id> <local-path> <remote-path> [--json]
payloads cmd <payload-id> <command-line> [--json]

Installed payload IDs use short operator-facing handles such as p1 and p2, backed by stable internal ids. The inventory is global to the workspace and stores operation, chain, target, throw, run, stamp, and artifact provenance; switching chains must not hide an installed payload. payloads installed shows active operational inventory by default: installed, connected, and unreachable. Removed records appear only with --all or an explicit removed-state filter.

payloads available currently lists configured payload_provider modules from the live module catalog; the Go provider RPC surface also defines list_payloads for richer provider discovery. payloads installed is a local database read and does not probe targets by default. payloads refresh is implemented as a record-preserving command today; provider-backed validation is still a planned behavior. payloads connect asks the current provider module to use the stored reconnect descriptor; if the provider is missing or incompatible, the record remains visible with reconnect marked unavailable.

payloads commands and the payloads capabilities alias list provider-owned typed actions for an installed payload. payloads call invokes one of those actions without opening an interactive session. payloads getfile, payloads putfile, and payloads cmd are compatibility shortcuts over the same call path. Human output renders command catalogs as tables with safe/write/destructive effect labels and pretty-prints JSON command stdout or stderr.

payloads connect <payload-id> creates a normal Hovel session when reconnect succeeds. The session appears in session list and links back to the installed payload id. The MS17-010-to-Squatter bridge performs this connection automatically during the throw after the MS17-010 exploit records the installed payload; payloads connect remains the manual reconnect path for already installed payloads. Providers may support multiple active sessions per installed payload; when they do not, the provider should reject a second connection with a clear error and Hovel should preserve the installed record.

Cleanup has two explicit paths. payloads cleanup <payload-id> asks the provider to execute its cleanup descriptor, then marks the installed payload record removed if provider cleanup succeeds. payloads mark-removed <payload-id> records operator bookkeeping when the payload is already gone or the target was rebuilt.

Commands that read or mutate installed payload records accept --workspace for explicit workspace selection. payloads inspect --events includes the installed-payload state-change history. Singular payload ... aliases exist for the implemented command paths.

Artifact Commands

The artifact command suite is the operator surface for materialized artifact records produced by throws, payload generation, and module execution.

artifact list [--workspace <path>] [--json]
artifact inspect <artifact-id> [--workspace <path>] [--json]

Artifact commands are read-only in the current registry. They inspect the workspace's recorded artifact metadata and support plural artifacts ... aliases for the implemented command paths.

Post-Exploitation Sessions (MSF-Style)

Hovel should treat post-exploitation access as a first-class operation object, not an ad-hoc log line. A successful exploit step may open zero, one, or many sessions. Sessions should be stable, interactive, resumable, and automatable across CLI and future TUI and MCP adapters.

Session Goals

  1. Operator UX parity with Metasploit: easy sessions listing, interact, background, kill, rename, and tagging.
  2. Multi-entity collaboration: one operator can hand off a live session to another operator or agent without losing context.
  3. Module maintainer simplicity: modules emit typed session open/close/update events and implement a narrow runtime contract.
  4. Daemon ownership: session lifecycle and transcript persistence live in hoveld, not in front-end memory.

Session Domain Model

Each session record should include:

  1. Session ID (stable within workspace).
  2. Operation and chain identifiers.
  3. Source run ID and source step ID.
  4. Target identifier.
  5. Session kind (shell, meterpreter-like, agent, extensible).
  6. Transport (tcp, http, https, named-pipe, etc.).
  7. Lifecycle state (opening, active, backgrounded, lost, closed).
  8. Ownership and lock state (free, attached, read-only observer).
  9. Capabilities (exec, upload, download, pivot, portfwd, pty, etc.).
  10. Last-seen timestamp and liveness metadata.
  11. Session labels/tags and optional notes.

Session Control Plane

Implemented operator commands:

session list
session connect <session-id> [--history-lines <n>|--history-bytes <n>|--no-history]
session tail <session-id> [--lines <n>|--bytes <n>] [--json]
session read <session-id> [--tail] [--json]
session send <session-id> <data> [--end <terminator>|--no-newline] [--json]
session close <session-id> [--json]
session commands <session-id> [--json]
session capabilities <session-id> [--json]
session call <session-id> <capability> [--arg <value> ...] [--set <key=value> ...] [--json]

session connect attaches the terminal to an active daemon-backed session from either hovel cli or a direct hovel session connect command. It prints recent history by default, supports byte or line history limits, and detaches on Ctrl-]. session tail prints recent transcript output without attaching. session read --tail streams until close or interruption. session send appends a newline by default; use --no-newline or --end for exact terminators. The session write alias maps to session send. session commands and session capabilities list typed provider-owned actions for an active session. session call invokes one action without writing bytes to the interactive stream, which is useful for agent sessions such as Squatter where tasking is typed even when an interactive shell-like view is also available.

Hovel CLI demo listing, reading, sending to, and reading from a mock shell session.
Step 1 non-attached session IO
Hovel CLI demo attaching to a mock shell session, detaching, and reconnecting.
Step 2 attach, detach, and reconnect

session inspect, session use, session background, session rename, and session tagging are future control-plane goals rather than current registry commands.

Control-plane requirements:

  1. Session attach/detach is explicit and logged.
  2. Concurrent attach policy is configurable (single-writer + multi-reader by default).
  3. Session loss is surfaced immediately with reason codes.
  4. Session takeover is explicit (--force) and audited.

Session Data Plane

Interactive traffic must flow through daemon-managed streams:

  1. Input stream: operator/agent keystrokes or commands.
  2. Output stream: remote stdout/stderr/events/chunks.
  3. Side-channel events: file transfer, port forward lifecycle, privilege escalation hints.

Design constraints:

  1. Binary-safe framing (not line-oriented only).
  2. Backpressure-aware buffers per attached entity.
  3. Replay cursor for reconnecting clients.
  4. Operator-defined filtering hooks if users need local output suppression.

Module Runtime Contract

Modules that can create sessions should implement:

  1. Declared capability: opens_sessions=true.
  2. Session open event including target, session kind, and capability set.
  3. Optional heartbeat/liveness updates.
  4. Session close event with terminal reason.

This keeps module maintainer burden low: they do not implement global session registries, lock arbitration, or persistence.

Acceptance Criteria

Operators and maintainers should both agree the model is the default when:

  1. A successful exploit reliably emits discoverable session objects within one command (session list).
  2. Detach/reattach is lossless for context and transcript.
  3. A second operator can safely observe or take over a session with auditable state transitions.
  4. Session APIs are identical across CLI and future TUI/MCP adapters, with only presentation differences.
  5. Module authors can add session support by emitting typed events and passing contract tests.

Mock Modules

The mocked stage should provide enough modules to exercise every UI path without executing real target behavior.

Required example modules:

  1. mock-survey
    • Type: survey.
    • Lives in its own Python project under modules/examples/python/mock_survey.
    • Requires target config: target.host: host, target.port: port.
    • Emits facts and a module log.
    • Tests per-target config, fact output, SDK schema declaration, and stdio JSON-RPC launch.
  2. mock-exploit
    • Type: exploit.
    • Lives in its own Python project under modules/examples/python/mock_exploit.
    • Requires chain config: operator.confirmed_lab: bool.
    • Requires target config: target.host: host, target.port: port.
    • Emits an example finding, artifact, result, and module log.
    • Tests throw transcript, final result rendering, SDK schema declaration, and stdio JSON-RPC launch.
    • Tests error rendering and failed throw states.