mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-21 12:04:59 +00:00
security(gateway): block prototype traversal in hook template paths
This commit is contained in:
@@ -372,4 +372,69 @@ describe("hooks mapping", () => {
|
||||
});
|
||||
expect(result?.ok).toBe(false);
|
||||
});
|
||||
|
||||
describe("prototype pollution protection", () => {
|
||||
it("blocks __proto__ traversal in webhook payload", async () => {
|
||||
const mappings = resolveHookMappings({
|
||||
mappings: [
|
||||
createGmailAgentMapping({
|
||||
id: "proto-test",
|
||||
messageTemplate: "value: {{__proto__}}",
|
||||
}),
|
||||
],
|
||||
});
|
||||
const result = await applyHookMappings(mappings, {
|
||||
payload: { __proto__: { polluted: true } } as Record<string, unknown>,
|
||||
headers: {},
|
||||
url: baseUrl,
|
||||
path: "gmail",
|
||||
});
|
||||
expect(result?.ok).toBe(true);
|
||||
if (result?.ok && result.action && result.action.kind === "agent") {
|
||||
expect(result.action.message).toBe("value: ");
|
||||
}
|
||||
});
|
||||
|
||||
it("blocks constructor traversal in webhook payload", async () => {
|
||||
const mappings = resolveHookMappings({
|
||||
mappings: [
|
||||
createGmailAgentMapping({
|
||||
id: "constructor-test",
|
||||
messageTemplate: "type: {{constructor.name}}",
|
||||
}),
|
||||
],
|
||||
});
|
||||
const result = await applyHookMappings(mappings, {
|
||||
payload: { constructor: { name: "INJECTED" } } as Record<string, unknown>,
|
||||
headers: {},
|
||||
url: baseUrl,
|
||||
path: "gmail",
|
||||
});
|
||||
expect(result?.ok).toBe(true);
|
||||
if (result?.ok && result.action && result.action.kind === "agent") {
|
||||
expect(result.action.message).toBe("type: ");
|
||||
}
|
||||
});
|
||||
|
||||
it("blocks prototype traversal in webhook payload", async () => {
|
||||
const mappings = resolveHookMappings({
|
||||
mappings: [
|
||||
createGmailAgentMapping({
|
||||
id: "prototype-test",
|
||||
messageTemplate: "val: {{prototype}}",
|
||||
}),
|
||||
],
|
||||
});
|
||||
const result = await applyHookMappings(mappings, {
|
||||
payload: { prototype: "leaked" } as Record<string, unknown>,
|
||||
headers: {},
|
||||
url: baseUrl,
|
||||
path: "gmail",
|
||||
});
|
||||
expect(result?.ok).toBe(true);
|
||||
if (result?.ok && result.action && result.action.kind === "agent") {
|
||||
expect(result.action.message).toBe("val: ");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,6 +80,8 @@ const hookPresetMappings: Record<string, HookMappingConfig[]> = {
|
||||
|
||||
const transformCache = new Map<string, HookTransformFn>();
|
||||
|
||||
const BLOCKED_PATH_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
||||
|
||||
type HookTransformResult = Partial<{
|
||||
kind: HookAction["kind"];
|
||||
text: string;
|
||||
@@ -465,6 +467,9 @@ function getByPath(input: Record<string, unknown>, pathExpr: string): unknown {
|
||||
current = current[part] as unknown;
|
||||
continue;
|
||||
}
|
||||
if (BLOCKED_PATH_KEYS.has(part)) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof current !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user