HOVEL // chapter 11
Chapter 11 Part 02 / Runtime Platform

Operations, Chains, and Throws

Operations are the durable work context. Chains are workflows with typed steps inside an operation. Throws are replay-inspectable executions of a chain against targets.

Chains have three related forms:

  1. An interactive chain record owned by an operation and edited through hovel cli.
  2. A chain template, which is a reusable chain definition with steps but without configured values or target bindings.
  3. A configured chain file, which represents a chain, its steps, configured string values, selected target bindings, and target config for review, sharing, and one-shot execution.

Interactive editing and persistent representation are separate concerns. chain add <module> remains the fast CLI workflow for building a chain. chain save <file> writes the active chain to a YAML file, and chain load <file> imports or updates a chain from that file. One-shot shell execution throws a configured chain file rather than relying on prompt session state. Target config is essential to configured chain files because it is part of what the operator reviews and confirms before throwing.

The current chain thrower is intentionally small. It executes ordered module steps with string chain and target configuration, persists plans and confirmation records, and records events, artifacts, installed payloads, throw records, and final state produced by the module or provider path. Chain files do not expose ${inputs.*} or ${steps.*} expression syntax today, and they do not support arbitrary expressions, loops, embedded scripting, dynamic graph mutation, or user-defined functions.

Operator-facing chain records are CRUD resources. A chain can be created, selected with chain use, renamed, inspected, listed, deleted, and then thrown against targets selected from the active operation. The active chain is client attachment state: it determines that client's prompt context, default chain for throw, and log topic subscription without changing another client's active chain.

Targets are owned by operations in the operator workflow. A target added in operation context is available to every chain but is not bound to any chain. A target added while alpha is active is still an operation target and is immediately bound to alpha; switching to beta does not make it a beta target until the operator binds it there. Target config follows the target, not the chain. Target sets, also available as target groups, are named operation-owned groups used only to select targets conveniently at plan time. Chain logs remain chain-scoped: chain logs renders the active chain's operation-scoped topic only, and multi-client front ends attached to the same operation and chain observe the same topic.

A throw selects a target source when planning. throw --target <id> is strict and blocks on an incompatible target. throw --target-set <name> and throw --target-group <name> select a named operation target group. Without an explicit selector, an interactive chain throw uses the active chain's bound targets and does not fall back to the whole operation inventory.

Hovel CLI demo creating a chain and adding modules and target.
Step 1 create the interactive chain record
Hovel CLI demo listing missing required chain and target config.
Step 2 inspect required config
Hovel CLI demo setting and listing resolved chain and target config.
Step 3 apply reviewed config
Hovel CLI demo validating and saving the configured chain.
Step 4 validate and save the chain file

Payload-consuming chains are composed from typed capability steps rather than provider-before-exploit ordering alone. A module reports, over JSON-RPC, which step contracts it supports, what each step requires, what it produces, which configuration schema applies, and which prepare-time values it materializes. Hovel loads those contracts into the module catalog, uses them during validation and planning to report typed missing requirements, and executes capability-step chains after confirmation. Persisting an exact contract snapshot at confirmation time, presenting editable prepared values in the review surface, and blocking post-confirmation contract drift are target safeguards rather than current source behavior. Squatter is the first core provider following this model; see Squatter Payload Provider.

Capability-Based Chain Runtime

Chain edges are typed capability matches, not arbitrary JSON handoffs. A step requirement is satisfied by a producer output when the capability type, required attributes, and allowed state match. Hovel owns the core schemas and validates module outputs before accepting them. Modules may add namespaced extension attributes, and a consumer may match on an extension only when it explicitly declares that namespace.

RemoteExecutionCapability
CredentialCapability
PayloadArtifact
PayloadInstance
TransportEndpoint
SessionRef
CleanupHandle

Every capability has a stable id, schema version, current state, producer step, and event history. Capabilities keep mutable current state for operator ergonomics and append-only history for audit. Diagnostic evidence is separate: evidence explains what happened, while capabilities are machine-consumable chain state. Modules may not emit undeclared capability outputs by default; undeclared material must be recorded as diagnostic evidence.

The lifecycle is explicit:

Step Lifecycle
  1. describe
  2. prepare
  3. confirm
  4. execute
  5. operate
  6. cleanup
  1. describe queries live module contracts over JSON-RPC.
  2. prepare is pure and local: it validates config and generates planned values, but it does not touch the target or produce final payload bytes.
  3. confirm currently records the reviewed plan, target set, module or step references, and configuration; freezing contract snapshots and prepared values is planned.
  4. execute materializes final artifacts, mutates targets, and records exact hashes and state transitions.
  5. operate keeps sessions attachable, detachable, and reconnectable while installed payload records remain active.
  6. cleanup executes durable cleanup handles, records verification where possible, and remains retriable until removed, verified, or abandoned.

The target safety model blocks execution if a module reports a different contract after confirmation; the operator must review the contract diff, re-prepare if needed, and re-confirm. The current implementation has the runtime pieces for step.prepare and step.execute, but generic pre-confirm prepared-value editing and drift blocking remain pending.

The catalog owns the first planning primitive: it can resolve every enabled module step against the current capability set and report deterministic bindings plus typed missing requirements. The same projection is exposed through module inspect --json so operators and front ends can see which live JSON-RPC step contracts are ready and which capabilities still need to be produced.

The first execution primitive is a generic capability step runtime. It executes an ordered set of module step references, resolves inputs before each step, calls step.prepare, calls step.execute, records produced capabilities, applies state transitions, and blocks at the first missing requirement. This primitive is intentionally not MS17-010-specific; the MS17-010-to-Squatter path is the representative chain used to prove the model.

Chain files identify both layers explicitly. uses locates the module package, and optional step locates the JSON-RPC step contract inside that module. Omitting step on every step preserves the legacy single-module execution path. Setting step opts the configured chain file into capability-step execution, and every step in that file must then name a JSON-RPC step contract, except for the current Squatter TCP-bind bridge. The approved throw plan records the module references and callable step references, but not yet full contract snapshots.

A capability-step throw executes the configured ordered steps through the capability runtime rather than the legacy per-module daemon runner. It produces one aggregate run per target. That aggregate run carries the final capability set, diagnostic evidence, logs, and any opened sessions; step-level prepare and execute detail remains available through capability evidence and structured events. One-shot chain-file throws use the same primitive with the configured module catalog and JSON-RPC step runner, so MS17-010-to-Squatter is not a special throw path; it is one ordered capability-step chain.

steps:
  - id: exploit
    uses: module:ms17-010@v1
    step: ms17-010.exploit
  - id: install
    uses: module:squatter-provider@v1
    step: squatter.install_smb

Representative MS17-010 to Squatter Chain

The MS17-010 chain is the representative example for payload and session orchestration. The MS17-010 module demonstrates exploitation and access establishment; it does not speak Squatter's protocol and does not own Squatter sessions.

ms17-010.exploit
  produces: RemoteExecutionCapability(connection_bound)

windows.credential.create_local_admin
  requires: RemoteExecutionCapability
  produces: CredentialCapability(protocol=smb, durable), CleanupHandle

squatter.generate
  produces: PayloadArtifact(provider=squatter)

squatter.install_smb
  requires: RemoteExecutionCapability, CredentialCapability(protocol=smb), PayloadArtifact
  produces: PayloadInstance(provider=squatter, transport=smb-named-pipe),
            SmbPipeEndpoint, CleanupHandle, InstalledPayloadDescriptor

squatter.connect_smb
  requires: PayloadInstance(provider=squatter, transport=smb-named-pipe),
            SmbPipeEndpoint,
            CredentialCapability(protocol=smb)
  produces: SessionRef(provider=squatter, transport=smb-named-pipe)

The same Squatter provider also supports TCP bind and TCP callback branches. SMB mode requires a credential capability. TCP bind requires a target endpoint. TCP callback requires a listener step. If the payload starts but no callback has arrived, the installed payload record remains installed with provider-owned callback status in its reconnect metadata; failed refresh or connect attempts may move it to unreachable.

Prepared Squatter SMB plans allocate neutral random identifiers before confirmation: credential username and password, service name, staged filename, pipe name, transport config, and planned cleanup handles. These identifiers do not include Hovel, Squatter, run ids, or other tool-identifying strings. Hovel evidence stores the full mapping plainly for remediation, including plaintext credentials.

Install success with session connection failure is not a generic failed chain. Hovel persists the explicit installed payload descriptor, marks the installed record unreachable, and records warning evidence. The CLI must show exactly what happened: target, staged path, service name, pipe, plaintext credential, connection endpoint, exact error, active cleanup handles, and reconnect/cleanup commands. Downstream steps are blocked only when their declared requirements need a live SessionRef; steps that require only a PayloadInstance may continue.

Installed Payload Records During Throws

Installed payload inventory is created only from explicit provider or module records. Hovel must not infer installation from log text, generic artifacts, connection attempts, or the mere existence of a payload binary. Any module may return an installed-payload descriptor, but it must name the provider and payload id that own reconnect semantics.

Hovel core validates the descriptor against the current provider contract, attaches operation, chain, target, throw, run, stamp, and artifact provenance, then writes the current-state row and append-only state event to SQLite. The provider owns opaque reconnect and cleanup descriptors; Hovel owns persistence, handles, audit events, and command/API presentation.

Repeated installs reconcile only with explicit identity. If the provider returns a stable instance_key or the same payload stamp reports itself again, Hovel may update the existing record. Without that proof, a successful install creates a new record even if host and transport look similar.

Operation Attachments

Each implemented CLI client, and each future TUI, REST, or MCP client, attaches to exactly one operation at a time and keeps its own active chain selection.

Concurrency rules:

  1. op use <operation> changes only the caller's attached operation.
  2. chain use <chain> changes only the caller's active chain inside the attached operation.
  3. chain add, chain config, chain validate, and throw without --chain use the caller's active chain.
  4. target add mutates the operation inventory and, when a chain is active, binds the target to that chain. target bind, target unbind, and chain-context target clear mutate only active-chain target bindings. Operation-context target clear is destructive and requires confirmation.
  5. Saved chain-file throws import or execute the file without moving another client's active chain.
  6. Mutations to one shared chain are serialized by the daemon and broadcast to subscribers of operation/<operation>/chain/<chain>/logs.
  7. Different clients may work different chains in the same operation concurrently.

Chain Definition

Current chain files use the flat descriptor shape enforced by hovel.chain.schema.json and the Go descriptor parser. They do not contain inputs, phases, or per-step with blocks. Chain and target configuration are string maps reviewed before confirmation. The descriptor schema accepts module:, service:, and provider: step references, but the current one-shot throw path executes module: steps.

apiVersion: hovel.dev/v1alpha1
kind: Chain
metadata:
  name: ssh-memory-flow
spec:
  mode: configured
  steps:
    - id: survey
      uses: module:ssh-survey
    - id: exploit
      uses: module:ssh-memory@0.1.0
  config:
    operator.confirmed_lab: "true"
    payload.transport: "ssh"
  targets:
    - id: ssh://10.41.32.2
      config:
        target.host: "10.41.32.2"
        target.port: "22"

Capability-step chain files may add a step field to each step. A throw that uses capability-step execution must declare step on every step in the file, except for the current Squatter TCP-bind bridge step.

A chain template records reusable workflow shape:

  1. Chain identity. The schema accepts descriptor metadata such as version and description, but current command flows use metadata.name as the durable chain identity.
  2. Ordered steps. The schema admits module:, service:, and provider: references; current one-shot throws execute module: references.
  3. Module references with resolved versions when available.
  4. Configuration schema is resolved from the referenced modules during validation and planning; it is not embedded in the current chain file shape.
  5. Expected providers, services, listeners, sessions, and artifacts are planning outputs when known, not fields in the current chain descriptor.

A configured chain file must contain enough information to reconstruct the review surface without relying on hidden prompt state. At minimum it records:

  1. Everything in the chain template.
  2. Chain-level configured values.
  3. Target list.
  4. Per-target configuration values.

Risk review and confirmation metadata live in throw-plan and confirmation records, not in chain files.

Payload plans include selected provider, selected payload query, target-scoped provider configuration, expected format and platform, compatibility facts, and whether pre-throw listener or post-throw connect hooks are required. They do not include generated payload bytes, installed payload handles, runtime session IDs, or provider-side handles; those values are produced after confirmation and recorded as events, artifacts, and installed-payload records.

Current Thrower Support

The implemented thrower supports:

  1. Sequential steps.
  2. String chain and target configuration.
  3. Capability-step input binding through declared module step contracts.
  4. Per-step status and logging.
  5. Artifact creation.
  6. Fact propagation as ordinary step output.
  7. Cancellation between steps.
  8. Persisted throw plans and throw records.
  9. Confirmation records, including prompt confirmations and --now bypass confirmations.

review and throw share the same planning and review code path. confirm stops after recording a pre-confirmation. review always displays the reviewed plan, requires the operator to type yes, and records or refreshes the confirmation without starting execution. throw starts execution only after an existing confirmation, an inline typed yes, or an explicit --now bypass has produced a confirmation record.

Direct command demo creating a chain and adding modules and target.
Step 1 create the chain from direct commands
Direct command demo listing missing chain and target config.
Step 2 inspect required config
Direct command demo setting and listing resolved chain and target config.
Step 3 apply and verify config
Direct command demo validating and saving a configured chain.
Step 4 validate and save

Eventual Throw Runtime Requirements

The eventual chain runtime should support:

  1. Sequential steps.
  2. Parallel steps across targets.
  3. Conditional branches.
  4. Step retries.
  5. Timeouts.
  6. Cancellation.
  7. Service startup.
  8. Service teardown.
  9. Per-step logging.
  10. Per-service logging.
  11. Per-target status.
  12. Artifact creation.
  13. Fact propagation.
  14. Listener and session state.

Productizing PoCs

Hovel exists because productizing proofs of concept is hard. A chain should absorb the work needed to turn a crude one-off script into a repeatable operator workflow:

  1. Input validation.
  2. Target normalization.
  3. Survey and fact collection.
  4. Payload selection.
  5. Listener startup.
  6. Transport selection.
  7. Error handling.
  8. Artifact and transcript capture.
  9. Cleanup.
  10. Logging.
  11. Multi-target execution.

Conceptual SSH Memory Chain

This chain is a conceptual example, not a current repository dependency. It uses authenticated SSH as a transport and demonstrates payload delivery/execution in a controlled authorized environment.

The Hovel repository should not need to provide the payload generator, listening post, or target-specific execution components for this example. Those should arrive through providers, managed services, or third-party modules.

Phases:

SSH Memory Chain Phases
  1. service_prepare
  2. survey
  3. resolve_payload
  4. prepare_delivery
  5. execute
  6. collect
  7. cleanup
  8. service_cleanup

Survey questions:

  1. What OS is the target?
  2. What architecture is the target?
  3. Is /dev/shm present?
  4. Is /tmp memory-backed?
  5. Is Python 3 present?
  6. What shell is available?
  7. Are required environment variables present?
  8. What execution constraints exist?

Delivery strategy categories:

stdin_loader
python_ctypes_loader
tmpfs_exec
command_only

Public examples should keep payloads benign and lab-oriented.

Expected outputs:

  1. Target facts.
  2. Started services.
  3. Started listeners.
  4. Selected strategy.
  5. Payload metadata.
  6. Execution transcript.
  7. Session refs.
  8. Artifacts.
  9. Result status.
  10. Cleanup status.