fix(security): harden npm plugin and hook install integrity flow
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import type { Command } from "commander";
|
||||
import fs from "node:fs";
|
||||
import fsp from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { Command } from "commander";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { HookEntry } from "../hooks/types.js";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { loadConfig, writeConfigFile } from "../config/io.js";
|
||||
import {
|
||||
buildWorkspaceHookStatus,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
resolveHookInstallDir,
|
||||
} from "../hooks/install.js";
|
||||
import { recordHookInstall } from "../hooks/installs.js";
|
||||
import type { HookEntry } from "../hooks/types.js";
|
||||
import { loadWorkspaceHookEntries } from "../hooks/workspace.js";
|
||||
import { resolveArchiveKind } from "../infra/archive.js";
|
||||
import { buildPluginStatusReport } from "../plugins/status.js";
|
||||
@@ -26,6 +26,7 @@ import { renderTable } from "../terminal/table.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
import { resolveUserPath, shortenHomePath } from "../utils.js";
|
||||
import { formatCliCommand } from "./command-format.js";
|
||||
import { promptYesNo } from "./prompt.js";
|
||||
|
||||
export type HooksListOptions = {
|
||||
json?: boolean;
|
||||
@@ -550,7 +551,8 @@ export function registerHooksCli(program: Command): void {
|
||||
.description("Install a hook pack (path, archive, or npm spec)")
|
||||
.argument("<path-or-spec>", "Path to a hook pack or npm package spec")
|
||||
.option("-l, --link", "Link a local path instead of copying", false)
|
||||
.action(async (raw: string, opts: { link?: boolean }) => {
|
||||
.option("--pin", "Record npm installs as exact resolved <name>@<version>", false)
|
||||
.action(async (raw: string, opts: { link?: boolean; pin?: boolean }) => {
|
||||
const resolved = resolveUserPath(raw);
|
||||
const cfg = loadConfig();
|
||||
|
||||
@@ -658,13 +660,29 @@ export function registerHooksCli(program: Command): void {
|
||||
}
|
||||
|
||||
let next = enableInternalHookEntries(cfg, result.hooks);
|
||||
const resolvedSpec = result.npmResolution?.resolvedSpec;
|
||||
const recordSpec = opts.pin && resolvedSpec ? resolvedSpec : raw;
|
||||
if (opts.pin && !resolvedSpec) {
|
||||
defaultRuntime.log(
|
||||
theme.warn("Could not resolve exact npm version for --pin; storing original npm spec."),
|
||||
);
|
||||
}
|
||||
if (opts.pin && resolvedSpec) {
|
||||
defaultRuntime.log(`Pinned npm install record to ${resolvedSpec}.`);
|
||||
}
|
||||
|
||||
next = recordHookInstall(next, {
|
||||
hookId: result.hookPackId,
|
||||
source: "npm",
|
||||
spec: raw,
|
||||
spec: recordSpec,
|
||||
installPath: result.targetDir,
|
||||
version: result.version,
|
||||
resolvedName: result.npmResolution?.name,
|
||||
resolvedVersion: result.npmResolution?.version,
|
||||
resolvedSpec: result.npmResolution?.resolvedSpec,
|
||||
integrity: result.npmResolution?.integrity,
|
||||
shasum: result.npmResolution?.shasum,
|
||||
resolvedAt: result.npmResolution?.resolvedAt,
|
||||
hooks: result.hooks,
|
||||
});
|
||||
await writeConfigFile(next);
|
||||
@@ -721,6 +739,18 @@ export function registerHooksCli(program: Command): void {
|
||||
mode: "update",
|
||||
dryRun: true,
|
||||
expectedHookPackId: hookId,
|
||||
expectedIntegrity: record.integrity,
|
||||
onIntegrityDrift: async (drift) => {
|
||||
const specLabel = drift.resolution.resolvedSpec ?? drift.spec;
|
||||
defaultRuntime.log(
|
||||
theme.warn(
|
||||
`Integrity drift detected for "${hookId}" (${specLabel})` +
|
||||
`\nExpected: ${drift.expectedIntegrity}` +
|
||||
`\nActual: ${drift.actualIntegrity}`,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
},
|
||||
logger: createInstallLogger(),
|
||||
});
|
||||
if (!probe.ok) {
|
||||
@@ -742,6 +772,18 @@ export function registerHooksCli(program: Command): void {
|
||||
spec: record.spec,
|
||||
mode: "update",
|
||||
expectedHookPackId: hookId,
|
||||
expectedIntegrity: record.integrity,
|
||||
onIntegrityDrift: async (drift) => {
|
||||
const specLabel = drift.resolution.resolvedSpec ?? drift.spec;
|
||||
defaultRuntime.log(
|
||||
theme.warn(
|
||||
`Integrity drift detected for "${hookId}" (${specLabel})` +
|
||||
`\nExpected: ${drift.expectedIntegrity}` +
|
||||
`\nActual: ${drift.actualIntegrity}`,
|
||||
),
|
||||
);
|
||||
return await promptYesNo(`Continue updating "${hookId}" with this artifact?`);
|
||||
},
|
||||
logger: createInstallLogger(),
|
||||
});
|
||||
if (!result.ok) {
|
||||
@@ -756,6 +798,12 @@ export function registerHooksCli(program: Command): void {
|
||||
spec: record.spec,
|
||||
installPath: result.targetDir,
|
||||
version: nextVersion,
|
||||
resolvedName: result.npmResolution?.name,
|
||||
resolvedVersion: result.npmResolution?.version,
|
||||
resolvedSpec: result.npmResolution?.resolvedSpec,
|
||||
integrity: result.npmResolution?.integrity,
|
||||
shasum: result.npmResolution?.shasum,
|
||||
resolvedAt: result.npmResolution?.resolvedAt,
|
||||
hooks: result.hooks,
|
||||
});
|
||||
updatedCount += 1;
|
||||
|
||||
Reference in New Issue
Block a user