Skip to content

Commit ec737ee

Browse files
authored
fix: rebase clawhub install trust (#81364)
1 parent 9a735be commit ec737ee

74 files changed

Lines changed: 7724 additions & 340 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/clawhub/cli.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,31 @@ OpenClaw agent or Gateway.
2424
```bash
2525
openclaw skills search "calendar"
2626
openclaw skills install @owner/<slug>
27+
openclaw skills install @owner/<slug> --acknowledge-clawhub-risk
2728
openclaw skills update @owner/<slug>
29+
openclaw skills update @owner/<slug> --acknowledge-clawhub-risk
2830
openclaw skills verify @owner/<slug>
2931

3032
openclaw plugins search "calendar"
3133
openclaw plugins install clawhub:<package>
34+
openclaw plugins install clawhub:<package> --acknowledge-clawhub-risk
3235
openclaw plugins update <id-or-npm-spec>
3336
```
3437

3538
Skill installs target the active workspace `skills/` directory by default. Add
3639
`--global` to install into the shared managed skills directory.
3740

41+
OpenClaw checks the selected community ClawHub skill or plugin trust state
42+
before downloading it. Versioned community skill and plugin releases use
43+
exact-release trust metadata; resolver-backed GitHub skills rely on ClawHub's
44+
install resolver to enforce scan and force-install policy before it returns a
45+
pinned commit. Malicious or blocked community releases are refused. Risky
46+
community releases require review and `--acknowledge-clawhub-risk` when a
47+
non-interactive command should continue after that review.
48+
49+
Official ClawHub publishers/packages and bundled OpenClaw sources bypass this
50+
release-trust prompt and security-verdict fetch during install and update.
51+
3852
Plugin installs use the `clawhub:` prefix when you want ClawHub resolution
3953
instead of npm or another install source.
4054

docs/cli/plugins.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ openclaw plugins install git:github.com/<owner>/<repo> # git repo
111111
openclaw plugins install git:github.com/<owner>/<repo>@<ref>
112112
openclaw plugins install <package> --force # overwrite existing install
113113
openclaw plugins install <package> --pin # pin version
114+
openclaw plugins install clawhub:<package> --acknowledge-clawhub-risk
114115
openclaw plugins install <package> --dangerously-force-unsafe-install
115116
openclaw plugins install <path> # local path
116117
openclaw plugins install <plugin>@<marketplace> # marketplace
@@ -163,6 +164,12 @@ is available, then fall back to `latest`.
163164

164165
If a plugin you published on ClawHub is hidden or blocked by a registry scan, use the publisher steps in [ClawHub publishing](/clawhub/publishing). `--dangerously-force-unsafe-install` does not ask ClawHub to rescan the plugin or make a blocked release public.
165166

167+
</Accordion>
168+
<Accordion title="--acknowledge-clawhub-risk">
169+
Community ClawHub installs check the selected release trust record before downloading the package. If ClawHub disables download for the release, reports malicious scan findings, or puts the release in a blocking moderation state such as quarantine, OpenClaw refuses the release. For non-blocking risky scan statuses, risky moderation states, or registry reasons, OpenClaw shows the trust details and asks for confirmation before continuing.
170+
171+
Use `--acknowledge-clawhub-risk` only after reviewing the ClawHub warning and deciding to continue without an interactive prompt. Pending or stale clean trust records warn but do not require acknowledgement. Official ClawHub packages and bundled OpenClaw plugin sources bypass this release-trust prompt.
172+
166173
</Accordion>
167174
<Accordion title="Hook packs and npm specs">
168175
`plugins install` is also the install surface for hook packs that expose `openclaw.hooks` in `package.json`. Use `openclaw hooks` for filtered hook visibility and per-hook enablement, not package installation.
@@ -390,6 +397,7 @@ openclaw plugins update <id-or-npm-spec>
390397
openclaw plugins update --all
391398
openclaw plugins update <id-or-npm-spec> --dry-run
392399
openclaw plugins update @openclaw/voice-call
400+
openclaw plugins update openclaw-codex-app-server --acknowledge-clawhub-risk
393401
openclaw plugins update openclaw-codex-app-server --dangerously-force-unsafe-install
394402
```
395403

@@ -421,6 +429,9 @@ Updates apply to tracked plugin installs in the managed plugin index and tracked
421429
<Accordion title="--dangerously-force-unsafe-install on update">
422430
`--dangerously-force-unsafe-install` is also accepted on `plugins update` for compatibility, but it is deprecated and no longer changes plugin update behavior. Operator `security.installPolicy` can still block updates; plugin `before_install` hooks only apply in processes where plugin hooks are loaded.
423431
</Accordion>
432+
<Accordion title="--acknowledge-clawhub-risk on update">
433+
Community ClawHub-backed plugin updates run the same exact-release trust check as installs before downloading the replacement package. Use `--acknowledge-clawhub-risk` for reviewed automation that should continue when the selected ClawHub release has a risky trust warning. Official ClawHub packages and bundled OpenClaw plugin sources bypass this release-trust prompt.
434+
</Accordion>
424435
</AccordionGroup>
425436

426437
### Inspect

docs/cli/skills.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ openclaw skills install git:owner/repo
3131
openclaw skills install git:owner/repo@main
3232
openclaw skills install ./path/to/skill --as custom-name
3333
openclaw skills install @owner/<slug> --force
34+
openclaw skills install @owner/<slug> --acknowledge-clawhub-risk
3435
openclaw skills install @owner/<slug> --agent <id>
3536
openclaw skills install @owner/<slug> --global
3637
openclaw skills update @owner/<slug>
38+
openclaw skills update @owner/<slug> --acknowledge-clawhub-risk
3739
openclaw skills update @owner/<slug> --global
3840
openclaw skills update --all
3941
openclaw skills update --all --agent <id>
@@ -97,6 +99,14 @@ Notes:
9799
- `install --version <version>` applies only to ClawHub skill refs.
98100
- `install --force` overwrites an existing workspace skill folder for the same
99101
slug.
102+
- Community ClawHub skill installs and updates check trust before downloading.
103+
Versioned community archive releases use exact-release trust metadata.
104+
Resolver-backed GitHub skills rely on ClawHub's install resolver to enforce
105+
scan and force-install policy before it returns a pinned commit. Malicious or
106+
blocked community releases are refused. Risky community releases require
107+
review and `--acknowledge-clawhub-risk` when a non-interactive command should
108+
continue after that review. Official ClawHub skill publishers and bundled
109+
OpenClaw skill sources bypass this release-trust prompt.
100110
- `--global` targets the shared managed skills directory and cannot be combined
101111
with `--agent <id>`.
102112
- `--agent <id>` targets one configured agent workspace and overrides current

docs/cli/update.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ openclaw update --tag main
2828
openclaw update --dry-run
2929
openclaw update --no-restart
3030
openclaw update --yes
31+
openclaw update --acknowledge-clawhub-risk
3132
openclaw update --json
3233
openclaw --update
3334
```
@@ -45,6 +46,11 @@ openclaw --update
4546
when npm plugin artifact drift is detected during post-update plugin sync.
4647
- `--timeout <seconds>`: per-step timeout (default is 1800s).
4748
- `--yes`: skip confirmation prompts (for example downgrade confirmation).
49+
- `--acknowledge-clawhub-risk`: after reviewing community ClawHub trust
50+
warnings, allow post-update plugin sync to continue without an interactive
51+
prompt. Without this, risky community ClawHub plugin releases are skipped and
52+
left unchanged when OpenClaw cannot prompt. Official ClawHub packages and
53+
bundled OpenClaw plugin sources bypass this release-trust prompt.
4854

4955
`openclaw update` does not have a `--verbose` flag. Use `--dry-run` to preview
5056
the planned channel/tag/install/restart actions, `--json` for machine-readable
@@ -88,6 +94,7 @@ converge.
8894
```bash
8995
openclaw update repair
9096
openclaw update repair --channel beta
97+
openclaw update repair --acknowledge-clawhub-risk
9198
openclaw update repair --json
9299
```
93100

@@ -98,6 +105,10 @@ Options:
98105
- `--json`: print machine-readable finalization JSON.
99106
- `--timeout <seconds>`: timeout for repair steps (default `1800`).
100107
- `--yes`: skip confirmation prompts.
108+
- `--acknowledge-clawhub-risk`: after reviewing community ClawHub trust
109+
warnings, allow repair-time plugin convergence to continue without an
110+
interactive prompt. Official ClawHub packages and bundled OpenClaw plugin
111+
sources bypass this release-trust prompt.
101112
- `--no-restart`: accepted for update command parity; repair never restarts the
102113
Gateway.
103114

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/** Structured ClawHub trust details carried in gateway error payloads. */
2+
export const ClawHubTrustErrorCodes = {
3+
SECURITY_UNAVAILABLE: "clawhub_security_unavailable",
4+
RISK_ACKNOWLEDGEMENT_REQUIRED: "clawhub_risk_acknowledgement_required",
5+
DOWNLOAD_BLOCKED: "clawhub_download_blocked",
6+
} as const;
7+
8+
export type ClawHubTrustErrorCode =
9+
(typeof ClawHubTrustErrorCodes)[keyof typeof ClawHubTrustErrorCodes];
10+
11+
export type ClawHubTrustErrorDetails = {
12+
clawhubTrustCode?: ClawHubTrustErrorCode;
13+
version?: string;
14+
warning?: string;
15+
};
16+
17+
function normalizeNonEmptyString(value: unknown): string | undefined {
18+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
19+
}
20+
21+
export function isClawHubTrustErrorCode(value: unknown): value is ClawHubTrustErrorCode {
22+
return (
23+
value === ClawHubTrustErrorCodes.SECURITY_UNAVAILABLE ||
24+
value === ClawHubTrustErrorCodes.RISK_ACKNOWLEDGEMENT_REQUIRED ||
25+
value === ClawHubTrustErrorCodes.DOWNLOAD_BLOCKED
26+
);
27+
}
28+
29+
export function buildClawHubTrustErrorDetails(params: {
30+
code?: ClawHubTrustErrorCode;
31+
version?: string;
32+
warning?: string;
33+
}): ClawHubTrustErrorDetails | undefined {
34+
if (!params.code && !params.version && !params.warning) {
35+
return undefined;
36+
}
37+
return {
38+
...(params.code ? { clawhubTrustCode: params.code } : {}),
39+
...(params.version ? { version: params.version } : {}),
40+
...(params.warning ? { warning: params.warning } : {}),
41+
};
42+
}
43+
44+
export function readClawHubTrustErrorDetails(
45+
details: unknown,
46+
): ClawHubTrustErrorDetails | undefined {
47+
if (!details || typeof details !== "object" || Array.isArray(details)) {
48+
return undefined;
49+
}
50+
const raw = details as {
51+
clawhubTrustCode?: unknown;
52+
version?: unknown;
53+
warning?: unknown;
54+
};
55+
const code = isClawHubTrustErrorCode(raw.clawhubTrustCode) ? raw.clawhubTrustCode : undefined;
56+
const version = normalizeNonEmptyString(raw.version);
57+
const warning = normalizeNonEmptyString(raw.warning);
58+
if (!code && !version && !warning) {
59+
return undefined;
60+
}
61+
return {
62+
...(code ? { clawhubTrustCode: code } : {}),
63+
...(version ? { version } : {}),
64+
...(warning ? { warning } : {}),
65+
};
66+
}

packages/gateway-protocol/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
// Public gateway protocol entrypoint. Keep this barrel aligned with schema.ts
22
// so clients can import wire types, JSON schemas, and validators from one place.
3+
export {
4+
buildClawHubTrustErrorDetails,
5+
ClawHubTrustErrorCodes,
6+
isClawHubTrustErrorCode,
7+
readClawHubTrustErrorDetails,
8+
type ClawHubTrustErrorCode,
9+
type ClawHubTrustErrorDetails,
10+
} from "./clawhub-trust-error-details.js";
311
import { Compile, type Validator as TypeBoxValidator } from "typebox/compile";
412
import {
513
type AgentEvent,

packages/gateway-protocol/src/schema/agents-models-skills.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Value } from "typebox/value";
33
import { describe, expect, it } from "vitest";
44
import {
55
AgentsListResultSchema,
6+
SkillsDetailResultSchema,
67
SkillsProposalInspectResultSchema,
78
SkillsProposalRequestRevisionResultSchema,
89
ToolsEffectiveResultSchema,
@@ -173,3 +174,34 @@ describe("SkillsProposalRequestRevisionResultSchema", () => {
173174
).toBe(false);
174175
});
175176
});
177+
178+
describe("SkillsDetailResultSchema", () => {
179+
it("accepts official ClawHub skill publisher metadata", () => {
180+
const result = {
181+
skill: {
182+
slug: "tao-setup-nvidia-gpu-host",
183+
displayName: "TAO Setup NVIDIA GPU Host",
184+
summary: "Prepare an NVIDIA GPU host for TAO workflows.",
185+
tags: { gpu: "GPU" },
186+
channel: "official",
187+
isOfficial: true,
188+
createdAt: 1_700_000_000,
189+
updatedAt: 1_700_010_000,
190+
},
191+
latestVersion: {
192+
version: "1.0.0",
193+
createdAt: 1_700_010_000,
194+
},
195+
owner: {
196+
handle: "nvidia",
197+
displayName: "NVIDIA",
198+
image: "https://example.test/nvidia.png",
199+
official: true,
200+
channel: "official",
201+
isOfficial: true,
202+
},
203+
};
204+
205+
expect(Value.Check(SkillsDetailResultSchema, result)).toBe(true);
206+
});
207+
});

packages/gateway-protocol/src/schema/agents-models-skills.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ export const SkillsInstallParamsSchema = Type.Union([
344344
slug: NonEmptyString,
345345
version: Type.Optional(NonEmptyString),
346346
force: Type.Optional(Type.Boolean()),
347+
acknowledgeClawHubRisk: Type.Optional(Type.Boolean()),
347348
timeoutMs: Type.Optional(Type.Integer({ minimum: 1000 })),
348349
},
349350
{ additionalProperties: false },
@@ -379,6 +380,7 @@ export const SkillsUpdateParamsSchema = Type.Union([
379380
source: Type.Literal("clawhub"),
380381
slug: Type.Optional(NonEmptyString),
381382
all: Type.Optional(Type.Boolean()),
383+
acknowledgeClawHubRisk: Type.Optional(Type.Boolean()),
382384
},
383385
{ additionalProperties: false },
384386
),
@@ -439,6 +441,8 @@ export const SkillsDetailResultSchema = Type.Object(
439441
displayName: NonEmptyString,
440442
summary: Type.Optional(Type.String()),
441443
tags: Type.Optional(Type.Record(NonEmptyString, Type.String())),
444+
channel: Type.Optional(Type.Union([Type.String(), Type.Null()])),
445+
isOfficial: Type.Optional(Type.Union([Type.Boolean(), Type.Null()])),
442446
createdAt: Type.Integer(),
443447
updatedAt: Type.Integer(),
444448
},
@@ -478,6 +482,9 @@ export const SkillsDetailResultSchema = Type.Object(
478482
handle: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
479483
displayName: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
480484
image: Type.Optional(Type.Union([Type.String(), Type.Null()])),
485+
official: Type.Optional(Type.Union([Type.Boolean(), Type.Null()])),
486+
channel: Type.Optional(Type.Union([Type.String(), Type.Null()])),
487+
isOfficial: Type.Optional(Type.Union([Type.Boolean(), Type.Null()])),
481488
},
482489
{ additionalProperties: false },
483490
),

scripts/e2e/lib/clawhub-fixture-server.cjs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,20 @@ async function main() {
403403
npmShasum: clawpack.npmShasum,
404404
},
405405
};
406+
const securityDetail = {
407+
package: artifactResolverDetail.package,
408+
release: {
409+
version: fixture.version,
410+
},
411+
trust: {
412+
scanStatus: "clean",
413+
moderationState: null,
414+
blockedFromDownload: false,
415+
reasons: [],
416+
pending: false,
417+
stale: false,
418+
},
419+
};
406420

407421
const server = http.createServer((request, response) => {
408422
const url = new URL(request.url, "http://127.0.0.1");
@@ -429,6 +443,13 @@ async function main() {
429443
json(response, artifactResolverDetail);
430444
return;
431445
}
446+
if (
447+
url.pathname ===
448+
`/api/v1/packages/${encodeURIComponent(packageName)}/versions/${fixture.version}/security`
449+
) {
450+
json(response, securityDetail);
451+
return;
452+
}
432453
if (
433454
betaStatus !== undefined &&
434455
url.pathname === `/api/v1/packages/${encodeURIComponent(packageName)}/versions/beta`

0 commit comments

Comments
 (0)