enclout = enclave + outbound SSH to connect to end devices.
This repository contains M1 scaffolding for a secure connector system that combines:
- Channel-agnostic access requests through OpenClaw identity.
- Local on-device confirmation for each connection request.
- Strict fail-closed attestation verification in the local agent.
- Signed attestation bundle delivery from control plane.
services/control-plane: request orchestration API and signed attestation bundle endpoint.services/local-agent: Linux/macOS agent for prompt, verification, and managed SSH key installation.services/connector-tee: deterministic identity + attestation payload publisher.tests/integration: strict-flow fixture tests.docs/plans: validated design and implementation plan.docs/runbooks: operator flow and failure-code references.docs/runbooks/openclaw-skill-contract.mddefines the channel-agnostic agent intent contract.docs/runbooks/signing-key-rotation.mdcovers safekidrotation.
skills/enclout-openclaw-agent: reusable skill package for OpenClaw/Codex chat agents.
Run:
./scripts/verify-m1.shThe script requires both go and node toolchains.
Required:
CONNECTOR_ID
TEE mode (default):
DSTACK_SIMULATOR_ENDPOINT(optional; set for local simulator instead of unix socket)DSTACK_KEY_PATH(optional; defaultssh/connector/v1)DSTACK_KEY_SUBJECT(optional; defaulted25519)
Measurements:
MRTD,RTMR0,RTMR1,RTMR2,RTMR3EVENT_LOG(optional; default[])POLICY_VERSION(optional; defaultv1)
Legacy local fallback mode (for non-TEE development only):
TEE_SEED_B64TEE_QUOTE_HEX
Required:
API_AUTH_TOKEN
Signing key configuration (choose one):
- Single key mode:
SIGNING_KEY_B64- Optional
SIGNING_ACTIVE_KID(defaultv1)
- Rotation/keyset mode:
SIGNING_KEYS_JSON(JSON object ofkid -> seed_b64)SIGNING_ACTIVE_KID(must exist inSIGNING_KEYS_JSON)
Optional:
BIND_ADDR(default127.0.0.1:8080)STORE_PATH(default./var/control-plane-requests.json)CONNECTOR_BUNDLE_TEMPLATES_JSON
Required:
CONTROL_PLANE_URLDEVICE_IDLOCAL_USERNAMEDCAP_VERIFIER_URL
Control-plane signing trust bootstrap (optional):
- Keyset bootstrap:
CONTROL_PLANE_SIGNING_KEYS_JSON(JSON object ofkid -> public_key_b64)
- Single key bootstrap (backward compatible):
CONTROL_PLANE_SIGNING_PUBKEY_B64- Optional
CONTROL_PLANE_SIGNING_KID(defaultv1)
Runtime keyset refresh:
- Local agent fetches
GET /v1/signing-keysand caches trusted keys. SIGNING_KEYSET_CACHE_TTL(seconds or duration string, default5m)
Optional:
AGENT_TOKENDCAP_VERIFIER_TOKENDCAP_VERIFIER_TIMEOUT(seconds or duration string, default10s)MANAGED_KEYS_DIR(default/var/lib/connector-agent/keys)ALLOW_MRTD(comma-separated allowlist)ALLOW_RTMR3(comma-separated allowlist)
For URL-first connect bootstrap from any paired channel:
- Request connect through OpenClaw intent
request_connector_connect. - Share/click the returned
install_url. - Run installer with the one-time
install_token(direct command or landing page instruction).
macOS (launchd):
CONTROL_PLANE_URL=http://127.0.0.1:8080 \
DCAP_VERIFIER_URL=http://127.0.0.1:9000 \
go run ./services/local-agent/cmd/install -- \
-token "<install_token>" \
-agent-bin "/usr/local/bin/enclout-agent"Linux (systemd --user):
CONTROL_PLANE_URL=http://127.0.0.1:8080 \
DCAP_VERIFIER_URL=http://127.0.0.1:9000 \
go run ./services/local-agent/cmd/install -- \
-token "<install_token>" \
-agent-bin "/usr/local/bin/enclout-agent"- Installer redeems token, auto-registers
connector_id+device_id, configures OS service (launchdorsystemd --user), starts the agent, and posts install result (installedorfailed) back to control plane.
You do not need to pre-bake the enclout skill into your OpenClaw image.
Recommended path is dynamic install when needed.
Recommended (Agentskills-compatible, explicit skill selector):
npx skills add HashWarlock/enclout --skill enclout-openclaw-agent -a openclaw -g -yMost reliable fallback (direct GitHub skill URL):
npx skills add https://github.com/HashWarlock/enclout/tree/main/skills/enclout-openclaw-agent -a openclaw -g -yDo not use this older syntax (can fail to resolve skill correctly):
HashWarlock/enclout@enclout-openclaw-agent
Use this as the first operator message in the paired chat channel:
Install skill from https://github.com/HashWarlock/enclout/tree/main/skills/enclout-openclaw-agent for the openclaw agent.
Then confirm enclout-openclaw-agent is loaded.
After that, for connect requests, run URL-first flow:
request_connector_connect -> send install_url -> approve_install_session -> poll install session -> request_connector_access.
Never ask end users for API_AUTH_TOKEN, CONTROL_PLANE_URL, openclaw_user_id, source_channel, connector_id (before install), or device_id (before install).
Derive control-plane base URL from the active OpenClaw gateway/session origin.
Use gateway session auth by default; if explicit bearer is required, generate a short-lived bearer via deterministic key generator capability.
If gateway URL/auth context cannot be resolved, return exactly:
"Operator action needed: OpenClaw gateway auth context unavailable (cannot resolve gateway URL or generate session bearer). End user does not need to provide tokens or URLs."
User experience note:
- The skill should not ask for
connector_id/device_idup front. - OpenClaw identity/channel context must be runtime-resolved.
- Control-plane URL/auth must be derived dynamically from gateway/session context.
- If gateway URL/auth context is missing, return operator error; do not ask the end user for tokens or URLs.
When using enclout-openclaw-agent, the chat agent must follow this behavior:
- Never ask the end user for runtime/operator values:
API_AUTH_TOKEN,CONTROL_PLANE_URL,openclaw_user_id,source_channel,connector_id(before install), ordevice_id(before install). - Start with URL-first flow:
request_connector_connect->install_url-> approval -> polling. - Derive control-plane URL/auth from active gateway/session context; do not require static env values in chat flow.
- If gateway URL/auth context is unavailable, return an operator-facing error instead of a user prompt.
Expected operator-facing error style:
Operator action needed: OpenClaw gateway auth context unavailable (cannot resolve gateway URL or generate session bearer). End user does not need to provide tokens or URLs.
Local fallback (when running from checked-out repo):
./scripts/install-enclout-skill.sh "$HOME/.agents/skills"Use this flow when your OpenClaw agent runs in a Docker Compose service.
- Update the repo on the CVM host:
cd ~/enclout
git checkout main
git pull origin main- Install skill dynamically in the running agent container:
docker compose exec openclaw-agent \
sh -lc 'npx skills add https://github.com/HashWarlock/enclout/tree/main/skills/enclout-openclaw-agent -a openclaw -g -y'- Optional but recommended: persist installed skills with a volume mount:
services:
openclaw-agent:
volumes:
- /path/on/host/agent-skills:/home/app/.agents/skills- Restart/recreate the agent service:
docker compose up -d --force-recreate openclaw-agent- Ask the agent to confirm skill load:
List installed skills and confirm enclout-openclaw-agent is available.
- Run a chat smoke test from any paired channel:
Connect me to this device.
If the agent still asks the user for env vars or IDs, send this correction prompt once:
Stop requesting runtime/operator values from end users.
Use enclout-openclaw-agent URL-first flow only.
Derive gateway URL/auth context dynamically; do not require static token/URL env vars.
If gateway URL/auth context is missing, return operator-action error only.
Expected flow:
- Agent calls
request_connector_connect. - Agent sends clickable
install_url. - Agent calls
approve_install_session. - You run the returned
enclout installcommand on the target device. - Agent polls
GET /v1/install-sessions/{id}until terminal state. - Agent reads
connector_id+device_idfrom install session and proceeds withrequest_connector_access.