Install
Build the Edge Connector from source, configure a policy, and integrate it with any AI agent framework on Linux.
Choose a path
For an ordinary agent on macOS, Linux, or Windows, use the core install and quickstart. Fleet stays off, and the separate C daemon is not installed.
For a Linux device without a Fleet gateway, use the local-only smoke below. It builds the MINIMAL development profile, runs the C tests, and needs no broker, certificates, or Fleet API token. The policy compiler test needs Python 3 and PyYAML. Use the production build and deployment steps later on this page when connecting real devices to Fleet.
For an optional Fleet deployment, install the core gateway first, run defenseclaw setup edge-connector on the gateway host, then provision an authenticated TLS broker with per-device topic ACLs and registration-issued device keys before starting Linux C devices. The wizard saves the API token, signing keys, and selected registration endpoint before enabling Fleet. On Linux it also provisions the local C service's environment through sudo; on macOS and Windows it configures the gateway only. The C daemon itself runs on Linux. CLI health and policy commands use the configured local gateway; --endpoint is used by device installation and registration.
Local-only Linux smoke
Install Git, a C compiler, CMake 3.22+, Make, Python 3, and PyYAML before running the commands below.
git clone https://github.com/cisco-ai-defense/defenseclaw.git
cd defenseclaw
cmake -S edge-connector -B edge-connector/build-local \
-DDCLAW_PROFILE=MINIMAL -DDCLAW_DEV_MODE=ON
cmake --build edge-connector/build-local -j2
ctest --test-dir edge-connector/build-local --output-on-failure
./edge-connector/build-local/edge-connector --helpThis checks the local C engine and binary without enabling the gateway or installing a service. The MINIMAL profile has no MQTT; use STANDARD or EDGE with TLS and keys for Fleet devices.
Build a Linux Fleet device from source
Required: gcc, cmake (3.22+), make, and mbedTLS 3.x development libraries for production MQTT over TLS.
git clone https://github.com/cisco-ai-defense/defenseclaw.git
cd defenseclaw/edge-connector
mkdir build && cd build
cmake .. -DDCLAW_PROFILE=STANDARD -DDCLAW_DEV_MODE=OFF -DDCLAW_ALLOW_HMAC_OTA=ON
make -j$(nproc)Run tests
ctest --output-on-failureRun the unit and acceptance suites for your selected build profile. The exact test count may change as suites are added.
Install system-wide
sudo make installCMake installs the binary and support files. For a managed Linux service, also install the shipped systemd unit and provision its required environment file as shown in the repository installation guide.
Build profiles
cmake .. -DDCLAW_PROFILE=MINIMAL # no MQTT; constrained builds
cmake .. -DDCLAW_PROFILE=STANDARD # Raspberry Pi, Jetson, SBCs
cmake .. -DDCLAW_PROFILE=EDGE # edge gateways with bloom filterBinary size depends on the target toolchain, linked TLS libraries, and build flags. Measure the installed binary for your target.
Configure the policy
Step 1 — Copy and edit
# Return to the repository root (if you are still in edge-connector/build)
cd ../..
mkdir -p ~/.defenseclaw
cp edge-connector/policies/strict.yaml ~/.defenseclaw/policy.yamlEdit ~/.defenseclaw/policy.yaml:
name: my-device-policy
version: 1
skill_actions:
critical:
runtime: disable # BLOCK
high:
runtime: disable # BLOCK
medium:
runtime: warn # WARN (allow but log)
iot_extensions:
capability_sequences:
- sequence: [net_fetch, exec_shell]
action: block
- sequence: [net_fetch, actuate]
action: block
destination_allowlist:
- "1.1.1.1" # illustrative public IP; replace with one approved for your service
Step 2 — Check built-in protection
Content patterns, rate limits (60 tool calls, 30 network requests, 10 actuations per minute), and the SSRF guard are built into this C release. The policy compiler rejects unsupported content_inspection, ssrf_protection, custom regex, destination_denylist, rate_limits, and escalation_mode settings. Production uses synchronous Fleet escalation by default; OTA policy cannot change this mode. Network tools require a nonempty destination. Hostnames, private IPs, and numeric aliases are blocked across profiles; an approved destination must be a canonical public IPv4 or bracketed globally scoped IPv6 literal in destination_allowlist. The parser does not prove external routability. Replace the illustrative 1.1.1.1 address with the approved endpoint for your service.
Step 3 — Compile (optional)
Only needed when you change the policy. Pre-built binaries include the default policy.
The compiler needs Python 3 and PyYAML. CMake installs the script but does not install its Python dependency. A raw release tar needs provisioned Python 3 and PyYAML before Fleet policy push. Linux enterprise RPM/DEB leaves them optional while Fleet is off; provision trusted /usr/bin/python3 and distro PyYAML before enabling Fleet. Managed install/ensure checks the prerequisite before service changes. Per-user installers use their bundled Python environment. A managed macOS .pkg with Fleet enabled requires PyYAML 6.0.3 inside a trusted Python.org framework interpreter (3.10–3.14). For example, check /Library/Frameworks/Python.framework/Versions/3.13/bin/python3.13 -I -c 'import yaml; print(yaml.__version__)'. The interpreter, framework parents, and PyYAML package tree must be root-owned and not group/world-writable; managed install/ensure refuses Fleet-enabled config if none of the trusted candidates passes. Apple's /usr/bin/python3 and Homebrew PATH are not selected for this channel. Managed Windows standalone Setup carries the compiler in its signed Setup payload and activates it only for authenticated Fleet-enabled config, using the admitted Python/PyYAML scanner runtime. Fleet-off Setup/upgrade leaves no active compiler; disabling Fleet removes a previous active script. Managed Secure Client profiles on macOS and Windows reject Fleet-enabled configuration because they do not ship this compiler/runtime.
Fleet is an optional gateway subsystem. Managed Linux/macOS packages carry the compiler as inert source under <InstallRoot>/share/fleet/; validated Fleet-enabled install/ensure copies it into the protected <InstallRoot>/lib/defenseclaw/ path. Fleet-off install/upgrade does not activate the compiler or need Python/PyYAML. A Fleet-off raw payload install discards its optional compiler source; provide the staged release --payload to enable Fleet later. The local observability bundle includes an empty Fleet dashboard, but no Fleet scrape job runs by default.
Standard per-user install.sh/install.ps1 leave Fleet off and copy the matching compiler only when the existing config explicitly enables it. defenseclaw setup edge-connector installs the optional compiler and enables fleet.enabled only after credential setup succeeds. The core gateway installer does not install or start the separate C Edge Connector service.
cd edge-connector
python3 tools/policy_compiler.py \
--input ~/.defenseclaw/policy.yaml \
--profile standard --version 1 \
--output-header generated/policy_tables.h \
--output-binary dist/policy.binThe command's binary output has a development stub signature when no signing key is supplied; production devices will reject it as an OTA update. Use the Fleet policy-push API with matching DCLAW_OTA_HMAC_KEY secrets to compile and sign production OTA updates. The compiler's Ed25519 --signing-key output is not accepted by this C receiver.
Integrate with your AI agent
The Edge Connector is framework-agnostic. Choose the integration that matches your stack.
Generic Python adapter (recommended)
The simplest starting point for any Python agent. Three lines to add security enforcement:
When using the source checkout, run your agent with PYTHONPATH=/path/to/defenseclaw/edge-connector/tools; after sudo make install, use the installed adapter directory (normally /usr/local/lib/defenseclaw). CMake copies the Python files there but does not add that directory to Python's import path. Set DCLAW_LIB_PATH if your shared library is not at /usr/local/lib/libdclaw_core.so.
from generic_hook import EdgeConnector
ec = EdgeConnector() # auto-detects FFI or Unix socket
verdict = ec.evaluate(
tool_name="exec_shell",
arguments={"command": "rm -rf /"},
)
if verdict.blocked:
print(f"Blocked: {verdict.reason}")Uses the Unix socket first when the daemon is running, then FFI (ctypes) when no daemon is available. The adapter fails closed if the engine is unavailable. In production, DCLAW_FAIL_OPEN=1 also requires an authorized DCLAW_FAIL_OPEN_TOKEN tied to DCLAW_AUDIT_KEY; the flag alone does not enable fail-open.
MCP proxy (Model Context Protocol)
A transparent MCP proxy that sits between an LLM client (Claude, Codex, etc.) and a real MCP server. Intercepts every tools/call request and evaluates it through the C engine's local guards, verdict cache, and optional Fleet escalation. All other MCP messages pass through unchanged.
LLM Client (Claude, Codex)
| MCP (stdio / HTTP)
v
Edge Connector MCP Proxy
| evaluates via EdgeConnector.evaluate()
| MCP (stdio / HTTP)
v
Real MCP Server (ESP-Claw, HA-MCP, etc.)From the edge-connector/ source directory, quick start with a local stdio MCP server:
DCLAW_MCP_UPSTREAM='["python3", "-m", "esp_claw.mcp_server"]' \
python3 tools/mcp_proxy.pyWith a config file:
python3 tools/mcp_proxy.py --config tools/mcp_proxy_config.yamlUse stdio for a local MCP server: the proxy rejects private and loopback HTTP upstream addresses. For a remote upstream, configure a reviewed public HTTPS endpoint and verify its downstream network behavior.
Use with Claude Desktop or Claude Code by adding the proxy to your MCP client config:
{
"mcpServers": {
"esp-claw-secured": {
"command": "python3",
"args": ["/usr/local/lib/defenseclaw/mcp_proxy.py"],
"env": {
"DCLAW_MCP_UPSTREAM": "[\"python3\", \"-m\", \"esp_claw.mcp_server\"]",
"DCLAW_LIB_PATH": "/usr/local/lib/libdclaw_core.so"
}
}
}
}Replace /usr/local/lib if CMake installed into another library directory. The named upstream module, esp_claw.mcp_server, must be installed separately; substitute your actual stdio MCP server command.
| MCP Method | Action | Description |
|---|---|---|
tools/call | Evaluate | Local guards, verdict cache, and optional Fleet request. Blocked calls return an MCP error with the reason. |
tools/list, resources/*, prompts/*, initialize, etc. | Passthrough | Forwarded unchanged to the real server. |
Environment variables: DCLAW_MCP_UPSTREAM (target command or URL), DCLAW_MCP_TRANSPORT (stdio or http), DCLAW_MCP_PORT (default 8089). All standard Edge Connector env vars are passed through.
LangChain / LangGraph
Wrap LangChain tools so every invocation passes through the Edge Connector:
from langchain_hook import wrap_tools
tools = wrap_tools(my_tools)
agent = create_react_agent(llm, tools)Or use as a LangGraph node:
from langchain_hook import edge_connector_node
graph.add_node("security_gate", edge_connector_node)
graph.add_edge("agent", "security_gate")
graph.add_edge("security_gate", "tools")HTTP middleware (FastAPI / Flask)
For agents that expose tool execution via HTTP APIs:
# FastAPI
from http_middleware import EdgeConnectorMiddleware
app.add_middleware(EdgeConnectorMiddleware)
# Flask
from http_middleware import flask_edge_connector
flask_edge_connector(app)Intercepts POST requests to tool-execution endpoints and returns 403 for blocked calls.
PicoClaw
PicoClaw integrates via its process hook system:
mkdir -p ~/.picoclaw/hooks
cp /usr/local/lib/defenseclaw/picoclaw_hook.py ~/.picoclaw/hooks/defenseclaw_gate.py
picoclaw config editAdd to hooks.processes:
{
"defenseclaw_gate": {
"enabled": true,
"priority": 5,
"transport": "stdio",
"command": ["python3", "/home/USERNAME/.picoclaw/hooks/defenseclaw_gate.py"],
"env": {
"DCLAW_LIB_PATH": "/usr/local/lib/libdclaw_core.so",
"DCLAW_LOG_PATH": "/home/USERNAME/edge-connector.log"
},
"intercept": ["before_tool", "before_llm", "after_llm"]
}
}Replace USERNAME with the real home directory. Process launchers do not necessarily expand ~ in JSON command or environment values.
What gets intercepted
| Hook | What happens | Actions |
|---|---|---|
before_tool | Input validation, rate limit, content scan, destination and SSRF checks, sequence correlation, cache lookup, then optional Fleet request | deny_tool or continue |
before_llm | Prompt injection detection (20+ patterns) | abort_turn or continue |
after_llm | PII/credential leakage scanning | Detection + logging |
Verify
# Check that the hook protocol responds (this does not evaluate a tool)
echo '{"jsonrpc":"2.0","id":1,"method":"hook.hello","params":{}}' | \
DCLAW_LIB_PATH=/usr/local/lib/libdclaw_core.so \
python3 ~/.picoclaw/hooks/defenseclaw_gate.py
# Observe a sensor-read decision; production waits for a Fleet verdict
picoclaw agent -m "check battery status"
# Observe a shell decision; inspect the actual tool call and audit result
picoclaw agent -m "run whoami in the shell"
# Watch decisions
tail -f ~/edge-connector.logLow-level: shared library (FFI)
Load libdclaw_core.so from any language that supports C FFI:
import ctypes
lib = ctypes.CDLL("/usr/local/lib/libdclaw_core.so")
# Call dclaw_evaluate() — see defenseclaw.h for struct definitionsLow-level: Unix socket (JSON-RPC)
Query the daemon from any process:
tool_hash="$(printf exec_shell | sha256sum | cut -d' ' -f1)"
printf '{"jsonrpc":"2.0","id":1,"method":"evaluate","params":{'\
'"tool_name":"exec_shell","tool_hash":"%s","capabilities":4,'\
'"destination":"","session_id":1}}\n' "$tool_hash" | \
socat - UNIX-CONNECT:/run/defenseclaw/defenseclaw.sockThis optional raw IPC probe requires socat and a running daemon. The daemon derives trusted capability flags from the tool name; the caller-supplied capabilities value is not authority.
Tool-to-capability mapping
Each tool must map to a capability that determines how the Edge Connector handles it.
| Capability | Flag | Escalation | Behavior |
|---|---|---|---|
SENSOR_READ | 0x40 | Optional speculative mode | Content, rate, and sequence checks |
READ_FS | 0x01 | Optional speculative mode | Content, rate, and sequence checks |
NET_FETCH | 0x08 | Optional speculative mode | Destination allowlist and SSRF checks first |
SEND_MSG | 0x10 | Optional speculative mode | Destination allowlist and SSRF checks first |
ACTUATE | 0x20 | Always synchronous | Physical action; blocks without a Fleet verdict |
EXEC_SHELL | 0x04 | Always synchronous | Code execution; blocks without a Fleet verdict |
WRITE_FS | 0x02 | Always synchronous | File modification; blocks without a Fleet verdict |
Production strict mode waits for a Fleet verdict on an uncached undecided call and blocks when the gateway is unavailable, including for read-only tools. DCLAW_STRICT_MODE=0 explicitly permits speculative execution only for the first four capabilities when the build supports it. A destination allowlist entry does not itself approve a tool call.
MQTT broker setup
The Edge Connector uses MQTT 3.1.1 for verdict escalation (device to gateway) and signed policy/emergency distribution (gateway to device). Any standards-compliant broker works; Eclipse Mosquitto is recommended for simplicity.
Anonymous local test with Docker:
Mosquitto 2.x defaults to local-only mode (no remote connections). You must mount a config file that binds to 0.0.0.0:
# Create a minimal config that accepts remote connections
cat > mosquitto.conf <<'EOF'
listener 1883 0.0.0.0
allow_anonymous true
EOF
chmod 0644 mosquitto.conf
docker run -d --name mosquitto -p 127.0.0.1:1883:1883 \
-v "$PWD/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro" \
eclipse-mosquitto:2allow_anonymous true is for isolated local testing only. Bind the published Docker port to loopback (-p 127.0.0.1:1883:1883) and remove the container after testing. Production MQTT requires TLS with broker certificate verification and topic ACLs, including per-device publish permissions.
Authenticated local test setup:
This is an alternative to the preceding container. Stop and remove that disposable mosquitto container before running the authenticated example with the same name and port:
docker rm -f mosquitto 2>/dev/null || true# Mosquitto's container runs as UID/GID 1883. Make its bind mount readable.
sudo install -d -o 1883 -g 1883 -m 0700 mqtt
docker run --rm -it --user 1883:1883 \
-v "$PWD/mqtt:/mosquitto/config" --entrypoint mosquitto_passwd \
eclipse-mosquitto:2 -c /mosquitto/config/passwd dclaw
# Create a config that requires authentication
sudo tee mqtt/mosquitto.conf >/dev/null <<'EOF'
listener 1883 0.0.0.0
allow_anonymous false
password_file /mosquitto/config/passwd
EOF
sudo chown 1883:1883 mqtt/mosquitto.conf
sudo chmod 0600 mqtt/mosquitto.conf mqtt/passwd
docker run -d --name mosquitto -p 127.0.0.1:1883:1883 \
-v "$PWD/mqtt:/mosquitto/config:ro" \
eclipse-mosquitto:2For this local test, set credentials on both sides using environment variables (the Go and C MQTT clients do not parse userinfo from the broker URL):
# Generate one nonzero test HMAC key; copy it securely to the device when it runs elsewhere.
export DCLAW_DEVICE_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(32))')"
# Gateway (Go fleet server)
export DCLAW_DEV_MODE=ON # unmanaged local test only; production rejects zero-key mode
export DCLAW_MQTT_BROKER_URL=tcp://127.0.0.1:1883
export DCLAW_MQTT_USER=dclaw
export DCLAW_MQTT_PASS='<password entered above>'
# Edge devices (C edge connector)
export DCLAW_BROKER_URL=mqtt://127.0.0.1:1883
export DCLAW_MQTT_USER=dclaw
export DCLAW_MQTT_PASS='<password entered above>'The local example uses plaintext only on the same host and requires a separate C development build (-DDCLAW_DEV_MODE=ON); the production build at the start of this guide rejects plaintext MQTT. For production, configure a TLS listener and per-device topic ACLs on the broker. Set the gateway broker URL to ssl://broker-host:8883 (with fleet.mqtt_require_tls: true) and the C connector URL to mqtts://broker-host:8883. Provision DCLAW_CA_CERT_PATH on each device; also set DCLAW_DEVICE_CERT_PATH and DCLAW_DEVICE_KEY_PATH when the broker requires mutual TLS. Verify broker hostname and certificate chain before deployment.
Configure the gateway (Go fleet server):
export DCLAW_MQTT_BROKER_URL=tcp://127.0.0.1:1883Configure edge devices (C edge connector):
export DCLAW_BROKER_URL=mqtt://127.0.0.1:1883Both sides must point to the same broker. Enable the Fleet subsystem explicitly in ~/.defenseclaw/config.yaml:
fleet:
enabled: true
mqtt_broker_url: tcp://127.0.0.1:1883 # local test onlyThe gateway publishes policy updates; devices publish heartbeats and escalation requests. Use per-device broker topic ACLs, because the gateway subscriber cannot infer a verified publisher ClientID from an MQTT PUBLISH packet.
Upgrade together: Heartbeat and registration messages now carry a durable 4-byte boot counter and 4-byte last-applied emergency sequence. Keyed C devices send a 40-byte payload plus a 32-byte HMAC (72 bytes total); the gateway rejects older 64- and 68-byte signed frames. Upgrade the Edge Connector on each device with the Fleet gateway, and confirm heartbeats resume before relying on device status or policy distribution. The gateway replays missed emergency commands in signed predecessor order from durable history, up to 32 per signed registration or heartbeat; later heartbeats request the next page. A history gap or unavailable durable store fails closed. Broker publication alone does not prove device application.
Legacy flash partitions with unsigned policy payloads are preserved but refused at boot, including in a development build. Back up the device state and use a trusted re-sign/reprovisioning procedure before starting the new connector. An in-place metadata migration alone does not make the old policy executable; verify the signed flash policy and authenticated heartbeat after upgrade.
The gateway rejects missing or invalid device HMAC keys by default. Pre-register every production device through the authenticated Fleet API; MQTT auto-registration is for unmanaged local development only. An unmanaged development host can opt into the legacy zero-key behavior only with DCLAW_DEV_MODE=ON; managed deployments never permit that fallback. For multiple devices, use registration-issued per-device keys rather than the one shared test key above. The C device binds its durable emergency sequence to tenant/fleet identity; moving it requires trusted flash reset and reprovisioning, because a new identity with the prior emergency state refuses startup.
Environment variables
Server-side (Go fleet gateway)
| Variable | Required | Description |
|---|---|---|
DCLAW_FLEET_API_TOKEN | yes | Bearer token for authenticating Fleet API requests. If unset, all API requests are rejected. |
DCLAW_MQTT_BROKER_URL | if fleet.mqtt_broker_url is unset | MQTT broker URI. Use TLS and fleet.mqtt_require_tls: true in production. |
DCLAW_TLS_CA_CERT | for private broker CAs | PEM CA file used by the Go Fleet MQTT client; otherwise it uses the system trust store. |
DCLAW_TLS_CLIENT_CERT, DCLAW_TLS_CLIENT_KEY | for broker mTLS | Client PEM certificate and private key for the Go Fleet MQTT client. |
DCLAW_OTA_HMAC_KEY | for OTA push | Nonzero 32-byte hex HMAC secret for policy signing. Must match the device secret. |
DCLAW_EMERGENCY_KEY | for emergency commands | Separate nonzero 32-byte hex signing secret. Must match the device secret. |
DCLAW_DEVICE_KEY | if no per-device key store | Nonzero 32-byte hex fallback HMAC key for a one-device test; production fleets should provision per-device keys. An unset or invalid key fails closed by default. |
Device-side (C edge connector and Python hooks)
| Variable | Default | Description |
|---|---|---|
DCLAW_TENANT_ID | (none) | Tenant identifier for this device. |
DCLAW_FLEET_ID | (none) | Fleet identifier for this device. |
DCLAW_DEVICE_ID | (none) | Unique device identifier within the fleet. |
DCLAW_BROKER_URL | (none) | MQTT broker URI. Production builds require mqtts://broker-host:8883. |
DCLAW_OTA_HMAC_KEY | (none) | Nonzero 32-byte hex shared secret. Production HMAC OTA also requires -DDCLAW_ALLOW_HMAC_OTA=ON. |
DCLAW_EMERGENCY_KEY | (none) | Separate nonzero 32-byte hex secret for emergency commands. |
DCLAW_CA_CERT_PATH | (none) | Broker CA PEM file for TLS server verification. |
DCLAW_DEVICE_CERT_PATH, DCLAW_DEVICE_KEY_PATH | (none) | Client PEM certificate and private key when broker mTLS is enabled. |
DCLAW_FLASH_PATH | /var/lib/defenseclaw/flash.bin | Persistent flash backing file; production overrides must stay under /var/lib/defenseclaw/ or /etc/defenseclaw/. |
DCLAW_IPC_SOCKET_PATH | /run/defenseclaw/defenseclaw.sock | Local IPC socket path. |
DCLAW_LIB_PATH | /usr/local/lib/libdclaw_core.so | Path to the Edge Connector shared library. |
DCLAW_LOG_PATH | ~/edge-connector.log | Audit log file path. |
DCLAW_AUDIT_KEY | (none) | Nonzero 32-byte hex audit HMAC key. Production builds refuse to start without it. |
Compile-time constants
Set in CMakeLists.txt; changing these requires rebuilding the C library:
| Constant | Default | Description |
|---|---|---|
DCLAW_POLICY_PATH | (embedded in binary) | Policy tables compiled into binary via policy_tables.h |
DCLAW_DEV_MODE | OFF | Development-only build flag. Keep OFF in production. |
DCLAW_ESCALATION_PAYLOAD_MAX | 1024 | Max content snippet size for cloud escalation (set in config.h.in) |
Grafana dashboard
The Fleet dashboard is provisioned with the bundled local observability stack. Its source JSON is at edge-connector/dashboards/fleet-dashboard.json. It shows registered device status, heartbeats, block and allow verdicts, Fleet alerts, cache activity, and rollback counters. The dashboard is empty until Prometheus scrapes the authenticated Fleet metrics endpoint.
Fleet is disabled by default. Enable fleet.enabled: true on the gateway and set its admin DCLAW_FLEET_API_TOKEN in the environment. The bundled Prometheus intentionally has no Fleet scrape target until you opt in, because the gateway API normally listens only on 127.0.0.1:18970 and containers cannot reach that address through the Linux host gateway. Tenant-scoped tokens cannot scrape global Fleet metrics.
For a local Docker stack, enable the optional scrape after confirming host.docker.internal:18970 reaches your gateway from a container. Docker Desktop on macOS provides this host name. On Linux, set gateway.api_bind to the Docker bridge's host IP only if your firewall restricts access to the Docker network; this changes the bind for the entire gateway REST API. Managed enterprise installs require loopback and cannot use that bind change. For those hosts, run Prometheus on the host or provide a separately secured metrics-only proxy. Keep the gateway API bearer token secret and use TLS for any nonlocal access.
cd bundles/local_observability_stack
test -n "$DCLAW_FLEET_API_TOKEN" || { echo 'Fleet API token is empty' >&2; exit 1; }
install -d -m 0700 "$HOME/.defenseclaw/fleet-secrets"
printf '%s' "$DCLAW_FLEET_API_TOKEN" > "$HOME/.defenseclaw/fleet-secrets/metrics-token"
chgrp "$(id -g)" "$HOME/.defenseclaw/fleet-secrets/metrics-token"
chmod 0640 "$HOME/.defenseclaw/fleet-secrets/metrics-token"
cp prometheus/examples/fleet.yml prometheus/scrape.d/fleet.yml
chmod 0755 prometheus/scrape.d
chmod 0644 prometheus/scrape.d/fleet.yml # Prometheus runs as a non-root user
export FLEET_METRICS_TOKEN_FILE="$HOME/.defenseclaw/fleet-secrets/metrics-token"
export FLEET_METRICS_TOKEN_GID="$(id -g)"
docker compose -f docker-compose.yml -f docker-compose.fleet.yml up -dKeep the private fleet-secrets directory outside the bundle so wheel staging and bundle upgrades cannot copy the token. The token file needs group-read permission because the Prometheus container runs as a non-root user; the optional Compose file grants only this file's group to that container. Verify Status → Targets in Prometheus at http://127.0.0.1:9090/targets: defenseclaw-fleet must be UP. A 401 means the token is wrong; connection refused means the gateway bind/target is unreachable. Remove prometheus/scrape.d/fleet.yml and restart Compose to disable the scrape. When using another Prometheus instance, import the JSON and point its defenseclaw-prometheus data source at that instance. On an existing managed stack, the normal refresh preserves Grafana and Prometheus operator configuration; use --refresh-config only after backing up custom files, or copy the Fleet dashboard and scrape settings into the installed bundle by hand.
Benchmark
cd edge-connector/build
./tests/bench_latencyMeasure latency and throughput on your own target hardware and selected build profile.
Troubleshooting
| Problem | Solution |
|---|---|
libdclaw_core.so: cannot open shared object file | Run sudo ldconfig or set LD_LIBRARY_PATH |
| All tools blocked | Check the C capability table (src/decision/policy_table.c). Unknown tool names fail closed before cache or Fleet escalation. A custom name needs a reviewed C mapping; a Python adapter mapping alone cannot authorize it. |
| Shipped adapter network/message tool blocked | Bubbaloop http_fetch/model_download and ESP-Claw http_request require one unambiguous HTTP(S) public numeric url and a matching C allowlist entry. Home Assistant webhook/integration and broker/Zenoh tools remain blocked until their target schemas are reviewed. A downstream MCP server must also constrain redirects and actual egress. |
CLOUD_TIMEOUT on allowed tools | Change escalation from sync_block to speculative |
DEST_DENY on a public IP | Add that public IP to destination_allowlist |
CONTENT_BLOCK false positives | Review or disable the offending built-in category; custom exception patterns are not implemented |
SSRF_BLOCK on hostnames or private addresses | Use an approved public numeric IP. Hostname destinations are blocked until a controlled egress resolver/proxy is available; private destinations have no policy bypass |