Skip to content

Commit 31de033

Browse files
committed
fix(hooks): allow dot-prefixed handler paths
1 parent e064cc9 commit 31de033

3 files changed

Lines changed: 33 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
2020
- Telegram: allow trusted local Bot API media files whose filenames start with dots instead of falling back to remote download.
2121
- Agents/Codex app-server: remap injected context files under dot-dot-prefixed workspace directories when a run switches to an effective sandbox workspace.
2222
- Control UI/i18n: use the installed workspace pi runtime for locale refreshes, update the fallback package pin, and skip scheduled refreshes with invalid provider credentials instead of failing main.
23+
- Hooks: load workspace-relative legacy hook modules from dot-dot-prefixed directories without treating the filename prefix as parent traversal.
2324
- Plugins: preserve installed package metadata and persisted registry freshness checks for plugin package paths under dot-dot-prefixed directories.
2425
- Agents: allow dot-dot-prefixed filenames such as `..note.txt` through sandbox FS bridge, remote sandbox reads, and apply_patch summaries without mistaking the name for parent traversal.
2526
- CLI/migrate: hide per-item source/plugin hints on non-conflicting Codex skill and plugin selection prompts, keeping the hint text reserved for rows that actually need attention. Thanks @sjf.

src/hooks/loader.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,28 @@ describe("loader", () => {
261261
expect(keys).toContain("command:stop");
262262
});
263263

264+
it("loads legacy handler modules from dot-prefixed workspace paths", async () => {
265+
await fs.mkdir(path.join(tmpDir, "..hooks"), { recursive: true });
266+
await writeHandlerModule(
267+
path.join("..hooks", "legacy-handler.js"),
268+
'export default async function(event) { event.messages.push("dot-prefixed-hook"); }\n',
269+
);
270+
271+
const cfg = createEnabledHooksConfig([
272+
{
273+
event: "command:new",
274+
module: path.join("..hooks", "legacy-handler.js"),
275+
},
276+
]);
277+
278+
const count = await loadInternalHooks(cfg, tmpDir);
279+
expect(count).toBe(1);
280+
281+
const event = createInternalHookEvent("command", "new", "test-session");
282+
await triggerInternalHook(event);
283+
expect(event.messages).toEqual(["dot-prefixed-hook"]);
284+
});
285+
264286
it("preserves plugin-registered hooks when workspace hooks reload", async () => {
265287
const pluginHandler = vi.fn();
266288
registerInternalHook("gateway:startup", pluginHandler);

src/hooks/loader.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ function safeLogValue(value: string): string {
3434
return sanitizeForLog(value);
3535
}
3636

37+
function isNonEmptyRelativePathInsideRoot(relativePath: string): boolean {
38+
return (
39+
relativePath !== "" &&
40+
relativePath !== ".." &&
41+
!relativePath.startsWith(`..${path.sep}`) &&
42+
!path.isAbsolute(relativePath)
43+
);
44+
}
45+
3746
function maybeWarnTrustedHookSource(source: string): void {
3847
if (source === "openclaw-workspace") {
3948
log.warn(
@@ -211,7 +220,7 @@ export async function loadInternalHooks(
211220
continue;
212221
}
213222
const rel = path.relative(baseDirReal, modulePathSafe);
214-
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
223+
if (!isNonEmptyRelativePathInsideRoot(rel)) {
215224
log.error(`Handler module path must stay within workspaceDir: ${safeLogValue(rawModule)}`);
216225
continue;
217226
}

0 commit comments

Comments
 (0)