Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/decisions/0001-openclaw-jws-response-verification-deferred.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 0001 — JWS Response Signature Verification deferred to @moltrust/openclaw-plugin v2.1

**Datum:** 2026-05-28
**Status:** Accepted

## Context

Der §12-Review für `@moltrust/openclaw-plugin@2.0.0-alpha.0` (Run 2026-05-28 16:36 UTC, Output `~/moltstack/reviews/20260528_163640_openclaw-plugin-v2.0.0-alpha.0_review.md`) hat als Blocker #3 (Konsens von Gemini 3.1 Pro Preview + Perplexity Sonar Pro) markiert:

> Keine JWS-Signatur-Verifikation der API-Antworten → MITM-anfällig.

Die MolTrust-API liefert seit CAEP Profile v1 (LIVE 2026-05-09, `kid: moltrust-registry-2026-v1`) Ed25519-signierte Trust-Scores. Der OpenClaw-Plugin-Client (`moltrust-openclaw-v2/src/client.ts`) ruft die Endpoints heute ohne lokale Signatur-Verifikation auf — er vertraut HTTPS + JSON-Parsing.

Risiko bei MITM-Szenarien (Corporate-Proxy mit gefälschten CA-Roots, Routing-Manipulation, kompromittierte Edge-Node): gefälschte ALLOW/DENY-Entscheidungen würden vom Plugin unbeanstandet ausgeführt.

## Decision

Die JWS-Verifikation wird **explizit deferred** auf `@moltrust/openclaw-plugin v2.1`, nicht in v2.0.0-alpha.1 implementiert.

Begründung:

- v2.0.0-alpha.x ist Public-Preview, kein Production-Release. Trust-Threshold ist `0` per Default (opt-in via `minTrustScore`).
- Saubere JWS-Verifikation braucht: (a) öffentlicher Key-Bootstrap-Mechanismus (JWKS-Fetch, Trust-on-First-Use, Pinning?), (b) Rotations-Policy (Plugin muss Key-Rotation der MolTrust-Registry handhaben), (c) Failure-Mode-Spec (Signatur-Mismatch vs Key-not-found vs Clock-Skew). Diese drei Punkte sind eine eigene Design-Spec, kein 1-Sprint-Fix.
- Der gefährlichste Fail-Open-Pfad (Plugin lässt Agents trotz API-Ausfall durch) wird **in v2.0.0-alpha.1** durch Blocker-Fix #1 (`failOpen: false` Default, opt-in) bereits geschlossen — damit ist der primäre MITM-Attack-Surface „API not reachable, fall through" mitigiert. Verbleibende MITM-Surface: aktive Man-in-the-Middle-Manipulation einer eigentlich erreichbaren API-Antwort.

## Consequences

**Positiv:**

- v2.0.0-alpha.1 kann zeitnah re-reviewed und publik gemacht werden (Preview-Status, kein Production-Trust-Gating-Anspruch).
- v2.1-Spec bekommt einen eigenen Sprint mit Design-Review für JWS-Bootstrap, Key-Rotation und Failure-Modes.

**Negativ:**

- Bis v2.1 publiziert ist, müssen Operators in MITM-fähigen Netzwerken (z.B. Corporate-Proxy mit Custom-CA) das Plugin als „ungeeignet für Production-Trust-Gating in solchen Umgebungen" einstufen.
- README muss diesen Trade-off explizit kommunizieren (Section „Security Posture & Roadmap").

**Pflicht-Begleitmaßnahmen für v2.0.0-alpha.1:**

- README-Note: „Response signature verification planned for v2.1 — see ADR 0001"
- ADR-Link aus README erreichbar (Discovery)
- v2.1-Spec als Item in `docs/BACKLOG.md` festhalten (separat von diesem PR)

## Alternatives considered

1. **JWS-Verify in v2.0.0-alpha.1 ad-hoc implementieren** — verworfen: ohne Bootstrap-/Rotations-Spec wird der Plugin selbst zur Angriffsfläche (z.B. Plugin akzeptiert jeden Key beim First-Use ohne Pinning, oder bricht stumm bei Routine-Key-Rotation der Registry).
2. **Public Release blocken bis v2.1 fertig** — verworfen: v2.0.0-alpha.x ist Preview. Realistische Anwender setzen es jetzt zum Testen ein, nicht für Production-Trust-Gating. Veröffentlichung mit klarer Roadmap-Note ist ehrlicher als Stillstand.
3. **Plugin als private (nicht-npm) Package halten bis v2.1** — verworfen: widerspricht dem v2-Ziel (öffentliche OpenClaw-Integration), und die Preview-Phase ist gerade dafür gedacht, dass Early-Adopters Feedback geben.
12 changes: 12 additions & 0 deletions moltrust-openclaw-v2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules/
dist/
coverage/
.DS_Store
*.log
npm-debug.log*
.env
.env.*
!.env.example
.vscode/
.idea/
*.tgz
15 changes: 15 additions & 0 deletions moltrust-openclaw-v2/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
src/
tests/
node_modules/
coverage/
.github/
.vscode/
.idea/
tsconfig.json
vitest.config.ts
.gitignore
.npmignore
.editorconfig
*.log
*.tgz
.DS_Store
105 changes: 93 additions & 12 deletions moltrust-openclaw-v2/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @moltrust/openclaw v2
# @moltrust/openclaw-plugin v2

> W3C DID trust verification + lifecycle gating for [OpenClaw](https://openclaw.ai)

Expand All @@ -7,10 +7,14 @@ v2 adds the four lifecycle hooks the OpenClaw core already exposes (per
`before_tool_call`, `inbound_claim`, `gateway_start` — on top of the v1
agent tools / slash commands / gateway RPC / CLI surface.

> **Preview release.** v2.0.0-alpha.x is a public preview, not a Production
> Trust-Gating release. See *Security Posture & Roadmap* below for the
> v2.1 hardening list.

## Install

```bash
openclaw plugins install @moltrust/openclaw
openclaw plugins install @moltrust/openclaw-plugin
```

Restart your gateway.
Expand Down Expand Up @@ -47,7 +51,9 @@ New tool: `moltrust_endorse` — issue a SkillEndorsementCredential (W3C VC,
"gateAllTools": false,
"installAllowlist": [],
"installBlocklist": [],
"cacheTtlMs": 300000
"cacheTtlMs": 10000,
"failOpen": false,
"registerMoltrustTools": true
}
}
}
Expand All @@ -61,8 +67,8 @@ Get an API key at [api.moltrust.ch/auth/signup](https://api.moltrust.ch/auth/sig

```
src/
├── openclaw-types.ts vendored OpenClaw plugin SDK types (subset)
├── client.ts MolTrustClient + LRU cache (5 min TTL)
├── openclaw-types.ts vendored OpenClaw plugin SDK types (subset, range 0.9.x–1.0.x)
├── client.ts MolTrustClient + LRU cache (10 s TTL default)
├── utils.ts extractDids / isLikelyDid
├── hooks/
│ ├── before-install.ts makeBeforeInstallHandler({cfg, logger})
Expand All @@ -79,17 +85,92 @@ unit-testable without an OpenClaw host.

```bash
npm install
npm test # ≥15 vitest tests across hooks + client
npm test # vitest hooks + client (>= 27 tests)
npm run build # produces dist/*.js + *.d.ts
```

## Fail-open on lookup errors
## Security Posture & Roadmap

### Fail-closed by default (v2.0.0-alpha.1)

When a MolTrust API lookup fails (network, rate-limit, 5xx), `before_tool_call`
and `inbound_claim` **block the call/inbound** with a clear `blockReason`
mentioning `failOpen=false`. This is the default — safe for Production
Trust-Gating.

Opt-in fail-open is available via `failOpen: true` for fleets where
availability matters more than trust-gating (e.g. internal dev environments,
non-financial tools). Set it explicitly and monitor the warn-log.

### Response signature verification — planned for v2.1

This release does **not** verify the Ed25519 JWS signatures that
`api.moltrust.ch` returns on trust-score and verify responses (kid
`moltrust-registry-2026-v1`). It trusts HTTPS + JSON parsing.

In MITM-capable environments (Corporate-Proxy with custom CA, routing
manipulation, compromised edge node) an attacker could forge ALLOW/DENY
decisions. The fail-closed default mitigates the most common attack path
("API unreachable, fall through"), but does not stop active in-line
manipulation.

JWS verification is on the v2.1 roadmap as a dedicated design sprint
(JWKS bootstrap, key rotation, failure-mode spec). See [ADR
0001](../docs/decisions/0001-openclaw-jws-response-verification-deferred.md)
in the parent MolTrust API repo for the full reasoning.

### Cache TTL

Default `cacheTtlMs: 10000` (10 seconds). Tunes revocation latency vs.
API-call volume. Lower it to 0 to disable caching entirely; raise it only
if your `minTrustScore` threshold is well above the worst-case score of any
agent you'd permit (i.e. cache cannot mask a decision flip).

### OpenClaw version range

The plugin vendors a subset of OpenClaw's plugin SDK types
(`src/openclaw-types.ts`) pinned to the upstream signature baseline at
commit `45146913007d` (tested range: 0.9.x – 1.0.x). On host versions
outside this range the hook contracts may diverge silently. Bump-and-test
when upstream cuts a breaking minor.

## Privacy & Data Handling

This plugin sends agent DIDs and (optionally) wallet addresses to
`api.moltrust.ch` for trust-score lookups. Specifically:

- **`before_tool_call`** sends your `agentDid` plus any DIDs found in the
tool call's `params` (via `did:*` regex on string values).
- **`inbound_claim`** sends the sender DID extracted from
`event.metadata.did` or `event.senderId`.
- **`gateway_start`** sends `agentDid` only if `verifyOnStart: true`.
- The `moltrust_verify` / `moltrust_trust_score` / `moltrust_endorse` tools
send whichever DID/address the calling agent passes as the argument.

**Endpoint:** `https://api.moltrust.ch` (configurable via `apiUrl` —
self-hosting documented separately).

**Retention:** the MolTrust service stores trust-score lookups per the
operator's privacy policy at [moltrust.ch/privacy](https://moltrust.ch/privacy)
(MolTrust as data processor; you remain controller for your fleet's DIDs).

**Disabling automatic outbound calls:** set `minTrustScore: 0` and
`verifyOnStart: false`. The lifecycle hooks then make no outbound calls.
However, the `moltrust_verify` / `moltrust_trust_score` / `moltrust_endorse`
agent tools remain **registered with the agent runtime** — an LLM
hallucination or unintended chain-of-thought could still trigger them.

**True air-gap mode:** additionally set `registerMoltrustTools: false`.
The three `moltrust_*` agent tools are then **not exposed to the agent
runtime at all** — the LLM cannot invoke them. Slash commands
(`/trust`, `/trustscore`) and the gateway RPC methods remain available
for explicit operator/user invocations.

`before_tool_call` and `inbound_claim` log a warning and **do not block** when
a MolTrust API lookup fails (network down, rate limit, etc.). This is a
deliberate design choice: a transient trust-API outage shouldn't take an
agent fleet offline. Operators should monitor the warn-log for sustained
failures.
This is a trust-verification plugin — *intentional* use requires sending
DIDs to MolTrust. There is no way to gate agents on remote trust scores
without that round-trip. If you need air-gapped trust gating, set
`minTrustScore: 0` + `verifyOnStart: false` + `registerMoltrustTools: false`
and rely only on the (manual) slash commands for ad-hoc lookups.

## License

Expand Down
18 changes: 15 additions & 3 deletions moltrust-openclaw-v2/openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,19 @@
},
"cacheTtlMs": {
"type": "integer",
"default": 300000,
"default": 10000,
"minimum": 0,
"description": "TTL for client-side cache of verify/score lookups (ms). 0 disables cache."
"description": "TTL for client-side cache of verify/score lookups (ms). 0 disables cache. Default 10s — Zero-Trust appropriate revocation latency."
},
"failOpen": {
"type": "boolean",
"default": false,
"description": "When a MolTrust API lookup fails (network, rate limit, 5xx), what to do? false (default) = block the call/message (fail-closed, safe). true = warn-log and pass through (fail-open, opt-in for low-criticality fleets where availability beats trust-gating). See ADR 0001."
},
"registerMoltrustTools": {
"type": "boolean",
"default": true,
"description": "Register the moltrust_verify / moltrust_trust_score / moltrust_endorse agent tools? Default true. Set false for true air-gapping: tools won't be exposed to the agent runtime, so LLM hallucinations cannot trigger outbound DID lookups. Slash commands and lifecycle hooks are unaffected."
}
}
},
Expand All @@ -70,6 +80,8 @@
"minTrustScore": { "label": "Minimum Trust Score (0=off)", "placeholder": "50" },
"agentDid": { "label": "Your Agent DID", "placeholder": "did:moltrust:..." },
"verifyOnStart": { "label": "Self-verify on gateway start" },
"gateAllTools": { "label": "Gate ALL tools (not just sensitive prefixes)" }
"gateAllTools": { "label": "Gate ALL tools (not just sensitive prefixes)" },
"failOpen": { "label": "Fail-open on API lookup error (opt-in — fail-closed is safer)" },
"registerMoltrustTools": { "label": "Register moltrust_* agent tools (set false for air-gap)" }
}
}
11 changes: 7 additions & 4 deletions moltrust-openclaw-v2/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 18 additions & 6 deletions moltrust-openclaw-v2/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@moltrust/openclaw",
"version": "2.0.0-alpha.0",
"description": "MolTrust v2 trust-provider plugin for OpenClaw — lifecycle hooks for install / tool-call / inbound / gateway-start gating, plus W3C DID identity verification, trust scoring, sybil detection.",
"name": "@moltrust/openclaw-plugin",
"version": "2.0.0-alpha.2",
"description": "MolTrust v2 trust-provider plugin for OpenClaw — lifecycle hooks for install / tool-call / inbound / gateway-start gating, plus W3C DID identity verification, trust scoring, sybil detection. Fail-closed by default.",
"keywords": [
"openclaw",
"moltrust",
Expand All @@ -15,17 +15,28 @@
"homepage": "https://moltrust.ch",
"repository": {
"type": "git",
"url": "https://github.com/MoltyCel/moltrust-openclaw"
"url": "git+https://github.com/MoltyCel/moltrust-openclaw-plugin.git"
},
"bugs": {
"url": "https://github.com/MoltyCel/moltrust-openclaw-plugin/issues"
},
"author": "Lars Kroehl <kersten.kroehl@cryptokri.ch> (https://moltrust.ch)",
"license": "MIT",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"openclaw.plugin.json",
"LICENSE"
"LICENSE",
"README.md"
],
"engines": {
"node": ">=20"
},
"publishConfig": {
"access": "public"
},
"openclaw": {
"extensions": [
"./dist/index.js"
Expand All @@ -35,7 +46,8 @@
"build": "tsc",
"dev": "tsc --watch",
"test": "vitest run",
"test:watch": "vitest"
"test:watch": "vitest",
"prepublishOnly": "npm run build && npm test"
},
"devDependencies": {
"@types/node": "^20.0.0",
Expand Down
Loading
Loading