diff --git a/server/test/unit/providers/network/advancedMatcher.test.ts b/server/test/unit/providers/network/advancedMatcher.test.ts index f94e9bb53..2cfb3a7f0 100644 --- a/server/test/unit/providers/network/advancedMatcher.test.ts +++ b/server/test/unit/providers/network/advancedMatcher.test.ts @@ -119,6 +119,34 @@ describe("AdvancedMatcher", () => { expect(result.ok).toBe(false); }); + it("returns a clear error when the regex pattern is rejected by RE2", () => { + const matcher = new AdvancedMatcher(createMockJmespath() as any); + const result = matcher.validate( + { code: "200" }, + // Backreferences are unsupported by RE2 and previously ReDoS-prone; RE2 throws on construction. + makeMonitor({ useAdvancedMatching: true, jsonPath: "code", matchMethod: "regex", expectedValue: "(a+)\\1" }) + ); + + expect(result.ok).toBe(false); + expect(result.message).toBe("Invalid regex pattern"); + expect(result.extracted).toBe("200"); + }); + + it("returns a distinct error when the extracted value cannot be coerced to a string", () => { + // An object with a throwing toString/valueOf (like Object.create(null)) makes String() throw. + const unstringifiable = { toString: () => { throw new Error("nope"); } }; + const jmespath = { search: jest.fn().mockReturnValue(unstringifiable) }; + const matcher = new AdvancedMatcher(jmespath as any); + + const result = matcher.validate( + {}, + makeMonitor({ useAdvancedMatching: true, jsonPath: "field", matchMethod: "equal", expectedValue: "x" }) + ); + + expect(result.ok).toBe(false); + expect(result.message).toBe("Error evaluating response value"); + }); + it("defaults to 'equal' comparison when matchMethod is undefined", () => { const matcher = new AdvancedMatcher(createMockJmespath() as any); const result = matcher.validate({ val: "exact" }, makeMonitor({ useAdvancedMatching: true, jsonPath: "val", expectedValue: "exact" })); diff --git a/server/test/unit/validation/monitorValidation.test.ts b/server/test/unit/validation/monitorValidation.test.ts index d7a68b00b..d6998367a 100644 --- a/server/test/unit/validation/monitorValidation.test.ts +++ b/server/test/unit/validation/monitorValidation.test.ts @@ -278,6 +278,53 @@ describe("monitorValidation — HEAD method gating", () => { }); }); +describe("monitorValidation — regex pattern gating", () => { + const baseRegex = { + name: "regex check", + type: "http" as const, + url: "https://example.com", + useAdvancedMatching: true, + matchMethod: "regex" as const, + }; + + describe("createMonitorBodyValidation", () => { + it("accepts an RE2-supported regex pattern", () => { + const parsed = createMonitorBodyValidation.parse({ ...baseRegex, expectedValue: "^2\\d{2}$" }); + expect(parsed.expectedValue).toBe("^2\\d{2}$"); + }); + + it("rejects a pattern using a backreference", () => { + expect(() => createMonitorBodyValidation.parse({ ...baseRegex, expectedValue: "(a+)\\1" })).toThrow(); + }); + + it("rejects a pattern using lookahead", () => { + expect(() => createMonitorBodyValidation.parse({ ...baseRegex, expectedValue: "foo(?=bar)" })).toThrow(); + }); + + it("ignores an invalid regex when matchMethod is not 'regex'", () => { + const parsed = createMonitorBodyValidation.parse({ ...baseRegex, matchMethod: "include", expectedValue: "(a+)\\1" }); + expect(parsed.expectedValue).toBe("(a+)\\1"); + }); + + it("ignores an empty expectedValue", () => { + const parsed = createMonitorBodyValidation.parse({ ...baseRegex, expectedValue: "" }); + expect(parsed.matchMethod).toBe("regex"); + }); + }); + + describe("editMonitorBodyValidation", () => { + it("rejects a pattern using a backreference", () => { + expect(() => editMonitorBodyValidation.parse({ matchMethod: "regex", expectedValue: "(a+)\\1" })).toThrow(); + }); + }); + + describe("importMonitorsBodyValidation", () => { + it("rejects a pattern using a backreference on imported monitors", () => { + expect(() => importMonitorsBodyValidation.parse({ monitors: [{ ...baseRegex, expectedValue: "(a+)\\1" }] })).toThrow(); + }); + }); +}); + describe("monitorValidation — customUpCodes", () => { const baseHttpBody = { name: "HTTP check",