mirror of https://github.com/openclaw/openclaw.git
fix(clawhub): sanitize archive temp filenames (openclaw#56779)
Verified: - pnpm build - pnpm check - pnpm test Co-authored-by: soimy <1550237+soimy@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
parent
7a16a48198
commit
eee8e9679e
|
|
@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Control UI/agents: auto-load agent workspace files on initial Files panel open, and populate overview model/workspace/fallbacks from effective runtime agent metadata so defaulted models no longer show as `Not set`. (#56637) Thanks @dxsx84.
|
||||
- Control UI/slash commands: make `/steer` and `/redirect` work from the chat command palette with visible pending state for active-run `/steer`, correct redirected-run tracking, and a single canonical `/steer` entry in the command menu. (#54625) Thanks @fuller-stack-dev.
|
||||
- Exec: fail closed when the implicit sandbox host has no sandbox runtime, and stop denied async approval followups from reusing prior command output from the same session. (#56800) Thanks @scoootscooob.
|
||||
- Plugins/ClawHub: sanitize temporary archive filenames for scoped package names and slash-containing skill slugs so `openclaw plugins install @scope/name` no longer fails with `ENOENT` during archive download. (#56452) Thanks @soimy.
|
||||
|
||||
## 2026.3.28
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import os from "node:os";
|
|||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
downloadClawHubPackageArchive,
|
||||
downloadClawHubSkillArchive,
|
||||
parseClawHubPluginSpec,
|
||||
resolveClawHubAuthToken,
|
||||
searchClawHubSkills,
|
||||
|
|
@ -164,4 +166,40 @@ describe("clawhub helpers", () => {
|
|||
|
||||
await expect(searchClawHubSkills({ query: "calendar", fetchImpl })).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it("writes scoped package archives to a safe temp file name", async () => {
|
||||
const archive = await downloadClawHubPackageArchive({
|
||||
name: "@soimy/dingtalk",
|
||||
fetchImpl: async () =>
|
||||
new Response(new Uint8Array([1, 2, 3]), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/zip" },
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
expect(path.basename(archive.archivePath)).toBe("@soimy__dingtalk.zip");
|
||||
await expect(fs.readFile(archive.archivePath)).resolves.toEqual(Buffer.from([1, 2, 3]));
|
||||
} finally {
|
||||
await fs.rm(path.dirname(archive.archivePath), { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("writes skill archives to a safe temp file name when slugs contain separators", async () => {
|
||||
const archive = await downloadClawHubSkillArchive({
|
||||
slug: "ops/calendar",
|
||||
fetchImpl: async () =>
|
||||
new Response(new Uint8Array([4, 5, 6]), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/zip" },
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
expect(path.basename(archive.archivePath)).toBe("ops__calendar.zip");
|
||||
await expect(fs.readFile(archive.archivePath)).resolves.toEqual(Buffer.from([4, 5, 6]));
|
||||
} finally {
|
||||
await fs.rm(path.dirname(archive.archivePath), { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { safeDirName } from "./install-safe-path.js";
|
||||
import { isAtLeast, parseSemver } from "./runtime-guard.js";
|
||||
import { compareComparableSemver, parseComparableSemver } from "./semver-compare.js";
|
||||
|
||||
|
|
@ -580,7 +581,7 @@ export async function downloadClawHubPackageArchive(params: {
|
|||
}
|
||||
const bytes = new Uint8Array(await response.arrayBuffer());
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-clawhub-package-"));
|
||||
const archivePath = path.join(tmpDir, `${params.name}.zip`);
|
||||
const archivePath = path.join(tmpDir, `${safeDirName(params.name)}.zip`);
|
||||
await fs.writeFile(archivePath, bytes);
|
||||
return {
|
||||
archivePath,
|
||||
|
|
@ -618,7 +619,7 @@ export async function downloadClawHubSkillArchive(params: {
|
|||
}
|
||||
const bytes = new Uint8Array(await response.arrayBuffer());
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-clawhub-skill-"));
|
||||
const archivePath = path.join(tmpDir, `${params.slug}.zip`);
|
||||
const archivePath = path.join(tmpDir, `${safeDirName(params.slug)}.zip`);
|
||||
await fs.writeFile(archivePath, bytes);
|
||||
return {
|
||||
archivePath,
|
||||
|
|
|
|||
Loading…
Reference in New Issue