mirror of https://github.com/openclaw/openclaw.git
fix: harden windows npm runtime path
This commit is contained in:
parent
92191fcd68
commit
86a3149b2e
|
|
@ -0,0 +1 @@
|
|||
**/node_modules/
|
||||
|
|
@ -0,0 +1 @@
|
|||
**/node_modules/
|
||||
|
|
@ -218,6 +218,16 @@ function runPackDry(): PackResult[] {
|
|||
return JSON.parse(raw) as PackResult[];
|
||||
}
|
||||
|
||||
export function collectForbiddenPackPaths(paths: Iterable<string>): string[] {
|
||||
return [...paths]
|
||||
.filter(
|
||||
(path) =>
|
||||
forbiddenPrefixes.some((prefix) => path.startsWith(prefix)) ||
|
||||
/(^|\/)node_modules\//.test(path),
|
||||
)
|
||||
.toSorted();
|
||||
}
|
||||
|
||||
function checkPluginVersions() {
|
||||
const rootPackagePath = resolve("package.json");
|
||||
const rootPackage = JSON.parse(readFileSync(rootPackagePath, "utf8")) as PackageJson;
|
||||
|
|
@ -422,9 +432,7 @@ function main() {
|
|||
return paths.has(group) ? [] : [group];
|
||||
})
|
||||
.toSorted();
|
||||
const forbidden = [...paths].filter((path) =>
|
||||
forbiddenPrefixes.some((prefix) => path.startsWith(prefix)),
|
||||
);
|
||||
const forbidden = collectForbiddenPackPaths(paths);
|
||||
|
||||
if (missing.length > 0 || forbidden.length > 0) {
|
||||
if (missing.length > 0) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const {
|
|||
resolveDiscordAllowlistConfigMock,
|
||||
resolveNativeCommandsEnabledMock,
|
||||
resolveNativeSkillsEnabledMock,
|
||||
voiceRuntimeModuleLoadedMock,
|
||||
} = vi.hoisted(() => {
|
||||
const createdBindingManagers: Array<{ stop: ReturnType<typeof vi.fn> }> = [];
|
||||
return {
|
||||
|
|
@ -103,6 +104,7 @@ const {
|
|||
})),
|
||||
resolveNativeCommandsEnabledMock: vi.fn(() => true),
|
||||
resolveNativeSkillsEnabledMock: vi.fn(() => false),
|
||||
voiceRuntimeModuleLoadedMock: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -210,10 +212,13 @@ vi.mock("../voice/command.js", () => ({
|
|||
createDiscordVoiceCommand: () => ({ name: "voice-command" }),
|
||||
}));
|
||||
|
||||
vi.mock("../voice/manager.js", () => ({
|
||||
DiscordVoiceManager: class DiscordVoiceManager {},
|
||||
DiscordVoiceReadyListener: class DiscordVoiceReadyListener {},
|
||||
}));
|
||||
vi.mock("../voice/manager.runtime.js", () => {
|
||||
voiceRuntimeModuleLoadedMock();
|
||||
return {
|
||||
DiscordVoiceManager: class DiscordVoiceManager {},
|
||||
DiscordVoiceReadyListener: class DiscordVoiceReadyListener {},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./agent-components.js", () => ({
|
||||
createAgentComponentButton: () => ({ id: "btn" }),
|
||||
|
|
@ -390,6 +395,7 @@ describe("monitorDiscordProvider", () => {
|
|||
});
|
||||
resolveNativeCommandsEnabledMock.mockClear().mockReturnValue(true);
|
||||
resolveNativeSkillsEnabledMock.mockClear().mockReturnValue(false);
|
||||
voiceRuntimeModuleLoadedMock.mockClear();
|
||||
});
|
||||
|
||||
it("stops thread bindings when startup fails before lifecycle begins", async () => {
|
||||
|
|
@ -424,6 +430,38 @@ describe("monitorDiscordProvider", () => {
|
|||
expect(reconcileAcpThreadBindingsOnStartupMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not load the Discord voice runtime when voice is disabled", async () => {
|
||||
const { monitorDiscordProvider } = await import("./provider.js");
|
||||
|
||||
await monitorDiscordProvider({
|
||||
config: baseConfig(),
|
||||
runtime: baseRuntime(),
|
||||
});
|
||||
|
||||
expect(voiceRuntimeModuleLoadedMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("loads the Discord voice runtime only when voice is enabled", async () => {
|
||||
resolveDiscordAccountMock.mockReturnValue({
|
||||
accountId: "default",
|
||||
token: "cfg-token",
|
||||
config: {
|
||||
commands: { native: true, nativeSkills: false },
|
||||
voice: { enabled: true },
|
||||
agentComponents: { enabled: false },
|
||||
execApprovals: { enabled: false },
|
||||
},
|
||||
});
|
||||
const { monitorDiscordProvider } = await import("./provider.js");
|
||||
|
||||
await monitorDiscordProvider({
|
||||
config: baseConfig(),
|
||||
runtime: baseRuntime(),
|
||||
});
|
||||
|
||||
expect(voiceRuntimeModuleLoadedMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("treats ACP error status as uncertain during startup thread-binding probes", async () => {
|
||||
const { monitorDiscordProvider } = await import("./provider.js");
|
||||
getAcpSessionStatusMock.mockResolvedValue({ state: "error" });
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ import { resolveDiscordAccount } from "../accounts.js";
|
|||
import { fetchDiscordApplicationId } from "../probe.js";
|
||||
import { normalizeDiscordToken } from "../token.js";
|
||||
import { createDiscordVoiceCommand } from "../voice/command.js";
|
||||
import { DiscordVoiceManager, DiscordVoiceReadyListener } from "../voice/manager.js";
|
||||
import {
|
||||
createAgentComponentButton,
|
||||
createAgentSelectMenu,
|
||||
|
|
@ -104,6 +103,17 @@ export type MonitorDiscordOpts = {
|
|||
setStatus?: DiscordMonitorStatusSink;
|
||||
};
|
||||
|
||||
type DiscordVoiceManager = import("../voice/manager.js").DiscordVoiceManager;
|
||||
|
||||
type DiscordVoiceRuntimeModule = typeof import("../voice/manager.runtime.js");
|
||||
|
||||
let discordVoiceRuntimePromise: Promise<DiscordVoiceRuntimeModule> | undefined;
|
||||
|
||||
async function loadDiscordVoiceRuntime(): Promise<DiscordVoiceRuntimeModule> {
|
||||
discordVoiceRuntimePromise ??= import("../voice/manager.runtime.js");
|
||||
return await discordVoiceRuntimePromise;
|
||||
}
|
||||
|
||||
function formatThreadBindingDurationForConfigLabel(durationMs: number): string {
|
||||
const label = formatThreadBindingDurationLabel(durationMs);
|
||||
return label === "disabled" ? "off" : label;
|
||||
|
|
@ -663,6 +673,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||
}
|
||||
|
||||
if (voiceEnabled) {
|
||||
const { DiscordVoiceManager, DiscordVoiceReadyListener } = await loadDiscordVoiceRuntime();
|
||||
voiceManager = new DiscordVoiceManager({
|
||||
client,
|
||||
cfg,
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export { DiscordVoiceManager, DiscordVoiceReadyListener } from "./manager.js";
|
||||
|
|
@ -3,6 +3,7 @@ import {
|
|||
collectAppcastSparkleVersionErrors,
|
||||
collectBundledExtensionManifestErrors,
|
||||
collectBundledExtensionRootDependencyGapErrors,
|
||||
collectForbiddenPackPaths,
|
||||
} from "../scripts/release-check.ts";
|
||||
|
||||
function makeItem(shortVersion: string, sparkleVersion: string): string {
|
||||
|
|
@ -150,3 +151,15 @@ describe("collectBundledExtensionManifestErrors", () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("collectForbiddenPackPaths", () => {
|
||||
it("flags nested node_modules leaking into npm pack output", () => {
|
||||
expect(
|
||||
collectForbiddenPackPaths([
|
||||
"dist/index.js",
|
||||
"extensions/tlon/node_modules/.bin/tlon",
|
||||
"node_modules/.bin/openclaw",
|
||||
]),
|
||||
).toEqual(["extensions/tlon/node_modules/.bin/tlon", "node_modules/.bin/openclaw"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue