fix(security): harden hook session-key normalization (#25750) (thanks @bmendonca3)

This commit is contained in:
Peter Steinberger
2026-02-24 23:47:59 +00:00
parent 006d0a2430
commit a327952d65
3 changed files with 19 additions and 2 deletions

View File

@@ -252,6 +252,11 @@ describe("external-content security", () => {
expect(isExternalHookSession(" HOOK:webhook:123 ")).toBe(true);
});
it("identifies Unicode full-width hook prefixes", () => {
expect(isExternalHookSession(":gmail:msg-123")).toBe(true);
expect(isExternalHookSession("custom:456")).toBe(true);
});
it("rejects non-hook sessions", () => {
expect(isExternalHookSession("cron:daily-task")).toBe(false);
expect(isExternalHookSession("agent:main")).toBe(false);
@@ -278,6 +283,11 @@ describe("external-content security", () => {
expect(getHookType("Hook:custom:456")).toBe("webhook");
});
it("returns hook type for Unicode full-width hook prefixes", () => {
expect(getHookType(":gmail:msg-123")).toBe("email");
expect(getHookType(" custom:456 ")).toBe("webhook");
});
it("returns unknown for non-hook sessions", () => {
expect(getHookType("cron:daily")).toBe("unknown");
});

View File

@@ -285,8 +285,14 @@ export function buildSafeExternalPrompt(params: {
/**
* Checks if a session key indicates an external hook source.
*/
function normalizeHookSessionKey(sessionKey: string): string {
// NFKC folds common full-width forms so hook-prefix checks cannot be bypassed by
// visually similar Unicode spellings like "...".
return sessionKey.normalize("NFKC").trim().toLowerCase();
}
export function isExternalHookSession(sessionKey: string): boolean {
const normalized = sessionKey.trim().toLowerCase();
const normalized = normalizeHookSessionKey(sessionKey);
return (
normalized.startsWith("hook:gmail:") ||
normalized.startsWith("hook:webhook:") ||
@@ -298,7 +304,7 @@ export function isExternalHookSession(sessionKey: string): boolean {
* Extracts the hook type from a session key.
*/
export function getHookType(sessionKey: string): ExternalContentSource {
const normalized = sessionKey.trim().toLowerCase();
const normalized = normalizeHookSessionKey(sessionKey);
if (normalized.startsWith("hook:gmail:")) {
return "email";
}