security(hooks): block prototype-chain traversal in webhook template getByPath (#22213)

* security(hooks): block prototype-chain traversal in webhook template getByPath

The getByPath() function in hooks-mapping.ts traverses attacker-controlled
webhook payload data using arbitrary property path expressions, but does not
filter dangerous property names (__proto__, constructor, prototype).

The config-paths module (config-paths.ts) already blocks these exact keys
for config path traversal via a BLOCKED_KEYS set, but the hooks template
system was not protected with the same guard.

Add a BLOCKED_PATH_KEYS set mirroring config-paths.ts and reject traversal
into __proto__, prototype, or constructor in getByPath(). Add three test
cases covering all three blocked keys.

Signed-off-by: Alan Ross <alan@sleuthco.ai>

* test(gateway): narrow hook action type in prototype-pollution tests

* changelog: credit hooks prototype-path guard in PR 22213

* changelog: move hooks prototype-path fix into security section

---------

Signed-off-by: Alan Ross <alan@sleuthco.ai>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
SleuthCo.AI
2026-02-21 03:01:03 -05:00
committed by GitHub
parent 0bee3f337a
commit fe609c0c77
3 changed files with 83 additions and 0 deletions

View File

@@ -438,6 +438,11 @@ function resolveTemplateExpr(expr: string, ctx: HookMappingContext) {
return getByPath(ctx.payload, expr);
}
// Block traversal into prototype-chain properties on attacker-controlled
// webhook payloads. Mirrors the same blocklist used by config-paths.ts
// for config path traversal.
const BLOCKED_PATH_KEYS = new Set(["__proto__", "prototype", "constructor"]);
function getByPath(input: Record<string, unknown>, pathExpr: string): unknown {
if (!pathExpr) {
return undefined;
@@ -465,6 +470,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;
}