Issue #821 Participant Workbench Preflight¶
This note sets the design guardrails for the in-appliance participant workbench. It is guidance, not an implementation plan. ADR-049 is the delivery-boundary decision; ADR-029, ADR-033, ADR-034, ADR-039 through ADR-042, and DSL-010 remain authoritative for secrets, evidence, TLS, browser/operator controls, Kali capture, and RAES participant runtime.
ADR-052 supersedes this note's credential lifecycle wording. Profile close always cleans up local possession; it claims upstream revocation only when the configured source provides and successfully executes that operation.
No new ADR is needed. The remaining ambiguity is profile isolation: a client that merely hides a tool, while its process can read another profile's config or credentials, does not satisfy this issue.
Architecture Decisions And Guardrails¶
- One desktop endpoint is a participant UX property, not authority placement. The browser or remote-desktop presentation sits in the participant zone; the supported coding agent, MCP client, and every APTL MCP server run in a management-zone runtime compartment inside the appliance. The participant endpoint has no Docker socket, management credential, model credential, blue credential, runstore mount, or direct SOC-management route.
- The participant listener is a separate route assembly and network listener, not the ADR-039 operator API with a participant role check. It may reuse BFF/session/Host/CSRF mechanics where their semantics fit, but never its bearer token, lifecycle, kill, config mutation, arbitrary terminal, raw evidence, or Docker-facing routes. The existing terminal endpoint is an operator surface, not a workbench shortcut.
- A profile is an enforced launch compartment, not a UI filter. The launcher
admits exactly one of the fixed
redorblueprofile identifiers, starts a fresh agent/MCP process set with only that profile's generated connection config, credentials, mounts, egress rules, and bookmarks, and stops the old set before switching. It must not mount both configs and toggle a client preference, nor rely on the model to obey a tool-selection instruction. - For the current operational scenario,
redexposesaptl-redonly; an optional reverse-engineering server is red-side only when the admitted scenario realizes that capability.blueexposes onlyaptl-indexer,aptl-wazuh,aptl-network,aptl-threatintel,aptl-casemgmt, andaptl-soar. Red never receives the credential aliases required by blue servers, blue never receives a Kali MCP connection, and neither profile receives Docker authority. The exacttools/listinventory, not just the server-name list, is the acceptance contract. - Treat the participant, its prompt content, and the coding agent as potentially compromised. A profile compartment must therefore be unable to discover or execute the other profile's built entrypoints, read its config or secret state, alter authoritative run/evidence state, or address an undeclared zone. Management placement alone is not sufficient when the agent has a shell or file-access tool.
- Purple work is a sequential, explicit profile transition. It records the selected profile and transition outcome against the current run identity, but does not create a second RAES scenario/episode/evidence schema or make a profile switch an alias for participant-episode reset, scenario reset, Compose teardown, or guest security reset.
- Session-local model authentication is guest-management-only mutable state. It is injected after boot through the ADR-049 bootstrap boundary, never placed in images, static assets, URLs, browser storage, profile JSON, MCP config, command lines, environment dumps, logs, captures, or support bundles. Normal profile close cleans up session state after ordered agent/MCP shutdown and telemetry/evidence flush; upstream revocation is source-specific under ADR-052. Guest replacement remains the security reset after compromise.
- Participant-visible bookmarks are non-secret presentation data for only
permitted appliance gateway routes. They must not contain host-loopback
URLs, Docker IPs, credentials, tokens, or SOC-management backdoors.
ENDPOINT_REGISTRYmay supply existing display metadata where appropriate, but its host-published URL projection is not the participant policy or a substitute for the default-deny flow matrix.
Cross-Cutting Incumbents To Reuse¶
| Concern | Canonical owner and required reuse |
|---|---|
| Appliance boundary and zones | ADR-049, especially APP-003 through APP-009, owns per-seat guest containment, default-deny flows, management placement, secrets, evidence, and replacement. The VM is not a RAES node provider or a reason to fork Compose/scenario contracts. |
| Participant/runtime semantics | AptlParticipantRuntime, published RAES participant contracts, RuntimeControlPlane, and DSL-010 own episodes, actions, receipts, status, snapshots, and behavior history. A profile is not another participant DTO or workflow state machine. |
| MCP build and server behavior | mcp/build-all-mcps.sh, .mcp.json.example, LabConfig, loadLabConfig(), createMCPServer(), JSON Schema tool definitions, handler assertions, HTTPClient, endpoint-origin checks, SSH lifecycle, TLS/CA support, and aptl-mcp-common remain the server contracts. A selected profile must use built artifacts and guest-internal generated configuration, not host-relative source execution. |
| Existing config limitation | aptl.core.lab._sync_mcp_config_keys() and .mcp.json are developer-local conveniences: they preserve arbitrary entries and inject all current blue keys into one client config. They are not reusable as the participant profile authority or secret store. loadLabConfig() validates basic shape, but its unresolved-env warning is not credential-completeness validation. |
| Secrets and generated state | ADR-028/029, load_dotenv(), placeholder detection, EnvVars, WebAuthSettings, redact() parity, curl_safe, atomic/no-symlink generated writes, ignored state, and restrictive modes own this boundary. Keep the ADR-049 appliance bootstrap parser narrow; do not enlarge AptlConfig or EnvVars into a workbench secret bag. |
| Browser/API controls | src/aptl/api/main.py, deps.py, session.py, middleware/bff.py, the Pydantic response models, and ADR-039/040 own the operator control plane. Reuse their proven Host/origin/session ideas only behind a separate participant gateway; never expose their operator routes. |
| Deployment and Docker authority | DeploymentBackend, DockerComposeBackend, typed operations, project scoping, bounded argv-list subprocesses, and ADR-013/023/037 own guest Docker. The workbench and profile MCP processes cannot receive a raw socket, Docker CLI authority, generic Docker RPC, or generic command broker. |
| Capture, run association, and evidence | ScenarioSession, trace-context.json, RunStorageBackend, LocalRunStore, evidence coordinator/content store, RangeSnapshot.to_dict(), ADR-033/041/042/044, MCP runs.ts, telemetry, and the capture registry own correlation and storage. Keep MCP-side PTY, Kali-side raw capture, OTel, and SOC evidence distinct. |
| Errors, logging, and readiness | LabResult, StartupOutcome, StartupDiagnostic, deployment errors, RAES Diagnostic/operation status, MCP SSHError and tool envelopes, get_logger(), and OTel telemetry remain the error/observability vocabulary. The workbench adds no appliance-wide exception hierarchy or readiness taxonomy. |
mcp/aptl-mcp-common/src/captures.ts is a deliberate incompatibility to
resolve at the existing management/evidence boundary: its current direct
docker cp harvest assumes Docker authority next to the MCP process. That
authority cannot be inherited by a participant-serving MCP runtime. Preserve
the existing capture layout and warning semantics through a fixed,
management-owned harvest/evidence operation; do not turn this into a generic
Docker or file-copy API.
Security And Validation Passage¶
- Payload and ingress: verify the ADR-049 signed appliance identity before accepting a seat. The host exposes only the participant endpoint and coarse health/recovery channel. The participant gateway validates its bounded request/profile shape, authenticates without reusing the operator bearer, applies Host/origin/CSRF/WebSocket protections, and returns narrow, redacted errors.
- Profile selection and generated configuration: accept only the fixed
profile vocabulary; resolve it through one versioned launcher mapping of
server identities, non-secret config references, credential aliases, and
bookmark references. Validate the rendered MCP/client JSON before launch,
reject missing, empty, malformed, or placeholder selected credentials, and
verify every referenced server artifact belongs to the released payload.
Do not duplicate
LabConfigor accept user-provided executable paths, endpoints, environment names, server names, or profile fragments. - MCP ingress and transport: all tool calls still pass each server's JSON
Schema, handler assertions (including canonical ID validation), endpoint
origin guard, HTTP timeout/error handling, TLS/CA policy, SSH host/session
lifecycle, common telemetry, and TypeScript redaction. Generated appliance
configs must use guest-internal, policy-reachable destinations. Current
localhostassumptions and currentverify_ssl: falseWazuh/indexer settings are not a participant-safe transport contract; no profile may add a new TLS-bypass escape hatch. - Secret and OS exposure: model and blue service secrets remain in management-only session state and pass only to the selected process through a non-argv secret mechanism. Neither profile nor its desktop gets Docker, host devices, shared PID namespace, writable host path, source checkout secret files, general appliance-management credential, or another profile's mount. Inspect process argv, environment/config readability, mounts, listeners, packages, and network reachability as separate proof points.
- Network and browser exposure: zone policy remains default-deny. Kali has no management, participant, blue-control, evidence, Docker, or general egress route. Browser bookmarks traverse only declared gateway flows; a bookmarked SOC UI is not permission to route Kali to the SOC management network or to expose those service ports at the appliance boundary.
- Persistence, telemetry, and error envelopes: profile launch, tool use,
PTY/capture, and teardown use the active run/trace identity from the
canonical session/runstore path.
_unboundcapture fallback, a new workbench-run directory, or a second profile-session ID cannot satisfy the association criterion. Redact structured records before persistence or telemetry; raw artifacts remain explicitly classified. Errors can expose a stable profile/server/validation label, never a token, auth header, provider response, command line, raw capture, internal topology, or another profile's existence. - Teardown and recovery: selected MCP servers receive their existing
graceful SIGTERM/OTel/SSH shutdown path before their compartment and session
secrets are removed. Failure is recorded as a narrow degraded/cleanup
outcome and blocks reuse of that session; it must not silently leave an old
blue-capable process alive. Outer guest replacement, not
aptl lab stop -vorAptlParticipantRuntime.reset(), restores trust after compromise.
Extensibility Seam¶
The one seam is a narrow, versioned profile-launch contract:
(profile_id, allowed_server_ids, generated_config_refs, credential_aliases,
bookmark_refs, run_id, policy_version).
It contains aliases and immutable references, never raw secret values,
arbitrary URLs, Docker targets, RAES topology, or copied MCP LabConfig
objects. The launcher resolves it against the signed payload, the admitted
scenario, existing server configuration, and appliance flow policy. The next
reasonable variation, an optional red capability or a read-only defensive
training profile, adds one allowlisted profile entry and its verification
fixture, not another agent implementation, credential schema, browser route
assembly, or copy of the MCP/server contracts.
Whole-Repository Surface¶
- ADR-049, ADR-029, ADR-033 through ADR-045, DSL-010, and the existing appliance/participant-runtime preflight records.
docker-compose.yml, network/port/mount/capability policy, container Dockerfiles,aptl.json,.env.example, generated.aptl/state, and appliance payload/launch assets.src/aptl/core/{config,env,credentials,lab,session,runstore,telemetry, endpoints,lifecycle_policy,kill}.py,core/deployment/,core/evidence/, andbackends/raes_participant_runtime.py.src/aptl/api/, the Svelte UI and route assemblies, API/web tests, and the participant-vs-operator gateway boundary..mcp.json.example,mcp/build-all-mcps.sh,mcp/aptl-mcp-common/, every dependent MCP package/config, and their build/test matrix.- Kali/capture, SOC, OTel, run/evidence, clean-teardown, deployment, workshop, recovery, security, and smoke/live validation documentation and tests.
Verification Guardrails¶
- Static payload/Compose/container checks prove no physical-host agent or MCP process, no participant/workload Docker socket, no forbidden mounts/devices, and no participant-reachable operator route or listener.
- Profile unit/integration checks launch each generated profile and assert its
exact MCP
tools/listinventory, absent server binaries/config visibility, credential-alias separation, failed-closed incomplete secret bootstrap, and no cross-profile process survives a switch. - Network checks prove both allowed profile flows and denied Kali-to-SOC, Kali-to-management, participant-to-operator, and workbench-to-Docker paths. They must validate live reachability as well as Compose/static declarations.
- End-to-end evidence checks prove agent/MCP/PTY/capture/OTel records share the correct existing run/trace identity across red, blue, and purple transitions without red-to-SIEM contamination. Teardown tests prove provider/session auth and profile state are absent afterward without exporting them.
- Changes under
mcp/aptl-mcp-commonrebuild and test every MCP; Compose, Dockerfile, or config changes require the repo's clean-lab validation in addition to focused pytest, vitest, web tests, andpre-commit run --all-files.
Gotchas And Anti-Patterns¶
- Do not reuse the current all-in-one
.mcp.jsonor_sync_mcp_config_keys()for a participant launch. It intentionally preserves arbitrary local client entries and combines blue credentials with red capability. - Do not call a red/blue selector secure when a coding agent can read the other config, inspect a shared environment, execute another built MCP entrypoint, talk directly to SOC APIs, or issue a generic shell/Docker request.
- Do not put model authentication in Kali, browser local storage, desktop
profile files, MCP JSON, Compose commands, URL/query data, process argv, or
a long-lived image/snapshot.
0600alone is not a serialization boundary. - Do not expose the existing operator API, web terminal,
killprocess scan, or host-facingENDPOINT_REGISTRYURLs through the participant UX. These are current developer/operator contracts with broader authority. - Do not use
docker cp/Docker CLI from profile MCPs, create a Docker socket proxy with arbitrary verbs, or make the capture/runstore volume writable by the agent as a workaround for evidence association. - Do not treat an MCP tool name list, DNS name, Docker network membership, loopback bind, CORS, TLS, or a VM as the enforcement boundary by itself.
- Do not create participant/profile copies of RAES DTOs, API types, endpoint catalogs, redactors, ID validators, run layouts, tool schemas, error classes, readiness states, or workflow engines.
Non-Goals And Boundaries¶
- This preflight does not implement the appliance, desktop/browser runtime, agent adapter, MCP launcher, profile renderer, gateway, browser bookmarks, secret bootstrap, capture mediation, or hosted fleet.
- It does not change the RAES scenario/participant schema, dynamic realization,
DeploymentBackend, existing MCP tool semantics, SOC product configuration, operator web control plane, or run/evidence schema. - It does not grant a participant generic terminal, appliance management, direct Docker, arbitrary egress, multi-seat sharing, RBAC/SSO, remote operator access, or raw evidence browsing.
- It does not make issue #821 depend on #557's broader participant-runtime delivery. It reuses #557's published participant contracts and leaves its episode semantics intact while providing the separate in-appliance launch and containment seam required for Arsenal delivery.