fix(security): harden hooks module loading

This commit is contained in:
Peter Steinberger
2026-02-14 14:04:29 +01:00
parent 3d0a41b584
commit 35c0e66ed0
11 changed files with 145 additions and 20 deletions

View File

@@ -1,6 +1,35 @@
import path from "node:path";
import { z } from "zod";
import { sensitive } from "./zod-schema.sensitive.js";
function isSafeRelativeModulePath(raw: string): boolean {
const value = raw.trim();
if (!value) {
return false;
}
// Hook modules are loaded via file-path resolution + dynamic import().
// Keep this strictly relative to a configured base dir to avoid path traversal and surprises.
if (path.isAbsolute(value)) {
return false;
}
if (value.startsWith("~")) {
return false;
}
// Disallow URL-ish and drive-relative forms (e.g. "file:...", "C:foo").
if (value.includes(":")) {
return false;
}
const parts = value.split(/[\\/]+/g);
if (parts.some((part) => part === "..")) {
return false;
}
return true;
}
const SafeRelativeModulePathSchema = z
.string()
.refine(isSafeRelativeModulePath, "module must be a safe relative path (no absolute paths)");
export const HookMappingSchema = z
.object({
id: z.string().optional(),
@@ -38,7 +67,7 @@ export const HookMappingSchema = z
timeoutSeconds: z.number().int().positive().optional(),
transform: z
.object({
module: z.string(),
module: SafeRelativeModulePathSchema,
export: z.string().optional(),
})
.strict()
@@ -50,7 +79,7 @@ export const HookMappingSchema = z
export const InternalHookHandlerSchema = z
.object({
event: z.string(),
module: z.string(),
module: SafeRelativeModulePathSchema,
export: z.string().optional(),
})
.strict();