mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
docs: clarify before_install hook scope (#92766)
Signed-off-by: sallyom <somalley@redhat.com>
This commit is contained in:
+2
-2
@@ -159,7 +159,7 @@ is available, then fall back to `latest`.
|
||||
<Accordion title="--dangerously-force-unsafe-install">
|
||||
`--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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -405,7 +405,7 @@ Updates apply to tracked plugin installs in the managed plugin index and tracked
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="--dangerously-force-unsafe-install on update">
|
||||
`--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.
|
||||
`--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.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ These run inside the agent loop or gateway pipeline:
|
||||
- **`agent_end`**: inspect the final message list and run metadata after completion.
|
||||
- **`before_compaction` / `after_compaction`**: observe or annotate compaction cycles.
|
||||
- **`before_tool_call` / `after_tool_call`**: intercept tool params/results.
|
||||
- **`before_install`**: inspect install context and optionally block skill or plugin installs after operator install policy runs.
|
||||
- **`before_install`**: inspect staged skill or plugin install material after operator install policy runs, when plugin hooks are loaded in the current OpenClaw process.
|
||||
- **`tool_result_persist`**: synchronously transform tool results before they are written to an OpenClaw-owned session transcript.
|
||||
- **`message_received` / `message_sending` / `message_sent`**: inbound + outbound message hooks.
|
||||
- **`session_start` / `session_end`**: session lifecycle boundaries.
|
||||
@@ -109,6 +109,7 @@ Hook decision rules for outbound/tool guards:
|
||||
- `before_tool_call`: `{ block: false }` is a no-op and does not clear a prior block.
|
||||
- `before_install`: `{ block: true }` is terminal and stops lower-priority handlers.
|
||||
- `before_install`: `{ block: false }` is a no-op and does not clear a prior block.
|
||||
- Use `security.installPolicy`, not `before_install`, for operator-owned install allow/block decisions that must cover CLI install and update paths.
|
||||
- `message_sending`: `{ cancel: true }` is terminal and stops lower-priority handlers.
|
||||
- `message_sending`: `{ cancel: false }` is a no-op and does not clear a prior cancel.
|
||||
|
||||
|
||||
+15
-6
@@ -152,7 +152,8 @@ observation-only.
|
||||
- `gateway_start` / `gateway_stop` - start or stop plugin-owned services with the Gateway
|
||||
- `deactivate` - deprecated compatibility alias for `gateway_stop`; use `gateway_stop` in new plugins
|
||||
- `cron_changed` - observe gateway-owned cron lifecycle changes (added, updated, removed, started, finished, scheduled)
|
||||
- **`before_install`** - inspect skill or plugin install context and optionally block
|
||||
- **`before_install`** - inspect staged skill or plugin install material from a loaded
|
||||
plugin runtime
|
||||
|
||||
## Debug runtime hooks
|
||||
|
||||
@@ -462,11 +463,19 @@ Decision rules:
|
||||
|
||||
## Install hooks
|
||||
|
||||
`before_install` runs after the operator-owned `security.installPolicy` check
|
||||
when one is configured. The `builtinScan` field remains in the event payload for
|
||||
compatibility, but OpenClaw no longer runs built-in install-time dangerous-code
|
||||
blocking, so it is an empty `ok` result. Return additional findings or
|
||||
`{ block: true, blockReason }` to stop the install.
|
||||
Use `security.installPolicy` for operator-owned allow/block decisions. That
|
||||
policy runs from OpenClaw config, covers CLI install and update paths, and fails
|
||||
closed when enabled but unavailable.
|
||||
|
||||
`before_install` is a plugin-runtime lifecycle hook. It runs after
|
||||
`security.installPolicy` only in the OpenClaw process where plugin hooks have
|
||||
already been loaded, such as Gateway-backed install flows. It is useful for
|
||||
plugin-owned observations, warnings, and compatibility checks, but it is not the
|
||||
primary enterprise or host security boundary for installs. The `builtinScan`
|
||||
field remains in the event payload for compatibility, but OpenClaw no longer
|
||||
runs built-in install-time dangerous-code blocking, so it is an empty `ok`
|
||||
result. Return additional findings or `{ block: true, blockReason }` to stop the
|
||||
install in that process.
|
||||
|
||||
`block: true` is terminal. `block: false` is treated as no decision.
|
||||
Handler failures block the install fail-closed.
|
||||
|
||||
@@ -431,6 +431,10 @@ semantics.
|
||||
|
||||
### Hook decision semantics
|
||||
|
||||
`before_install` is a plugin-runtime lifecycle hook, not the operator install
|
||||
policy surface. Use `security.installPolicy` when an allow/block decision must
|
||||
cover CLI and Gateway-backed install or update paths.
|
||||
|
||||
- `before_tool_call`: returning `{ block: true }` is terminal. Once any handler sets it, lower-priority handlers are skipped.
|
||||
- `before_tool_call`: returning `{ block: false }` is treated as no decision (same as omitting `block`), not as an override.
|
||||
- `before_install`: returning `{ block: true }` is terminal. Once any handler sets it, lower-priority handlers are skipped.
|
||||
|
||||
@@ -147,10 +147,12 @@ such as `@beta` stay pinned to the selected package and fail when incompatible.
|
||||
|
||||
Configure `security.installPolicy` to run a trusted local policy command before
|
||||
plugin install or update proceeds. The policy receives metadata plus the staged
|
||||
source path and can allow or block the install. It runs before plugin
|
||||
`before_install` hooks. The deprecated `--dangerously-force-unsafe-install`
|
||||
flag is accepted for compatibility but does not bypass install policy, hooks, or
|
||||
OpenClaw's built-in plugin dependency denylist.
|
||||
source path and can allow or block the install. It covers CLI and Gateway-backed
|
||||
plugin install/update paths. Plugin `before_install` hooks run later only in
|
||||
OpenClaw processes where plugin hooks are loaded, so use `security.installPolicy`
|
||||
for operator-owned install decisions. The deprecated
|
||||
`--dangerously-force-unsafe-install` flag is accepted for compatibility but does
|
||||
not bypass install policy or OpenClaw's built-in plugin dependency denylist.
|
||||
|
||||
See [Skills config](/tools/skills-config#operator-install-policy-securityinstallpolicy)
|
||||
for the shared `security.installPolicy` exec schema used by both skills and
|
||||
|
||||
@@ -153,7 +153,7 @@ export function registerPluginsCli(program: Command) {
|
||||
.option("--pin", "Record npm installs as exact resolved <name>@<version>", false)
|
||||
.option(
|
||||
"--dangerously-force-unsafe-install",
|
||||
"Deprecated no-op; install policy and plugin hooks may still block",
|
||||
"Deprecated no-op; security.installPolicy may still block",
|
||||
false,
|
||||
)
|
||||
.option(
|
||||
@@ -184,7 +184,7 @@ export function registerPluginsCli(program: Command) {
|
||||
.option("--dry-run", "Show what would change without writing", false)
|
||||
.option(
|
||||
"--dangerously-force-unsafe-install",
|
||||
"Deprecated no-op; install policy and plugin hooks may still block",
|
||||
"Deprecated no-op; security.installPolicy may still block",
|
||||
false,
|
||||
)
|
||||
.action(async (id: string | undefined, opts: PluginUpdateOptions) => {
|
||||
|
||||
@@ -78,8 +78,8 @@ describe("plugins cli update", () => {
|
||||
|
||||
expect(helpText).toContain("--dangerously-force-unsafe-install");
|
||||
expect(helpText).toContain("Deprecated no-op");
|
||||
expect(helpText).toContain("install policy and");
|
||||
expect(helpText).toContain("plugin hooks may still block");
|
||||
expect(helpText).toContain("security.installPolicy");
|
||||
expect(helpText).toContain("may still block");
|
||||
});
|
||||
|
||||
it("refuses plugin updates in Nix mode before package-manager work", async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Builds install policy context for plugin install checks.
|
||||
// Builds payloads for the plugin-runtime before_install lifecycle hook.
|
||||
import type {
|
||||
PluginHookBeforeInstallBuiltinScan,
|
||||
PluginHookBeforeInstallContext,
|
||||
|
||||
@@ -1970,7 +1970,7 @@ describe("installPluginFromArchive", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("surfaces plugin scanner findings from before_install", async () => {
|
||||
it("surfaces plugin lifecycle findings from before_install", async () => {
|
||||
const handler = vi.fn().mockReturnValue({
|
||||
findings: [
|
||||
{
|
||||
@@ -2052,10 +2052,10 @@ describe("installPluginFromArchive", () => {
|
||||
expect(requests[1]?.request.requestedSpecifier).toBe(pluginDir);
|
||||
});
|
||||
|
||||
it("blocks plugin install when before_install rejects dangerous-looking source", async () => {
|
||||
it("blocks plugin install when before_install rejects the staged source", async () => {
|
||||
const handler = vi.fn().mockReturnValue({
|
||||
block: true,
|
||||
blockReason: "Blocked by enterprise policy",
|
||||
blockReason: "Blocked by plugin lifecycle hook",
|
||||
});
|
||||
initializeGlobalHookRunner(createMockPluginRegistry([{ hookName: "before_install", handler }]));
|
||||
|
||||
@@ -2078,7 +2078,7 @@ describe("installPluginFromArchive", () => {
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error).toBe("Blocked by enterprise policy");
|
||||
expect(result.error).toBe("Blocked by plugin lifecycle hook");
|
||||
expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_BLOCKED);
|
||||
}
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
@@ -2098,14 +2098,14 @@ describe("installPluginFromArchive", () => {
|
||||
extensions: ["index.js"],
|
||||
});
|
||||
expect(
|
||||
warnings.some((w) => w.includes("blocked by plugin hook: Blocked by enterprise policy")),
|
||||
warnings.some((w) => w.includes("blocked by plugin hook: Blocked by plugin lifecycle hook")),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps before_install hook blocks even when dangerous force unsafe install is set", async () => {
|
||||
const handler = vi.fn().mockReturnValue({
|
||||
block: true,
|
||||
blockReason: "Blocked by enterprise policy",
|
||||
blockReason: "Blocked by plugin lifecycle hook",
|
||||
});
|
||||
initializeGlobalHookRunner(createMockPluginRegistry([{ hookName: "before_install", handler }]));
|
||||
|
||||
@@ -2132,7 +2132,7 @@ describe("installPluginFromArchive", () => {
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error).toBe("Blocked by enterprise policy");
|
||||
expect(result.error).toBe("Blocked by plugin lifecycle hook");
|
||||
expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_BLOCKED);
|
||||
}
|
||||
expect(
|
||||
@@ -2144,7 +2144,7 @@ describe("installPluginFromArchive", () => {
|
||||
).toBe(false);
|
||||
expect(
|
||||
warnings.some((warning) =>
|
||||
warning.includes("blocked by plugin hook: Blocked by enterprise policy"),
|
||||
warning.includes("blocked by plugin hook: Blocked by plugin lifecycle hook"),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -109,7 +109,7 @@ async function withWorkspaceCase(
|
||||
}
|
||||
}
|
||||
|
||||
describe("installSkill install policy hooks", () => {
|
||||
describe("installSkill before_install hooks", () => {
|
||||
beforeEach(() => {
|
||||
resetGlobalHookRunner();
|
||||
runCommandWithTimeoutMock.mockClear();
|
||||
@@ -273,7 +273,7 @@ describe("installSkill install policy hooks", () => {
|
||||
it("blocks install when before_install rejects the skill", async () => {
|
||||
const handler = vi.fn().mockReturnValue({
|
||||
block: true,
|
||||
blockReason: "Blocked by enterprise policy",
|
||||
blockReason: "Blocked by plugin lifecycle hook",
|
||||
});
|
||||
initializeGlobalHookRunner(createMockPluginRegistry([{ hookName: "before_install", handler }]));
|
||||
|
||||
@@ -287,7 +287,7 @@ describe("installSkill install policy hooks", () => {
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.message).toBe("Blocked by enterprise policy");
|
||||
expect(result.message).toBe("Blocked by plugin lifecycle hook");
|
||||
expect(runCommandWithTimeoutMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user