fix(plugins): bind warning approvals to stable inputs

This commit is contained in:
jesse-merhi
2026-08-11 14:14:38 +10:00
parent 0d97bca40a
commit e2273c8d49
13 changed files with 239 additions and 51 deletions
@@ -48,7 +48,7 @@ describe("resolveInstallPolicyWarningAcknowledgementCliOptions", () => {
targetName: "demo\npkg",
targetType: "plugin",
requestMode: fixture.requestMode,
warningFingerprint: "review-required",
approvalFingerprint: "review-required",
scan: {
requestKind: "plugin-npm",
originType: "plugin-npm",
@@ -79,7 +79,7 @@ describe("resolveInstallPolicyWarningAcknowledgementCliOptions", () => {
targetName: "demo",
targetType: "skill",
requestMode: "install",
warningFingerprint: "review-required",
approvalFingerprint: "review-required",
scan: {
requestKind: "skill-install",
originType: "skill-managed-install",
@@ -127,7 +127,7 @@ describe("resolveInstallPolicyWarningAcknowledgementCliOptions", () => {
targetName: "demo",
targetType: "plugin",
requestMode: "install",
warningFingerprint: "review-required",
approvalFingerprint: "review-required",
scan: {
requestKind: "plugin-npm",
originType: "plugin-npm",
+5 -5
View File
@@ -50,7 +50,7 @@ function warningOccurrence(
warning: InstallPolicyWarningDetails,
scan: InstallPolicyWarningScanIdentity = packageScan,
): InstallPolicyWarningOccurrence {
return { scan, warning, warningFingerprint: `fingerprint:${warning.reason}` };
return { scan, warning, approvalFingerprint: `fingerprint:${warning.reason}` };
}
async function callHandler(
@@ -435,7 +435,7 @@ describe("plugin management Gateway handlers", () => {
warnings: [
{
scan: packageScan,
warningFingerprint: "fingerprint:Scanner found behavior that needs review",
approvalFingerprint: "fingerprint:Scanner found behavior that needs review",
warning: {
targetName: "demo-plugin",
targetType: "plugin",
@@ -485,7 +485,7 @@ describe("plugin management Gateway handlers", () => {
warnings: [
{
scan: packageScan,
warningFingerprint: "fingerprint:Scanner found a different issue",
approvalFingerprint: "fingerprint:Scanner found a different issue",
warning: {
targetName: "demo-plugin",
targetType: "plugin",
@@ -591,7 +591,7 @@ describe("plugin management Gateway handlers", () => {
"scan",
);
expect((first.error as { details?: Record<string, unknown> }).details).not.toHaveProperty(
"warningFingerprint",
"approvalFingerprint",
);
managementMocks.install.mockRejectedValueOnce(
@@ -616,7 +616,7 @@ describe("plugin management Gateway handlers", () => {
"scan",
);
expect((second.error as { details?: Record<string, unknown> }).details).not.toHaveProperty(
"warningFingerprint",
"approvalFingerprint",
);
managementMocks.install.mockResolvedValueOnce({
+9
View File
@@ -1784,6 +1784,15 @@ describe("installPluginFromClawHub", () => {
expect(archiveInstallCall().onInstallPolicyWarning).toBe(onInstallPolicyWarning);
});
it("preserves the reviewed policy spec when retrying a pinned ClawHub version", async () => {
await installPluginFromClawHub({
spec: "clawhub:demo@2026.3.22",
installPolicyRequestedSpecifier: "clawhub:demo",
});
expect(archiveInstallCall().installPolicyRequest?.requestedSpecifier).toBe("clawhub:demo");
});
it("cleans up the downloaded archive even when archive install fails", async () => {
installPluginFromArchiveMock.mockResolvedValueOnce({
ok: false,
+2 -1
View File
@@ -1218,6 +1218,7 @@ export async function installPluginFromClawHub(
dryRun?: boolean;
expectedPluginId?: string;
expectedIntegrity?: string;
installPolicyRequestedSpecifier?: string;
env?: RuntimeVersionEnv;
acknowledgeClawHubRisk?: boolean;
onClawHubRisk?: (request: ClawHubRiskAcknowledgementRequest) => boolean | Promise<boolean>;
@@ -1457,7 +1458,7 @@ export async function installPluginFromClawHub(
expectedPluginId: runtimeIdResolution.expectedPluginId,
installPolicyRequest: {
kind: "plugin-archive",
requestedSpecifier: params.spec,
requestedSpecifier: params.installPolicyRequestedSpecifier ?? params.spec,
source: {
kind: "clawhub",
authority: officialClawHubPackage ? "official" : clawhubAuthority,
@@ -0,0 +1,85 @@
import fs from "node:fs/promises";
import { beforeEach, describe, expect, it, vi } from "vitest";
type ResolveNpmSpecMetadata =
typeof import("../infra/install-source-utils.js").resolveNpmSpecMetadata;
type PreflightPluginNpmInstallPolicy =
typeof import("./install-security-scan.js").preflightPluginNpmInstallPolicy;
const resolveNpmSpecMetadataMock = vi.fn<ResolveNpmSpecMetadata>();
const preflightPluginNpmInstallPolicyMock = vi.fn<PreflightPluginNpmInstallPolicy>();
vi.mock("../infra/install-source-utils.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../infra/install-source-utils.js")>();
return { ...actual, resolveNpmSpecMetadata: resolveNpmSpecMetadataMock };
});
vi.mock("./install-security-scan.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./install-security-scan.js")>();
return {
...actual,
preflightPluginNpmInstallPolicy: preflightPluginNpmInstallPolicyMock,
};
});
const { installPluginFromNpmSpec } = await import("./install-npm.js");
describe("npm install policy metadata", () => {
beforeEach(() => {
resolveNpmSpecMetadataMock.mockReset();
preflightPluginNpmInstallPolicyMock.mockReset();
resolveNpmSpecMetadataMock.mockResolvedValue({
ok: true,
metadata: {
name: "@openclaw/demo",
version: "1.2.3",
resolvedSpec: "@openclaw/demo@1.2.3",
integrity: "sha512-reviewed",
shasum: "reviewed",
packageOpenClaw: { extensions: ["./index.js"] },
},
});
});
it("keeps policy metadata stable when a retry pins the resolved npm spec", async () => {
const policyInputs: Array<{
requestedSpecifier: string;
metadata: Record<string, unknown>;
}> = [];
preflightPluginNpmInstallPolicyMock.mockImplementation(async (params) => {
policyInputs.push({
requestedSpecifier: params.requestedSpecifier ?? "",
metadata: JSON.parse(await fs.readFile(params.sourcePath, "utf8")) as Record<
string,
unknown
>,
});
return { blocked: { code: "security_scan_blocked", reason: "test stop" } };
});
const requestedSpecifier = "@openclaw/demo@latest";
const first = await installPluginFromNpmSpec({ spec: requestedSpecifier });
const retry = await installPluginFromNpmSpec({
spec: "@openclaw/demo@1.2.3",
installPolicyRequestedSpecifier: requestedSpecifier,
expectedIntegrity: "sha512-reviewed",
});
expect(first.ok).toBe(false);
expect(retry.ok).toBe(false);
expect(policyInputs).toHaveLength(2);
expect(policyInputs[0]).toMatchObject({
requestedSpecifier,
metadata: {
packageName: "@openclaw/demo",
requestedSpecifier,
resolution: {
resolvedSpec: "@openclaw/demo@1.2.3",
integrity: "sha512-reviewed",
},
},
});
expect(policyInputs[1]).toEqual(policyInputs[0]);
expect(policyInputs[0]).not.toHaveProperty("metadata.resolution.resolvedAt");
});
});
+8 -4
View File
@@ -56,6 +56,7 @@ export async function installPluginFromNpmSpec(
expectedPluginId?: string;
expectedReplacementPluginId?: string;
expectedIntegrity?: string;
installPolicyRequestedSpecifier?: string;
onIntegrityDrift?: (params: PluginNpmIntegrityDriftParams) => boolean | Promise<boolean>;
},
): Promise<InstallPluginResult> {
@@ -66,6 +67,7 @@ export async function installPluginFromNpmSpec(
);
const expectedPluginId = params.expectedPluginId;
const spec = params.spec.trim();
const installPolicyRequestedSpecifier = params.installPolicyRequestedSpecifier?.trim() || spec;
const specError = runtime.validateRegistryNpmSpec(spec);
if (specError) {
return {
@@ -211,14 +213,16 @@ export async function installPluginFromNpmSpec(
const policyTempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-npm-policy-"));
try {
const policyNpmResolution = { ...npmResolution };
delete policyNpmResolution.resolvedAt;
const policyMetadataPath = path.join(policyTempDir, "npm-package-metadata.json");
await fs.writeFile(
policyMetadataPath,
`${JSON.stringify(
{
packageName: parsedSpec.name,
requestedSpecifier: spec,
resolution: npmResolution,
requestedSpecifier: installPolicyRequestedSpecifier,
resolution: policyNpmResolution,
},
null,
2,
@@ -239,7 +243,7 @@ export async function installPluginFromNpmSpec(
mode: policyMode,
packageName: parsedSpec.name,
...(expectedPluginId ? { pluginId: expectedPluginId } : {}),
requestedSpecifier: spec,
requestedSpecifier: installPolicyRequestedSpecifier,
source: npmInstallPolicySource,
sourcePath: policyMetadataPath,
sourcePathKind: "file",
@@ -265,7 +269,7 @@ export async function installPluginFromNpmSpec(
displaySpec: spec,
installPolicyRequest: {
kind: "plugin-npm",
requestedSpecifier: spec,
requestedSpecifier: installPolicyRequestedSpecifier,
source: npmInstallPolicySource,
},
extensionsDir: params.extensionsDir,
@@ -3,7 +3,9 @@ import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js";
const runInstallPolicyMock = vi.fn();
type RunInstallPolicy = typeof import("../security/install-policy.js").runInstallPolicy;
const runInstallPolicyMock = vi.fn<RunInstallPolicy>();
const getGlobalHookRunnerMock = vi.fn();
vi.mock("../security/install-policy.js", async (importOriginal) => {
@@ -326,10 +328,18 @@ describe("legacy file install scan compatibility", () => {
const onInstallPolicyWarning = vi.fn().mockResolvedValue({ status: "approved" });
runInstallPolicyMock
.mockResolvedValueOnce({
warning: { reason: "review this plugin", fingerprint: "warning-a" },
warning: {
reason: "review this plugin",
fingerprint: "warning-a",
approvalFingerprint: "approval-a",
},
})
.mockResolvedValueOnce({
warning: { reason: "review this plugin", fingerprint: "warning-a" },
warning: {
reason: "review this plugin",
fingerprint: "warning-a",
approvalFingerprint: "approval-a",
},
});
const result = await scanFileInstallSourceRuntime({
@@ -344,7 +354,7 @@ describe("legacy file install scan compatibility", () => {
targetName: "payload",
targetType: "plugin",
requestMode: "install",
warningFingerprint: "warning-a",
approvalFingerprint: "approval-a",
scan: {
requestKind: "plugin-file",
originType: "plugin-file",
@@ -365,10 +375,18 @@ describe("legacy file install scan compatibility", () => {
const onInstallPolicyWarning = vi.fn().mockResolvedValue({ status: "approved" });
runInstallPolicyMock
.mockResolvedValueOnce({
warning: { reason: "review this plugin", fingerprint: "warning-a" },
warning: {
reason: "review this plugin",
fingerprint: "warning-a",
approvalFingerprint: "approval-a",
},
})
.mockResolvedValueOnce({
warning: { reason: "review the new finding", fingerprint: "warning-b" },
warning: {
reason: "review the new finding",
fingerprint: "warning-b",
approvalFingerprint: "approval-a",
},
findings: [
{
ruleId: "changed-warning",
@@ -406,7 +424,7 @@ describe("legacy file install scan compatibility", () => {
},
],
},
warningFingerprint: "warning-b",
approvalFingerprint: "approval-a",
},
});
expect(result?.blocked?.reason).toContain("Reason: review the new finding");
@@ -419,7 +437,11 @@ describe("legacy file install scan compatibility", () => {
it("renders metadata changes that require approval again", async () => {
const onInstallPolicyWarning = vi.fn().mockResolvedValue({ status: "approved" });
const initialWarning = {
warning: { reason: "same bounded reason", fingerprint: "full-warning-a" },
warning: {
reason: "same bounded reason",
fingerprint: "full-warning-a",
approvalFingerprint: "approval-a",
},
findings: [
{
ruleId: "initial-rule",
@@ -430,7 +452,11 @@ describe("legacy file install scan compatibility", () => {
],
};
runInstallPolicyMock.mockResolvedValueOnce(initialWarning).mockResolvedValueOnce({
warning: { ...initialWarning.warning, fingerprint: "full-warning-b" },
warning: {
...initialWarning.warning,
fingerprint: "full-warning-b",
approvalFingerprint: "approval-b",
},
findings: [
{
ruleId: "refreshed-rule",
@@ -455,7 +481,7 @@ describe("legacy file install scan compatibility", () => {
);
expect(result?.blocked?.reason).not.toContain("initial-rule");
expect(result?.blocked?.reason).not.toContain("initial evidence");
expect(result?.blocked?.installPolicyWarning?.warningFingerprint).toBe("full-warning-b");
expect(result?.blocked?.installPolicyWarning?.approvalFingerprint).toBe("approval-b");
expect(onInstallPolicyWarning).toHaveBeenCalledTimes(1);
expect(runInstallPolicyMock).toHaveBeenCalledTimes(2);
});
@@ -463,7 +489,11 @@ describe("legacy file install scan compatibility", () => {
it("keeps a block from policy re-evaluation terminal", async () => {
runInstallPolicyMock
.mockResolvedValueOnce({
warning: { reason: "review this plugin", fingerprint: "warning-a" },
warning: {
reason: "review this plugin",
fingerprint: "warning-a",
approvalFingerprint: "approval-a",
},
})
.mockResolvedValueOnce({
blocked: { code: "security_scan_blocked", reason: "now blocked" },
@@ -481,7 +511,11 @@ describe("legacy file install scan compatibility", () => {
it("keeps the deprecated unsafe flag inert when policy warns", async () => {
runInstallPolicyMock.mockResolvedValue({
warning: { reason: "review this plugin", fingerprint: "warning-a" },
warning: {
reason: "review this plugin",
fingerprint: "warning-a",
approvalFingerprint: "approval-a",
},
});
const result = await scanFileInstallSourceRuntime({
@@ -505,7 +539,7 @@ describe("legacy file install scan compatibility", () => {
requestMode: "install",
reason: "review this plugin",
},
warningFingerprint: "warning-a",
approvalFingerprint: "approval-a",
},
reason: expectedInstallPolicyNotice({
decision: "warn",
@@ -528,7 +562,11 @@ describe("legacy file install scan compatibility", () => {
it("keeps a block from acknowledged policy re-evaluation terminal", async () => {
runInstallPolicyMock
.mockResolvedValueOnce({
warning: { reason: "review this plugin", fingerprint: "warning-a" },
warning: {
reason: "review this plugin",
fingerprint: "warning-a",
approvalFingerprint: "approval-a",
},
})
.mockResolvedValueOnce({
blocked: { code: "security_scan_blocked", reason: "now blocked" },
@@ -547,7 +585,11 @@ describe("legacy file install scan compatibility", () => {
it("distinguishes an exhausted noninteractive approval from cancellation", async () => {
runInstallPolicyMock.mockResolvedValue({
warning: { reason: "review the dependency warning", fingerprint: "warning-a" },
warning: {
reason: "review the dependency warning",
fingerprint: "warning-a",
approvalFingerprint: "approval-a",
},
findings: [
{
ruleId: "dependency-warning",
@@ -584,7 +626,7 @@ describe("legacy file install scan compatibility", () => {
requestMode: "install",
reason: "review the dependency warning",
},
warningFingerprint: "warning-a",
approvalFingerprint: "approval-a",
});
expect(runInstallPolicyMock).toHaveBeenCalledTimes(1);
});
@@ -592,7 +634,11 @@ describe("legacy file install scan compatibility", () => {
it("renders warning details as one readable review notice", async () => {
const warnings: string[] = [];
runInstallPolicyMock.mockResolvedValue({
warning: { reason: "review this plugin", fingerprint: "warning-a" },
warning: {
reason: "review this plugin",
fingerprint: "warning-a",
approvalFingerprint: "approval-a",
},
findings: [{ ruleId: "context", severity: "info", message: "Informational context." }],
});
@@ -616,7 +662,7 @@ describe("legacy file install scan compatibility", () => {
reason: "review this plugin",
findings: [{ ruleId: "context", severity: "info", message: "Informational context." }],
},
warningFingerprint: "warning-a",
approvalFingerprint: "approval-a",
});
expect(warnings).toEqual([
`${expectedInstallPolicyNotice({
+2 -2
View File
@@ -841,7 +841,7 @@ async function runOperatorInstallPolicy(params: {
...(params.skill ? { skillInstallId: params.skill.installId } : {}),
},
warning: installPolicyWarning,
warningFingerprint: result.warning.fingerprint,
approvalFingerprint: result.warning.approvalFingerprint,
};
if (!params.onInstallPolicyWarning) {
return {
@@ -899,7 +899,7 @@ async function runOperatorInstallPolicy(params: {
reason: reevaluated.warning.reason,
...(reevaluated.findings?.length ? { findings: reevaluated.findings } : {}),
},
warningFingerprint: reevaluated.warning.fingerprint,
approvalFingerprint: reevaluated.warning.approvalFingerprint,
},
reason: formatInstallPolicyNotice({
decision: "warn",
+2 -2
View File
@@ -21,7 +21,7 @@ type InstallPolicyWarningScanIdentity = {
export type InstallPolicyWarningOccurrence = {
scan: InstallPolicyWarningScanIdentity;
warning: InstallPolicyWarningDetails;
warningFingerprint: string;
approvalFingerprint: string;
};
export type InstallPolicyWarningAcknowledgementRequest = InstallPolicyWarningOccurrence & {
@@ -30,7 +30,7 @@ export type InstallPolicyWarningAcknowledgementRequest = InstallPolicyWarningOcc
requestMode: "install" | "update";
};
type InstallPolicyWarningAcknowledgementResult =
export type InstallPolicyWarningAcknowledgementResult =
| { status: "approved" }
| { status: "declined" }
| {
+1 -1
View File
@@ -17,7 +17,7 @@ describe("installPluginDirectoryIntoExtensions", () => {
fs.mkdirSync(sourceDir, { recursive: true });
fs.writeFileSync(path.join(sourceDir, "index.js"), "export default {};\n");
const installPolicyWarning = {
warningFingerprint: "review-installed-dependency-tree",
approvalFingerprint: "review-installed-dependency-tree",
scan: {
requestKind: "plugin-archive" as const,
originType: "plugin-dependency-tree",
@@ -207,7 +207,7 @@ describe("plugin management install-policy acknowledgements", () => {
await expectOneShotInstallPolicyWarningAcknowledgement(mocks.clawhubInstall);
});
it("acknowledges a reviewed warning only at the same scan stage and only once", async () => {
it("acknowledges the same warning only for the same target and scan stage once", async () => {
mocks.readConfig.mockResolvedValue(configSnapshot());
mockHostedOfficialCatalog([hostedFeedDiffsEntry]);
mockClawHubInstall("diffs", "@openclaw/diffs");
@@ -220,7 +220,7 @@ describe("plugin management install-policy acknowledgements", () => {
"first approved warning",
);
const dependencyWarning: InstallPolicyWarningOccurrence = {
warningFingerprint: packageWarning.warningFingerprint,
approvalFingerprint: packageWarning.approvalFingerprint,
scan: {
...packageWarning.scan,
originType: "plugin-dependency-tree",
@@ -230,7 +230,18 @@ describe("plugin management install-policy acknowledgements", () => {
};
const changedFingerprintWarning: InstallPolicyWarningOccurrence = {
...packageWarning,
warningFingerprint: "changed-full-warning",
approvalFingerprint: "changed-warning-approval",
};
const wrongTargetWarning: InstallPolicyWarningOccurrence = {
...packageWarning,
warning: { ...packageWarning.warning, targetName: "other-plugin" },
};
const restagedPackageWarning: InstallPolicyWarningOccurrence = {
...packageWarning,
warning: {
...packageWarning.warning,
reason: "Review this warning in /tmp/openclaw-stage-b",
},
};
await installManagedPlugin({
@@ -269,11 +280,19 @@ describe("plugin management install-policy acknowledgements", () => {
...dependencyWarning,
}),
).toEqual({ status: "unavailable", reason: "warning-not-approved" });
expect(
await acknowledge({
targetName: wrongTargetWarning.warning.targetName,
targetType: wrongTargetWarning.warning.targetType,
requestMode: wrongTargetWarning.warning.requestMode,
...wrongTargetWarning,
}),
).toEqual({ status: "unavailable", reason: "warning-not-approved" });
const packageRequest: InstallPolicyWarningAcknowledgementRequest = {
targetName: packageWarning.warning.targetName,
targetType: packageWarning.warning.targetType,
requestMode: packageWarning.warning.requestMode,
...packageWarning,
targetName: restagedPackageWarning.warning.targetName,
targetType: restagedPackageWarning.warning.targetType,
requestMode: restagedPackageWarning.warning.requestMode,
...restagedPackageWarning,
};
expect(await acknowledge(packageRequest)).toEqual({ status: "approved" });
expect(await acknowledge(packageRequest)).toEqual({
@@ -284,7 +303,7 @@ describe("plugin management install-policy acknowledgements", () => {
it("pins reviewed npm warnings to the first resolved version and integrity", async () => {
const warning: InstallPolicyWarningOccurrence = {
warningFingerprint: "review-npm-package",
approvalFingerprint: "review-npm-package",
scan: {
requestKind: "plugin-npm",
originType: "plugin-npm",
@@ -343,6 +362,7 @@ describe("plugin management install-policy acknowledgements", () => {
expect(resolvedRequest).toMatchObject({
source: "official",
spec: npmResolution.resolvedSpec,
installPolicyRequestedSpecifier: "@openclaw/npm-demo",
expectedIntegrity: npmResolution.integrity,
});
@@ -362,6 +382,7 @@ describe("plugin management install-policy acknowledgements", () => {
expect(mocks.npmInstall).toHaveBeenLastCalledWith(
expect.objectContaining({
spec: npmResolution.resolvedSpec,
installPolicyRequestedSpecifier: "@openclaw/npm-demo",
expectedIntegrity: npmResolution.integrity,
}),
);
@@ -369,7 +390,7 @@ describe("plugin management install-policy acknowledgements", () => {
it("keeps npm warnings terminal when immutable resolution metadata is incomplete", async () => {
const warning: InstallPolicyWarningOccurrence = {
warningFingerprint: "review-npm-package",
approvalFingerprint: "review-npm-package",
scan: {
requestKind: "plugin-npm",
originType: "plugin-npm",
@@ -424,7 +445,7 @@ describe("plugin management install-policy acknowledgements", () => {
it("pins reviewed ClawHub warnings to the downloaded archive integrity", async () => {
const warning: InstallPolicyWarningOccurrence = {
warningFingerprint: "review-clawhub-package",
approvalFingerprint: "review-clawhub-package",
scan: {
requestKind: "plugin-archive",
originType: "plugin-package",
@@ -462,6 +483,7 @@ describe("plugin management install-policy acknowledgements", () => {
expect(failure.installPolicyResolvedRequest).toMatchObject({
source: "clawhub",
spec: "clawhub:community/demo@1.2.3",
installPolicyRequestedSpecifier: "clawhub:community/demo",
expectedIntegrity: "sha256-reviewed",
});
});
+25 -4
View File
@@ -156,6 +156,7 @@ export type ManagedPluginSourceInstallRequest =
| {
source: "clawhub";
spec: string;
installPolicyRequestedSpecifier?: string;
mode?: "install" | "update";
expectedPluginId?: string;
expectedIntegrity?: string;
@@ -171,6 +172,7 @@ export type ManagedPluginSourceInstallRequest =
| {
source: "official";
spec: string;
installPolicyRequestedSpecifier?: string;
pluginId: string;
expectedIntegrity?: string;
mode: "install" | "update";
@@ -179,6 +181,7 @@ export type ManagedPluginSourceInstallRequest =
| {
source: "npm";
spec: string;
installPolicyRequestedSpecifier?: string;
mode: "install" | "update";
pin?: boolean;
expectedPluginId?: string;
@@ -1020,6 +1023,7 @@ function pinInstallPolicyResolvedRequest(
return {
...request,
spec: npmResolution.resolvedSpec,
installPolicyRequestedSpecifier: request.installPolicyRequestedSpecifier ?? request.spec,
expectedIntegrity: npmResolution.integrity,
};
}
@@ -1034,6 +1038,7 @@ function pinInstallPolicyResolvedRequest(
? {
...request,
spec: buildClawHubSpec(parsed.name, version),
installPolicyRequestedSpecifier: request.installPolicyRequestedSpecifier ?? request.spec,
expectedIntegrity: integrity,
}
: undefined;
@@ -1378,6 +1383,9 @@ export async function installManagedPluginSource(params: {
installPluginFromClawHub({
...common,
spec: request.spec,
...(request.installPolicyRequestedSpecifier
? { installPolicyRequestedSpecifier: request.installPolicyRequestedSpecifier }
: {}),
mode: request.mode,
...(request.expectedPluginId ? { expectedPluginId: request.expectedPluginId } : {}),
...(request.expectedIntegrity ? { expectedIntegrity: request.expectedIntegrity } : {}),
@@ -1401,6 +1409,9 @@ export async function installManagedPluginSource(params: {
installPluginFromNpmSpec({
...common,
spec: request.spec,
...(request.installPolicyRequestedSpecifier
? { installPolicyRequestedSpecifier: request.installPolicyRequestedSpecifier }
: {}),
mode: request.mode,
...(request.source === "official" || request.trustedSourceLinkedOfficialInstall
? { trustedSourceLinkedOfficialInstall: true }
@@ -1533,10 +1544,20 @@ export async function installManagedPlugin(params: {
...(params.request.installPolicyWarningAcknowledgement
? {
safetyOverrides: {
onInstallPolicyWarning: async ({ scan, warning, warningFingerprint }) => {
const currentWarning = { scan, warning, warningFingerprint };
const warningIndex = remainingInstallPolicyWarnings.findIndex((approved) =>
isDeepStrictEqual(currentWarning, approved),
onInstallPolicyWarning: async ({
scan,
approvalFingerprint,
targetName,
targetType,
requestMode,
}) => {
const warningIndex = remainingInstallPolicyWarnings.findIndex(
(approved) =>
approved.approvalFingerprint === approvalFingerprint &&
isDeepStrictEqual(approved.scan, scan) &&
approved.warning.targetName === targetName &&
approved.warning.targetType === targetType &&
approved.warning.requestMode === requestMode,
);
if (warningIndex < 0) {
return { status: "unavailable", reason: "warning-not-approved" };
@@ -7,7 +7,7 @@ import type {
} from "../install-security-scan.types.js";
const officialDiffsWarningOccurrence: InstallPolicyWarningOccurrence = {
warningFingerprint: "review-diffs-warning",
approvalFingerprint: "review-diffs-warning",
scan: {
requestKind: "plugin-archive",
originType: "plugin-package",