Skip to content

Commit e6ffcf7

Browse files
authored
docs: clarify before_install hook scope (#92766)
Signed-off-by: sallyom <[email protected]>
1 parent 8047350 commit e6ffcf7

10 files changed

Lines changed: 45 additions & 29 deletions

File tree

docs/cli/plugins.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ is available, then fall back to `latest`.
159159
<Accordion title="--dangerously-force-unsafe-install">
160160
`--dangerously-force-unsafe-install` is deprecated and is now a no-op. OpenClaw no longer runs built-in install-time dangerous-code blocking for plugin installs.
161161

162-
Use the shared operator-owned `security.installPolicy` surface when host-specific install policy is required. Plugin `before_install` hooks and `security.installPolicy` can still block installs.
162+
Use the shared operator-owned `security.installPolicy` surface when host-specific install policy is required. Plugin `before_install` hooks are plugin-runtime lifecycle hooks and are not the primary policy boundary for CLI installs.
163163

164164
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.
165165

@@ -405,7 +405,7 @@ Updates apply to tracked plugin installs in the managed plugin index and tracked
405405

406406
</Accordion>
407407
<Accordion title="--dangerously-force-unsafe-install on update">
408-
`--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` and plugin `before_install` hooks can still block updates.
408+
`--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.
409409
</Accordion>
410410
</AccordionGroup>
411411

docs/concepts/agent-loop.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ These run inside the agent loop or gateway pipeline:
9797
- **`agent_end`**: inspect the final message list and run metadata after completion.
9898
- **`before_compaction` / `after_compaction`**: observe or annotate compaction cycles.
9999
- **`before_tool_call` / `after_tool_call`**: intercept tool params/results.
100-
- **`before_install`**: inspect install context and optionally block skill or plugin installs after operator install policy runs.
100+
- **`before_install`**: inspect staged skill or plugin install material after operator install policy runs, when plugin hooks are loaded in the current OpenClaw process.
101101
- **`tool_result_persist`**: synchronously transform tool results before they are written to an OpenClaw-owned session transcript.
102102
- **`message_received` / `message_sending` / `message_sent`**: inbound + outbound message hooks.
103103
- **`session_start` / `session_end`**: session lifecycle boundaries.
@@ -109,6 +109,7 @@ Hook decision rules for outbound/tool guards:
109109
- `before_tool_call`: `{ block: false }` is a no-op and does not clear a prior block.
110110
- `before_install`: `{ block: true }` is terminal and stops lower-priority handlers.
111111
- `before_install`: `{ block: false }` is a no-op and does not clear a prior block.
112+
- Use `security.installPolicy`, not `before_install`, for operator-owned install allow/block decisions that must cover CLI install and update paths.
112113
- `message_sending`: `{ cancel: true }` is terminal and stops lower-priority handlers.
113114
- `message_sending`: `{ cancel: false }` is a no-op and does not clear a prior cancel.
114115

docs/plugins/hooks.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ observation-only.
152152
- `gateway_start` / `gateway_stop` - start or stop plugin-owned services with the Gateway
153153
- `deactivate` - deprecated compatibility alias for `gateway_stop`; use `gateway_stop` in new plugins
154154
- `cron_changed` - observe gateway-owned cron lifecycle changes (added, updated, removed, started, finished, scheduled)
155-
- **`before_install`** - inspect skill or plugin install context and optionally block
155+
- **`before_install`** - inspect staged skill or plugin install material from a loaded
156+
plugin runtime
156157

157158
## Debug runtime hooks
158159

@@ -462,11 +463,19 @@ Decision rules:
462463

463464
## Install hooks
464465

465-
`before_install` runs after the operator-owned `security.installPolicy` check
466-
when one is configured. The `builtinScan` field remains in the event payload for
467-
compatibility, but OpenClaw no longer runs built-in install-time dangerous-code
468-
blocking, so it is an empty `ok` result. Return additional findings or
469-
`{ block: true, blockReason }` to stop the install.
466+
Use `security.installPolicy` for operator-owned allow/block decisions. That
467+
policy runs from OpenClaw config, covers CLI install and update paths, and fails
468+
closed when enabled but unavailable.
469+
470+
`before_install` is a plugin-runtime lifecycle hook. It runs after
471+
`security.installPolicy` only in the OpenClaw process where plugin hooks have
472+
already been loaded, such as Gateway-backed install flows. It is useful for
473+
plugin-owned observations, warnings, and compatibility checks, but it is not the
474+
primary enterprise or host security boundary for installs. The `builtinScan`
475+
field remains in the event payload for compatibility, but OpenClaw no longer
476+
runs built-in install-time dangerous-code blocking, so it is an empty `ok`
477+
result. Return additional findings or `{ block: true, blockReason }` to stop the
478+
install in that process.
470479

471480
`block: true` is terminal. `block: false` is treated as no decision.
472481
Handler failures block the install fail-closed.

docs/plugins/sdk-overview.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,10 @@ semantics.
431431

432432
### Hook decision semantics
433433

434+
`before_install` is a plugin-runtime lifecycle hook, not the operator install
435+
policy surface. Use `security.installPolicy` when an allow/block decision must
436+
cover CLI and Gateway-backed install or update paths.
437+
434438
- `before_tool_call`: returning `{ block: true }` is terminal. Once any handler sets it, lower-priority handlers are skipped.
435439
- `before_tool_call`: returning `{ block: false }` is treated as no decision (same as omitting `block`), not as an override.
436440
- `before_install`: returning `{ block: true }` is terminal. Once any handler sets it, lower-priority handlers are skipped.

docs/tools/plugin.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,12 @@ such as `@beta` stay pinned to the selected package and fail when incompatible.
147147

148148
Configure `security.installPolicy` to run a trusted local policy command before
149149
plugin install or update proceeds. The policy receives metadata plus the staged
150-
source path and can allow or block the install. It runs before plugin
151-
`before_install` hooks. The deprecated `--dangerously-force-unsafe-install`
152-
flag is accepted for compatibility but does not bypass install policy, hooks, or
153-
OpenClaw's built-in plugin dependency denylist.
150+
source path and can allow or block the install. It covers CLI and Gateway-backed
151+
plugin install/update paths. Plugin `before_install` hooks run later only in
152+
OpenClaw processes where plugin hooks are loaded, so use `security.installPolicy`
153+
for operator-owned install decisions. The deprecated
154+
`--dangerously-force-unsafe-install` flag is accepted for compatibility but does
155+
not bypass install policy or OpenClaw's built-in plugin dependency denylist.
154156

155157
See [Skills config](/tools/skills-config#operator-install-policy-securityinstallpolicy)
156158
for the shared `security.installPolicy` exec schema used by both skills and

src/cli/plugins-cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export function registerPluginsCli(program: Command) {
153153
.option("--pin", "Record npm installs as exact resolved <name>@<version>", false)
154154
.option(
155155
"--dangerously-force-unsafe-install",
156-
"Deprecated no-op; install policy and plugin hooks may still block",
156+
"Deprecated no-op; security.installPolicy may still block",
157157
false,
158158
)
159159
.option(
@@ -184,7 +184,7 @@ export function registerPluginsCli(program: Command) {
184184
.option("--dry-run", "Show what would change without writing", false)
185185
.option(
186186
"--dangerously-force-unsafe-install",
187-
"Deprecated no-op; install policy and plugin hooks may still block",
187+
"Deprecated no-op; security.installPolicy may still block",
188188
false,
189189
)
190190
.action(async (id: string | undefined, opts: PluginUpdateOptions) => {

src/cli/plugins-cli.update.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ describe("plugins cli update", () => {
7878

7979
expect(helpText).toContain("--dangerously-force-unsafe-install");
8080
expect(helpText).toContain("Deprecated no-op");
81-
expect(helpText).toContain("install policy and");
82-
expect(helpText).toContain("plugin hooks may still block");
81+
expect(helpText).toContain("security.installPolicy");
82+
expect(helpText).toContain("may still block");
8383
});
8484

8585
it("refuses plugin updates in Nix mode before package-manager work", async () => {

src/plugins/install-policy-context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Builds install policy context for plugin install checks.
1+
// Builds payloads for the plugin-runtime before_install lifecycle hook.
22
import type {
33
PluginHookBeforeInstallBuiltinScan,
44
PluginHookBeforeInstallContext,

src/plugins/install.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,7 +1970,7 @@ describe("installPluginFromArchive", () => {
19701970
);
19711971
});
19721972

1973-
it("surfaces plugin scanner findings from before_install", async () => {
1973+
it("surfaces plugin lifecycle findings from before_install", async () => {
19741974
const handler = vi.fn().mockReturnValue({
19751975
findings: [
19761976
{
@@ -2052,10 +2052,10 @@ describe("installPluginFromArchive", () => {
20522052
expect(requests[1]?.request.requestedSpecifier).toBe(pluginDir);
20532053
});
20542054

2055-
it("blocks plugin install when before_install rejects dangerous-looking source", async () => {
2055+
it("blocks plugin install when before_install rejects the staged source", async () => {
20562056
const handler = vi.fn().mockReturnValue({
20572057
block: true,
2058-
blockReason: "Blocked by enterprise policy",
2058+
blockReason: "Blocked by plugin lifecycle hook",
20592059
});
20602060
initializeGlobalHookRunner(createMockPluginRegistry([{ hookName: "before_install", handler }]));
20612061

@@ -2078,7 +2078,7 @@ describe("installPluginFromArchive", () => {
20782078

20792079
expect(result.ok).toBe(false);
20802080
if (!result.ok) {
2081-
expect(result.error).toBe("Blocked by enterprise policy");
2081+
expect(result.error).toBe("Blocked by plugin lifecycle hook");
20822082
expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_BLOCKED);
20832083
}
20842084
expect(handler).toHaveBeenCalledTimes(1);
@@ -2098,14 +2098,14 @@ describe("installPluginFromArchive", () => {
20982098
extensions: ["index.js"],
20992099
});
21002100
expect(
2101-
warnings.some((w) => w.includes("blocked by plugin hook: Blocked by enterprise policy")),
2101+
warnings.some((w) => w.includes("blocked by plugin hook: Blocked by plugin lifecycle hook")),
21022102
).toBe(true);
21032103
});
21042104

21052105
it("keeps before_install hook blocks even when dangerous force unsafe install is set", async () => {
21062106
const handler = vi.fn().mockReturnValue({
21072107
block: true,
2108-
blockReason: "Blocked by enterprise policy",
2108+
blockReason: "Blocked by plugin lifecycle hook",
21092109
});
21102110
initializeGlobalHookRunner(createMockPluginRegistry([{ hookName: "before_install", handler }]));
21112111

@@ -2132,7 +2132,7 @@ describe("installPluginFromArchive", () => {
21322132

21332133
expect(result.ok).toBe(false);
21342134
if (!result.ok) {
2135-
expect(result.error).toBe("Blocked by enterprise policy");
2135+
expect(result.error).toBe("Blocked by plugin lifecycle hook");
21362136
expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_BLOCKED);
21372137
}
21382138
expect(
@@ -2144,7 +2144,7 @@ describe("installPluginFromArchive", () => {
21442144
).toBe(false);
21452145
expect(
21462146
warnings.some((warning) =>
2147-
warning.includes("blocked by plugin hook: Blocked by enterprise policy"),
2147+
warning.includes("blocked by plugin hook: Blocked by plugin lifecycle hook"),
21482148
),
21492149
).toBe(true);
21502150
});

src/skills/lifecycle/install.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ async function withWorkspaceCase(
109109
}
110110
}
111111

112-
describe("installSkill install policy hooks", () => {
112+
describe("installSkill before_install hooks", () => {
113113
beforeEach(() => {
114114
resetGlobalHookRunner();
115115
runCommandWithTimeoutMock.mockClear();
@@ -273,7 +273,7 @@ describe("installSkill install policy hooks", () => {
273273
it("blocks install when before_install rejects the skill", async () => {
274274
const handler = vi.fn().mockReturnValue({
275275
block: true,
276-
blockReason: "Blocked by enterprise policy",
276+
blockReason: "Blocked by plugin lifecycle hook",
277277
});
278278
initializeGlobalHookRunner(createMockPluginRegistry([{ hookName: "before_install", handler }]));
279279

@@ -287,7 +287,7 @@ describe("installSkill install policy hooks", () => {
287287
});
288288

289289
expect(result.ok).toBe(false);
290-
expect(result.message).toBe("Blocked by enterprise policy");
290+
expect(result.message).toBe("Blocked by plugin lifecycle hook");
291291
expect(runCommandWithTimeoutMock).not.toHaveBeenCalled();
292292
});
293293
});

0 commit comments

Comments
 (0)