Providers, Workspace PKI, Artifacts, and Structured Logging
Providers let modules and chains ask for typed resources without hard-coding where those resources come from.
In the current implementation, provider behavior is exposed through module RPC:
Go modules can implement PayloadProvider, Go and Python modules can
implement step.* providers, Go/Python/Rust modules can implement
strongly typed optional credential and Mesh contracts, and any module can return
explicit installed-payload descriptors. The SDK contexts do not expose a
universal ctx.providers convenience API.
The consuming module should not care whether the payload came from a local file, a build step, an artifact cache, another module, a managed service, or an external project.
Current Provider Scope
The implemented provider path is intentionally narrow. Artifact provider is a product role here, not a current SDK interface:
- Hovel core ingests module file-like outputs as artifacts and owns their hashes, metadata, and run associations; there is no exported
ArtifactProviderSDK contract today. PayloadProviderresolves tagged bytes and returns typed payload results. Hovel core records hashes or artifact references and validates installed-payload reconnect descriptors before accepting them. The provider may eventually become the live-discovery source for richer payload catalogs. Today,payloads availableprojects configured payload-provider modules until live discovery is wired through the operator command.CredentialProvideris a set of optional Go/Python/Rust module contracts: discovery, runtime material, protected files, provider encoding, and standard or advanced stamping. Each request is bound to an assignment, exact provider descriptor, and non-secret operation scope.MeshProviderexposes typed discovery, topology, beacons, listeners, tasks, and streams; credential runtime/file delivery can run immediately before a consuming Mesh listener, task, or stream in the same provider process.- The structured logging rail reports provider resolution and artifact creation, but is not itself a provider.
FactProvider, a general managed ListenerProvider, and a
general SessionProvider remain product roles rather than one mandatory
interface. Facts can start as module outputs, Mesh owns its own listener/session
surfaces, and richer managed-service providers can arrive without widening every
module contract.
Capability Records
Chain steps communicate reusable state through capabilities. The daemon owns the core schemas, validates module outputs, persists current state, and appends state-transition history. Modules own observed transitions and provide evidence explaining what happened.
RemoteExecutionCapability
CredentialCapability
PayloadArtifact
PayloadInstance
TransportEndpoint
SessionRef
CleanupHandle
Capabilities are distinct from evidence. A capability may be consumed by later
steps; evidence is displayed and audited. Failed steps may still produce partial
capabilities with explicit states, for example a Squatter
PayloadInstance that produced an installed payload record but did
not produce a live SessionRef, plus a warning evidence record.
Target-created credentials as evidence
Credentials created on a target during an assessment are evidence and remediation
material. Hovel stores a CredentialCapability in plaintext and shows
it plainly in normal CLI output so an operator can remove the account or recover
when automated cleanup fails. The sensitive field marks the material
for evidence export and downstream handling; it does not cause operator CLI
redaction.
CredentialCapability:
protocol: smb
principal: local
username: m7q4z92d
password: plain-high-entropy-password
sensitive: true
state: active
cleanup: registered
Target-side generated names should be neutral random strings by default. They should not include Hovel, Squatter, run ids, or tool-identifying markers. Evidence stores the mapping needed for remediation.
Workspace PKI is encrypted custody, not chain evidence
Workspace PKI solves a different problem. The daemon owns authorities, immutable certificate generations, private keys, trust sets, CRLs, assignments, and audit history. Private key material is envelope-encrypted in SQLite; the wrapping master keys live in an owner-only workspace file. Normal list and inspect projections do not contain private bytes. Export and signing require explicit authorization and scoped policy.
| Concept | Owner and purpose | Normal visibility |
|---|---|---|
CredentialCapability | A module-created target account or access secret used as chain state and remediation evidence. | Plaintext in operator workflow output by design; sensitive guides downstream handling. |
| Workspace PKI generation/bundle | Daemon-owned certificate, key, chain, trust, and revocation material governed by assignment and export policy. | Public metadata and certificates are inspectable; private bytes stay in encrypted custody unless explicitly resolved for an authorized operation. |
| Provider delivery | One short-lived projection of an active assignment delivered to its declared consumer slot. | Secret bytes, references, and protected paths are transient; durable records keep digests, sizes, provenance, and non-secret receipts. |
Do not convert target-created account evidence into workspace PKI, and do not copy
workspace private keys into CredentialCapability. The shared word
“credential” does not make their custody or display policies interchangeable.
Credential delivery and stamping
A provider advertises only what it consumes through
credential.describe and
hovel.pki.credential-delivery/v1. Slots constrain purpose, endpoint
role, consumer type, bundle/profile/compatibility versions, projections, material
forms, maximum encoded size, private-material policy, and stamping remainder
policy. Optional execution methods are:
credential.runtime— deliver one resolved byte or provider-scoped reference projection directly to the provider process;credential.files— deliver bounded owner-protected files valid for the invocation;credential.encode— invoke one advertised, versioned provider encoding schema under a hard output limit;credential.stamp— apply a persisted plan to an exact artifact or provider-owned deployment using a named or advanced typed target.
Secret-bearing unions fail closed. Resolved material is exactly bytes or a scoped reference; artifact content is exactly inline data or a protected path; stamp output is exactly an artifact or deployment. SDK secret wrappers redact ordinary string/debug formatting. Providers must still avoid putting bytes, paths, write-only config, or deployment receipts in logs and errors.
Assignment to consuming operation
- bind assignment to consumer
- stage and activate generation
- match provider slot and descriptor digest
- resolve short-lived projection
- credential.runtime or credential.files
- consume in Mesh listener, task, or stream
- persist digest-only execution record
The strongly typed models, assignment validation, non-secret execution ledger,
provider dispatch, assignment-to-delivery orchestration, and same-process Mesh
sequencing are implemented. Credential-bearing Mesh mutations accept non-secret
assignment and slot selectors. The daemon resolves each active assignment,
validates its exact provider descriptor and consumer binding, and sends the
resulting runtime delivery before
mesh.listener.start, mesh.task, or
mesh.open_stream in the same process, so ephemeral in-memory material
is available to the consuming method. OpenMeshBridge uses the same
path before it opens the provider stream. The current external selector supports
direct runtime certificate DER, public-key SPKI, and private-key PKCS #8
projections. General non-Mesh consuming operations and externally initiated file
delivery remain extension points, not implemented behavior.
Stamping has the same honesty boundary: descriptor validation, immutable plans, provider execution contracts, result verification models, SQLite persistence, and list/inspect bookkeeping exist, but no production external control path currently initiates the complete plan → resolve → provider → ingest → terminal-transition workflow. See the wire reference and the Go, Python, and Rust credential-provider guide.
What callers can initiate today
| Surface | Can initiate now | Credential-delivery limit |
|---|---|---|
| CLI and one-shot command mode | Workspace PKI initialization; authority create/unlock/lock; certificate issue/renew/rotate/revoke; CRL publish; assignment bind/stage/activate/unbind; trust-set create/stage/activate. | No command initiates provider delivery, encoding, or stamping. |
| Daemon HTTP/JSON RPC | The PKI lifecycle above, authority rollover phases, bundle export, CRL reconciliation, and credential-bearing Mesh listener, survey-task, stream, and bridge mutations. | Mesh callers can select active assignments for direct runtime certificate and key projections. Execution-capable Mesh task kinds remain blocked until persisted throw orchestration is wired. Credential stamp and execution endpoints remain list/inspect only; no external method initiates provider encoding or stamping. |
| MCP | No PKI or credential-delivery tools are registered. | Neither initiation nor PKI list/inspect is exposed through MCP today. |
| Module stdio RPC | The daemon can call the five provider methods after selecting a module. | This is an internal plugin boundary, not an operator, web, or Elixir control API. |
Payload Provider
Payload query:
target: 10.0.0.42
kind: pe
platform: windows
os: windows
arch: x86
format: pe-exe
tags:
- pe
- windows
transport: tcp-bind
config:
lhost: 10.0.0.5
lport: "9101"
capabilities:
- session.shell
Payload metadata response:
id: squatter/windows/x86/windows-7/tcp-bind/pe-exe
name: Squatter TCP bind PE
version: 0.1.0
kind: pe
platform: windows
os: windows
arch: x86
minOS: Windows 7
testedOS:
- Windows 7
formats:
- pe-exe
- pe
tags:
- pe
- windows
capabilities:
- session.shell
transport:
kind: tcp-bind
encrypted: false
session:
kind: shell
acquisition: connect
requiresPreThrowListener: false
requiresPostThrowConnect: true
owner: provider
Payload generation response:
primary:
name: squatter.exe
role: primary
kind: pe
format: pe-exe
os: windows
arch: x86
tags:
- pe
- windows
encoding: base64
bytes: TVqQAAMAAAAEAAAA...
size: 123456
sha256: abc123
artifacts: []
Provider outputs are logically payload artifacts. An implementation may return inline base64 bytes or a provider handle that Hovel streams with read_payload_chunk.
Hovel performs minimal payload validation:
- Ensure bytes were returned.
- Record declared kind, size, and hash.
- Enforce configured size limits.
- Persist or hash-track the bytes for audit.
- Pass the bytes to the next module without parsing or proving the payload format.
For core payload-provider modules such as Squatter, Hovel materializes provider-returned bytes into controlled workspace artifact paths before exploit execution. Exploit modules receive typed payload metadata and local paths, not raw bytes. This keeps storage location, deduplication, hashing, audit metadata, and cleanup under Hovel's control while leaving payload internals opaque.
Payload generation is per target. A provider may return multiple artifacts for one target, but one artifact must be marked primary so consuming exploits can identify the deliverable path.
Stamped Payloads
Generated payload artifacts receive Hovel-issued stamp IDs. A stamp ties a provider response to the exact artifact set, target, transport, generation inputs, hashes, and later install records. Stamps make generated artifacts queryable without treating them as installed target state.
stampId: payload-stamp-uuid
provider: squatter
payloadId: squatter/windows/x86/windows-7/tcp-bind/pe-exe
targetId: t1
transport: tcp-bind
primaryArtifact: artifact-uuid
sha256: abc123
createdBy:
throwId: throw-uuid
runId: run-uuid
A stamped artifact becomes associated with installed inventory only after an install step or module execution returns an explicit installed-payload descriptor. Hovel must not infer installed payloads from log lines, artifact names, or generic events.
Installed Payload Inventory
Installed payload records are persisted by Hovel core in SQLite. Providers and installing modules may return explicit descriptors, but they never write directly to the workspace database. Hovel validates provider identity, payload identity, target provenance, transport, state, and reconnect schema before accepting the record.
The normalized core fields are intentionally small: internal id, operator handle, provider id, payload id/version, target address and optional target id, transport, endpoint summary, state, timestamps, stamp id, source operation, chain, throw, run, and artifact references. Provider-specific reconnect and cleanup material is stored as opaque JSON with a schema name and version owned by the provider.
State transitions are append-only audit events over a current-state row:
- created
- connected
- unreachable
- cleanup_attempted
- removed
- manually_marked_removed
The current state is one of installed, connected,
unreachable, or removed. Failed installs remain throw
records and evidence; they do not become installed payload records. Repeated
successful installs upsert only when the provider supplies an explicit
instance_key or stamp identity proving it is the same target-side
instance. Otherwise, Hovel creates a new record.
Cleanup is split into provider-executed removal and operator bookkeeping.
The Go provider RPC surface already includes cleanup_payload, but
the current command-mode payloads cleanup <payload> path is not wired
to provider cleanup yet and fails explicitly. Today, after manual or
out-of-band provider cleanup, payloads mark-removed <payload>
records bookkeeping when the operator knows the payload is gone or the target
was rebuilt.
PIC Payloads
PIC payloads are ordinary payload resources with specific metadata. Hovel should not implement PIC generation in core; external providers can generate or supply bytes.
kind: pic
arch: x86_64
abi: sysv
os: linux
entrypoint: offset:0
relocatable: true
requires:
- executable_memory
An external provider may be a local service, a Python package wrapper, a binary, or a pre-existing process. For example, pic can be wrapped as a provider for PIC payload bytes, but Hovel must treat it as a replaceable provider implementation rather than a core dependency. The only Hovel requirement is that the provider returns tagged payload bytes through the provider interface.
Listener Provider
Listener providers are part of the target model. The current source uses module-backed provider behavior for payloads, steps, installed payloads, and sessions; richer managed listener providers remain future work.
Listener providers expose managed listener capabilities.
Listener query:
name: sshplant-lp
protocol: tcp
bindHost: 127.0.0.1
bindPort: 0
sessionType: stdio
Listener response:
id: listener-uuid
serviceId: service:sshplant-lp
bindHost: 127.0.0.1
bindPort: 49152
protocol: tcp
status: listening
capabilities:
- session_broker
- artifact_capture
Provider Storage
Current provider data lives in SQLite and workspace-owned files. Workspace initialization creates workspace.json, workspace.db, and the artifacts/, logs/, modules/, throws/, and services/ directories. Provider-specific directories can be added when a provider needs file-shaped state.
.hovel/
workspace.json
workspace.db
artifacts/
<throw-id>/
<run-id>/
<artifact-name>
services/
logs/
throws/
workspace.db is the canonical durable store for operation session state, throw plans, confirmation records, throw records, installed payload records, installed payload state-change history, event indexes, and artifact metadata. Artifact bytes live in the workspace artifact store or in a configured workspace artifact path. Large artifacts must not be stored inline in SQLite.
Artifact
An artifact is any file-like result generated by a throw, module, service, or provider.
Examples:
- Logs.
- Transcripts.
- Captures.
- Payload builds.
- Raw responses.
- Generated commands.
- Plans.
- Listener captures.
- Session transcripts.
Artifact metadata:
id: artifact-uuid
throwId: throw-uuid
targetId: target-uuid
serviceId: service:sshplant-lp
name: ssh-transcript.txt
mediaType: text/plain
path: artifacts/throw-uuid/run-uuid/ssh-transcript.txt
sha256: abc123
createdBy: module:ssh-memory
Artifact bytes should be content-addressed or hash-tracked. Small inline artifacts returned by modules should be materialized into the artifact store and recorded with metadata. Large file artifacts should be saved directly into the workspace artifact store, or into a configured artifact path, with only metadata and hashes in SQLite.
Resolved payload bytes should be persisted as artifacts or otherwise hash-tracked before module execution when practical. This makes throws replay-inspectable without requiring Hovel to understand the payload internals.
Session
A session is an interactive or semi-interactive connection produced by a payload, listener, transport, or service.
id: session-uuid
runId: run-uuid
targetId: target-uuid
listenerId: listener-uuid
serviceId: service:sshplant-lp
type: stdio
state: open
openedAt: 2026-04-25T18:00:00Z
capabilities:
- read
- write
- close
Long-lived payloads should also have a durable installed payload record.
The persisted installed payload record answers "what is deployed on the target";
a SessionRef answers "what is connected right now." Reconnecting an
installed payload creates a normal Hovel session through the existing session
broker and links that session back to the installed payload id.
Session events:
session.created
session.input
session.output
session.closed
session.error
Structured Logging Rail
Hovel does not treat evidence as a first-class product vertical in the alpha. Other tools can handle assessment evidence, reporting, and proof curation. Hovel is responsible for being a red-team thrower with excellent structured observability: operators should be able to see what ran, what it did, what it produced, where it failed, and which artifacts or sessions were created.
Structured logging is a logging rail, not only a database table. Producers emit typed structured events. Handlers subscribe to those events and decide what to do with them. Persistence is one handler. CLI rendering is a handler. Future TUI rendering, NDJSON export, remote streaming, debug sinks, and test recorders are handlers.
Required components:
Eventis the durable envelope.EventBusfans events out to registered handlers.EventHandlerreceives events and may filter before acting.EventFiltersupports operation, chain, throw, run, module, target, level, type, and topic filters.SQLiteEventStorepersists event envelopes and small JSON fields.TerminalRendererrenders human-readable output from the same events.InMemoryEventRecordersupports tests.
Every event must include a human message, but terminal text is not the source of truth. Front ends render structured event records. Automation consumes structured event records. No adapter should parse terminal output.
Minimum event envelope:
id: event-uuid
schemaVersion: hovel.event/v1alpha1
timestamp: 2026-05-03T12:00:00Z
level: info
type: hovel.throw.started
message: Throw started
workspace: .hovel
operation: redteam-lab
chain: ssh-memory
throwId: throw-uuid
runId: run-uuid
moduleId: mock-exploit@v0.0.0-example
target: mock://router-01
topic: operation/redteam-lab/chain/ssh-memory/logs
fields:
stepId: step-1
elapsedSeconds: "000.06"
Envelope rules:
- Events are append-only. Corrections are new events.
- Event IDs are generated, not globally deduplicated.
- Event types are lowercase dot-separated segments using letters, digits, and underscores.
- Hovel reserves core categories such as
hovel.*andworkspace.*. - Modules may emit custom types under
module.*using the same lowercase segment rules. - The envelope is validated.
fieldsis arbitrary JSON. - Large payloads, transcripts, and binary data must be stored as artifacts. Events reference artifact IDs or paths and hashes.
- Module crashes, malformed frames, protocol timeouts, artifact registration failures, and guardrail failures must produce structured failure events when the host can observe them.
Core event taxonomy, including current emitted events and planned producer categories:
hovel.throw.planned
workspace.initialized
hovel.throw.confirmed
hovel.throw.started
hovel.throw.completed
hovel.throw.failed
hovel.run.started
hovel.run.completed
hovel.run.failed
hovel.module.log
hovel.module.stdout
hovel.module.stderr
hovel.artifact.recorded
hovel.provider.resolved
hovel.payload.installed
hovel.payload.connected
hovel.payload.unreachable
hovel.payload.cleanup_attempted
hovel.payload.removed
hovel.session.created
hovel.session.input
hovel.session.output
hovel.session.closed
hovel.service.started
hovel.service.healthy
hovel.service.log
hovel.service.failed
hovel.service.stopped
hovel.error.raised
The CLI, MCP adapter, future TUI, and future REST stream should all subscribe to the same logging rail. They may apply different filters and renderers, but they must observe the same underlying event contract.