mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix(security): consume CLI policy approval once
This commit is contained in:
+1
-1
@@ -198,7 +198,7 @@ non-npm sources are not rewritten.
|
||||
`--pin` applies to npm installs only and records the resolved exact `<name>@<version>`. It is not supported with `git:` installs (pin the ref in the spec instead, e.g. `git:github.com/acme/plugin@v1.2.3`) or with `--marketplace` (marketplace installs persist marketplace source metadata instead of an npm spec).
|
||||
</Accordion>
|
||||
<Accordion title="--acknowledge-install-policy-warning">
|
||||
When `security.installPolicy` returns `warn` in an interactive terminal, OpenClaw prints the reason and findings, then uses the same acknowledgement copy as a suspicious ClawHub release: `type: '<plugin>' to install anyway`. A matching answer re-evaluates the staged source before continuing. A declined or non-interactive install stops before commit; after review, `--acknowledge-install-policy-warning` is the explicit noninteractive approval. Every approved warning is re-evaluated before continuing. Neither form overrides `block` or a policy failure.
|
||||
When `security.installPolicy` returns `warn` in an interactive terminal, OpenClaw prints the reason and findings, then uses the same acknowledgement copy as a suspicious ClawHub release: `type: '<plugin>' to install anyway`. A matching answer re-evaluates the staged source before continuing. A declined or non-interactive install stops before commit; after review, `--acknowledge-install-policy-warning` is the explicit noninteractive approval. The flag is consumed by the first warning in one command; a later warning fails closed and requires interactive review. Every approved warning is re-evaluated before continuing. Neither form overrides `block` or a policy failure.
|
||||
|
||||
If a plugin you published on ClawHub is hidden or blocked by a registry scan, use the publisher steps in [ClawHub publishing](/clawhub/publishing). This flag does not ask ClawHub to rescan the plugin or make a blocked release public. The deprecated `--dangerously-force-unsafe-install` flag remains a no-op.
|
||||
|
||||
|
||||
+3
-1
@@ -106,7 +106,9 @@ anyway` (or `update anyway`). A matching answer evaluates the staged skill
|
||||
again before continuing. Declined and non-interactive commands stop before
|
||||
commit; after review, `--acknowledge-install-policy-warning` is the explicit
|
||||
noninteractive approval, and every approved warning is re-evaluated before
|
||||
continuing. `block` and policy failures remain terminal. The deprecated
|
||||
continuing. The flag is consumed by the first warning in one command; a later
|
||||
warning fails closed and requires interactive review. `block` and policy
|
||||
failures remain terminal. The deprecated
|
||||
`--dangerously-force-unsafe-install` flag remains a no-op.
|
||||
|
||||
Notes:
|
||||
|
||||
@@ -340,7 +340,7 @@ Plugins run in-process with the Gateway - treat them as trusted code.
|
||||
- npm and git plugin installs run package-manager dependency convergence only during the explicit install/update flow. Local paths and archives are treated as self-contained packages; OpenClaw copies/references them without running `npm install`.
|
||||
- Prefer pinned exact versions (`@scope/pkg@1.2.3`) and inspect the unpacked code before enabling.
|
||||
- `security.installPolicy` lets operators run a trusted local command to return `allow`, `warn`, or `block` for skill and plugin installs. It runs after source material is staged but before install continues and applies to ClawHub skills too.
|
||||
- A `warn` result stops before commit. Interactive CLI commands ask the operator to type the plugin or skill name using the same wording as suspicious ClawHub releases, then re-evaluate policy before continuing. Declined and non-interactive commands can use `--acknowledge-install-policy-warning` as explicit approval after review; every approved warning is re-evaluated before continuing. `block` and policy failures remain terminal. The deprecated `--dangerously-force-unsafe-install` flag remains a no-op.
|
||||
- A `warn` result stops before commit. Interactive CLI commands ask the operator to type the plugin or skill name using the same wording as suspicious ClawHub releases, then re-evaluate policy before continuing. Declined and non-interactive commands can use `--acknowledge-install-policy-warning` as explicit approval after review; the flag is consumed by the first warning in one command, and a later warning fails closed and requires interactive review. Every approved warning is re-evaluated before continuing. `block` and policy failures remain terminal. The deprecated `--dangerously-force-unsafe-install` flag remains a no-op.
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
|
||||
@@ -183,6 +183,8 @@ same `install anyway` or `update anyway` copy as suspicious ClawHub releases,
|
||||
then run policy again before continuing. Declined and non-interactive commands
|
||||
may use `--acknowledge-install-policy-warning` as explicit approval after review;
|
||||
every approved warning is re-evaluated before continuing.
|
||||
The flag is consumed by the first warning in one command; a later warning
|
||||
fails closed and requires interactive review.
|
||||
Gateway-backed and automatic installs remain
|
||||
blocked on warnings because they have no operator-confirmation flow. The deprecated
|
||||
`--dangerously-force-unsafe-install` flag remains a no-op. A `block`, non-zero
|
||||
|
||||
@@ -98,6 +98,13 @@ describe("resolveInstallPolicyWarningAcknowledgementCliOptions", () => {
|
||||
requestMode: "install",
|
||||
}),
|
||||
).resolves.toBe(true);
|
||||
await expect(
|
||||
options.onInstallPolicyWarning?.({
|
||||
targetName: "demo-dependency",
|
||||
targetType: "plugin",
|
||||
requestMode: "install",
|
||||
}),
|
||||
).resolves.toBe(false);
|
||||
expect(promptTextMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,10 +18,19 @@ export function resolveInstallPolicyWarningAcknowledgementCliOptions(params: {
|
||||
!params.acknowledgeInstallPolicyWarning &&
|
||||
params.allowPrompt !== false &&
|
||||
canPromptForInstallPolicyWarning();
|
||||
let explicitAcknowledgementAvailable = params.acknowledgeInstallPolicyWarning === true;
|
||||
return {
|
||||
...(params.dangerouslyForceUnsafeInstall ? { dangerouslyForceUnsafeInstall: true } : {}),
|
||||
...(params.acknowledgeInstallPolicyWarning
|
||||
? { onInstallPolicyWarning: async () => true }
|
||||
? {
|
||||
onInstallPolicyWarning: async () => {
|
||||
if (!explicitAcknowledgementAvailable) {
|
||||
return false;
|
||||
}
|
||||
explicitAcknowledgementAvailable = false;
|
||||
return true;
|
||||
},
|
||||
}
|
||||
: canPrompt
|
||||
? {
|
||||
onInstallPolicyWarning: async (request: InstallPolicyWarningAcknowledgementRequest) => {
|
||||
|
||||
@@ -1644,7 +1644,24 @@ describe("plugins cli install", () => {
|
||||
"--acknowledge-install-policy-warning",
|
||||
]);
|
||||
|
||||
expect(clawHubInstallCall().onInstallPolicyWarning).toEqual(expect.any(Function));
|
||||
const acknowledgement = clawHubInstallCall().onInstallPolicyWarning;
|
||||
if (typeof acknowledgement !== "function") {
|
||||
throw new Error("expected ClawHub install-policy acknowledgement callback");
|
||||
}
|
||||
await expect(
|
||||
acknowledgement({
|
||||
targetName: "demo",
|
||||
targetType: "plugin",
|
||||
requestMode: "install",
|
||||
}),
|
||||
).resolves.toBe(true);
|
||||
await expect(
|
||||
acknowledgement({
|
||||
targetName: "demo-dependency",
|
||||
targetType: "plugin",
|
||||
requestMode: "install",
|
||||
}),
|
||||
).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("does not report a ClawHub install when durable persistence fails", async () => {
|
||||
|
||||
@@ -1457,6 +1457,55 @@ describe("plugins cli update", () => {
|
||||
expect(updateParams.onInstallPolicyWarning).toEqual(expect.any(Function));
|
||||
});
|
||||
|
||||
it("shares one noninteractive install-policy acknowledgement across bulk plugin and hook updates", async () => {
|
||||
setTty(false);
|
||||
const config = createTrackedPluginConfig({
|
||||
pluginId: "openclaw-codex-app-server",
|
||||
spec: "openclaw-codex-app-server",
|
||||
});
|
||||
loadConfig.mockReturnValue(config);
|
||||
setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {});
|
||||
setHookInstallRecords({
|
||||
"demo-hooks": {
|
||||
source: "npm",
|
||||
spec: "@acme/demo-hooks@1.0.0",
|
||||
installPath: "/tmp/hooks/demo-hooks",
|
||||
},
|
||||
});
|
||||
primePluginUpdate(config);
|
||||
updateNpmInstalledHookPacks.mockResolvedValue({
|
||||
config,
|
||||
changed: false,
|
||||
outcomes: [],
|
||||
});
|
||||
|
||||
await runPluginsCommand(["plugins", "update", "--all", "--acknowledge-install-policy-warning"]);
|
||||
|
||||
const pluginAcknowledgement =
|
||||
expectSingleCallParams(updateNpmInstalledPlugins).onInstallPolicyWarning;
|
||||
const hookAcknowledgement = expectSingleCallParams(
|
||||
updateNpmInstalledHookPacks,
|
||||
).onInstallPolicyWarning;
|
||||
if (typeof pluginAcknowledgement !== "function") {
|
||||
throw new Error("expected plugin install-policy acknowledgement callback");
|
||||
}
|
||||
expect(hookAcknowledgement).toBe(pluginAcknowledgement);
|
||||
await expect(
|
||||
pluginAcknowledgement({
|
||||
targetName: "openclaw-codex-app-server",
|
||||
targetType: "plugin",
|
||||
requestMode: "update",
|
||||
}),
|
||||
).resolves.toBe(true);
|
||||
await expect(
|
||||
pluginAcknowledgement({
|
||||
targetName: "demo-hooks",
|
||||
targetType: "plugin",
|
||||
requestMode: "update",
|
||||
}),
|
||||
).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("writes updated config when updater reports changes", async () => {
|
||||
const cfg = {
|
||||
plugins: {
|
||||
|
||||
@@ -342,6 +342,11 @@ async function runPluginUpdateCommandUnlocked(params: RunPluginUpdateCommandPara
|
||||
}
|
||||
}
|
||||
|
||||
const installPolicyWarningAcknowledgement = resolveInstallPolicyWarningAcknowledgementCliOptions({
|
||||
acknowledgeInstallPolicyWarning: params.opts.acknowledgeInstallPolicyWarning,
|
||||
dangerouslyForceUnsafeInstall: params.opts.dangerouslyForceUnsafeInstall,
|
||||
allowPrompt: !params.opts.dryRun,
|
||||
});
|
||||
const pluginResult =
|
||||
pluginSelection.pluginIds.length > 0
|
||||
? await updateNpmInstalledPlugins({
|
||||
@@ -353,11 +358,7 @@ async function runPluginUpdateCommandUnlocked(params: RunPluginUpdateCommandPara
|
||||
officialPluginUpdateChannel,
|
||||
syncOfficialPluginInstalls: params.opts.all ? true : undefined,
|
||||
coreVersion: VERSION,
|
||||
...resolveInstallPolicyWarningAcknowledgementCliOptions({
|
||||
acknowledgeInstallPolicyWarning: params.opts.acknowledgeInstallPolicyWarning,
|
||||
dangerouslyForceUnsafeInstall: params.opts.dangerouslyForceUnsafeInstall,
|
||||
allowPrompt: !params.opts.dryRun,
|
||||
}),
|
||||
...installPolicyWarningAcknowledgement,
|
||||
...resolveClawHubRiskAcknowledgementCliOptions({
|
||||
acknowledgeClawHubRisk: params.opts.acknowledgeClawHubRisk,
|
||||
action: "updating",
|
||||
@@ -384,11 +385,7 @@ async function runPluginUpdateCommandUnlocked(params: RunPluginUpdateCommandPara
|
||||
hookSelection.hookIds.length > 0
|
||||
? await updateNpmInstalledHookPacks({
|
||||
config: pluginResult.config,
|
||||
...resolveInstallPolicyWarningAcknowledgementCliOptions({
|
||||
acknowledgeInstallPolicyWarning: params.opts.acknowledgeInstallPolicyWarning,
|
||||
dangerouslyForceUnsafeInstall: params.opts.dangerouslyForceUnsafeInstall,
|
||||
allowPrompt: !params.opts.dryRun,
|
||||
}),
|
||||
...installPolicyWarningAcknowledgement,
|
||||
hookIds: hookSelection.hookIds,
|
||||
specOverrides: hookSelection.specOverrides,
|
||||
dryRun: params.opts.dryRun,
|
||||
|
||||
Reference in New Issue
Block a user