mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:10:45 +00:00
fix: warn on invalid hook transform directories
This commit is contained in:
@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
|
||||
- fix(infra): block workspace state-directory env override [AI]. (#75940) Thanks @pgondhi987.
|
||||
- MCP/OpenAI: normalize parameter-free tool schemas whose top-level object `properties` is missing, null, or invalid before sending tools to OpenAI, so MCP tools without params stay usable. Fixes #75362. Thanks @tolkonepiu and @SymbolStar.
|
||||
- TTS: honor explicit short `[[tts:text]]...[[/tts:text]]` blocks while keeping untagged short auto-TTS suppressed, so tagged voice replies are synthesized instead of being dropped as empty voice-only payloads. Fixes #73758. Thanks @yfge.
|
||||
- Hooks/doctor: warn when `hooks.transformsDir` points outside the canonical hooks transform directory, so invalid workspace skill paths get a direct recovery hint before the Gateway crash-loops. Fixes #75853. Thanks @midobk.
|
||||
- Proxy/audio: convert standard `FormData` bodies before proxy-backed undici fetches, so audio transcription and multipart uploads no longer send `[object FormData]` when `HTTP_PROXY` or `HTTPS_PROXY` is configured. Fixes #48554. Thanks @dco5.
|
||||
- Discord: allow explicitly configured ack reactions in tool-only guild channels while keeping automatic lifecycle/status reactions suppressed. Fixes #74922. Thanks @samvilian and @BlueBirdBack.
|
||||
- Gateway/diagnostics: include a bounded redacted startup error message in stability bundles, so crash-loop reports identify the failing plugin or contract without exposing secrets. Refs #75797. Thanks @ymebosma.
|
||||
|
||||
@@ -590,6 +590,7 @@ Validation and safety notes:
|
||||
- Templates like `{{messages[0].subject}}` read from the payload.
|
||||
- `transform` can point to a JS/TS module returning a hook action.
|
||||
- `transform.module` must be a relative path and stays within `hooks.transformsDir` (absolute paths and traversal are rejected).
|
||||
- Keep `hooks.transformsDir` under `~/.openclaw/hooks/transforms`; workspace skill directories are rejected. If `openclaw doctor` reports this path as invalid, move the transform module into the hooks transforms directory or remove `hooks.transformsDir`.
|
||||
- `agentId` routes to a specific agent; unknown IDs fall back to default.
|
||||
- `allowedAgentIds`: restricts explicit routing (`*` or omitted = allow all, `[]` = deny all).
|
||||
- `defaultSessionKey`: optional fixed session key for hook agent runs without explicit `sessionKey`.
|
||||
|
||||
@@ -1385,6 +1385,30 @@ describe("doctor config flow", () => {
|
||||
expect(doctorWarnings.some((line) => line.includes("mutable allowlist"))).toBe(false);
|
||||
});
|
||||
|
||||
it("warns when hooks transformsDir points outside the hook transforms root", async () => {
|
||||
const doctorWarnings = await collectDoctorWarnings({
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "hook-secret",
|
||||
transformsDir: "/virtual/.openclaw/workspace/skills/linear-webhook",
|
||||
mappings: [
|
||||
{
|
||||
match: { path: "linear" },
|
||||
action: "agent",
|
||||
messageTemplate: "Linear event",
|
||||
transform: { module: "./openclaw-linear-transform.js" },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const warning = doctorWarnings.join("\n");
|
||||
expect(warning).toContain("hooks.transformsDir:");
|
||||
expect(warning).toContain("/virtual/.openclaw/workspace/skills/linear-webhook");
|
||||
expect(warning).toContain("/virtual/.openclaw/hooks/transforms");
|
||||
expect(warning).toContain("move custom transforms there or remove hooks.transformsDir");
|
||||
});
|
||||
|
||||
it("does not warn about sender-based group allowlist for googlechat", async () => {
|
||||
const doctorWarnings = await collectDoctorWarnings({
|
||||
channels: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import path from "node:path";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { findLegacyConfigIssues } from "../config/legacy.js";
|
||||
import { CONFIG_PATH } from "../config/paths.js";
|
||||
@@ -26,6 +27,30 @@ function hasLegacyInternalHookHandlers(raw: unknown): boolean {
|
||||
return Array.isArray(handlers) && handlers.length > 0;
|
||||
}
|
||||
|
||||
function collectInvalidHookTransformsDirWarnings(
|
||||
cfg: OpenClawConfig,
|
||||
configPath: string,
|
||||
): string[] {
|
||||
const transformsDir = cfg.hooks?.transformsDir?.trim();
|
||||
if (!transformsDir) {
|
||||
return [];
|
||||
}
|
||||
const configDir = path.dirname(configPath);
|
||||
const transformsRoot = path.join(configDir, "hooks", "transforms");
|
||||
const resolved = path.isAbsolute(transformsDir)
|
||||
? path.resolve(transformsDir)
|
||||
: path.resolve(transformsRoot, transformsDir);
|
||||
const relative = path.relative(transformsRoot, resolved);
|
||||
const escapesRoot =
|
||||
relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
|
||||
if (!escapesRoot) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
`- hooks.transformsDir: ${transformsDir} is outside ${transformsRoot}. Hook transform modules must live under ${transformsRoot}; move custom transforms there or remove hooks.transformsDir.`,
|
||||
];
|
||||
}
|
||||
|
||||
function collectConfiguredChannelIds(cfg: OpenClawConfig): string[] {
|
||||
const channels =
|
||||
cfg.channels && typeof cfg.channels === "object" && !Array.isArray(cfg.channels)
|
||||
@@ -111,6 +136,10 @@ export async function loadAndMaybeMigrateDoctorConfig(params: {
|
||||
"Legacy config keys detected",
|
||||
);
|
||||
}
|
||||
const hookTransformsDirWarnings = collectInvalidHookTransformsDirWarnings(cfg, snapshot.path);
|
||||
if (hookTransformsDirWarnings.length > 0) {
|
||||
note(sanitizeDoctorNote(hookTransformsDirWarnings.join("\n")), "Doctor warnings");
|
||||
}
|
||||
|
||||
const normalized = normalizeCompatibilityConfigValues(candidate);
|
||||
if (normalized.changes.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user