diff --git a/src/core/process/order.test.ts b/src/core/process/order.test.ts index 06670750..4de388a0 100644 --- a/src/core/process/order.test.ts +++ b/src/core/process/order.test.ts @@ -183,7 +183,9 @@ describe("Test processOrder", () => { const result = await fn(); assert(result.isOk()); - expect(result.value.spanAttributes["details.oracle"]).toBe("https://oracle.example.com"); + expect(result.value.spanAttributes["details.oracle.url"]).toBe( + "https://oracle.example.com", + ); expect(result.value.spanAttributes["details.oracle.prev"]).toBe( JSON.stringify(signedContext), ); diff --git a/src/core/process/order.ts b/src/core/process/order.ts index ab6b349a..1b52a85f 100644 --- a/src/core/process/order.ts +++ b/src/core/process/order.ts @@ -65,7 +65,7 @@ export async function processOrder( spanAttributes["details.owner"] = orderDetails.takeOrder.struct.order.owner.toLowerCase(); spanAttributes["details.startBlockNumber"] = dataFetcherBlockNumber.toString(); if (orderDetails.oracleUrl) { - spanAttributes["details.oracle"] = orderDetails.oracleUrl; + spanAttributes["details.oracle.url"] = orderDetails.oracleUrl; } const quoteOrderTime = performance.now(); diff --git a/src/oracle/types.test.ts b/src/oracle/types.test.ts index c0ab8769..0eb33ad9 100644 --- a/src/oracle/types.test.ts +++ b/src/oracle/types.test.ts @@ -1,40 +1,38 @@ -import { OracleHealthMap } from "./types"; +import { OracleConstants } from "./types"; import { describe, it, expect } from "vitest"; -describe("Test OracleHealthMap namespace", () => { - describe("key", () => { - it("should build the key from the url and lowercased owner", () => { - expect(OracleHealthMap.key("https://oracle.example.com", "0xAbCd")).toBe( - "https://oracle.example.com-0xabcd", - ); - }); - - it("should build identical keys regardless of owner casing", () => { - expect(OracleHealthMap.key("https://oracle.example.com", "0xABCD")).toBe( - OracleHealthMap.key("https://oracle.example.com", "0xabcd"), - ); - }); +describe("Test OracleConstants.isKnown", () => { + it("should accept the known urls", () => { + for (const url of OracleConstants.KnownUrls) { + expect(OracleConstants.isKnown(url)).toBe(true); + expect(OracleConstants.isKnown(`${url}?chain=1`)).toBe(true); + } }); - describe("getOrCreate", () => { - it("should create and store a fresh state when none exists", () => { - const map: OracleHealthMap = new Map(); - const state = OracleHealthMap.getOrCreate(map, "https://oracle.example.com", "0xAbCd"); - - expect(state).toEqual({ consecutiveFailures: 0, cooloffUntil: 0 }); - expect(map.get("https://oracle.example.com-0xabcd")).toBe(state); - expect(map.size).toBe(1); - }); - - it("should return the existing state without replacing it", () => { - const map: OracleHealthMap = new Map(); - const existing = { consecutiveFailures: 3, cooloffUntil: 123 }; - map.set("https://oracle.example.com-0xabcd", existing); - - const state = OracleHealthMap.getOrCreate(map, "https://oracle.example.com", "0xAbCd"); + it("should accept any https subdomain of a known domain", () => { + for (const url of [ + "https://t0trade.com/context", + "https://oracle.t0trade.com/context", + "https://oracle-base.t0trade.com/context", + "https://a.b.c.t0trade.com/some/path?x=1", + "https://T0TRADE.com/context", + ]) { + expect(OracleConstants.isKnown(url)).toBe(true); + } + }); - expect(state).toBe(existing); - expect(map.size).toBe(1); - }); + it("should reject lookalike hosts, other domains and non https urls", () => { + for (const url of [ + "https://t0trade.com.evil.com/context", + "https://evil-t0trade.com/context", + "https://evil.com/oracle.t0trade.com/context", + "https://evil.com/?u=https://oracle.t0trade.com/context", + "http://oracle.t0trade.com/context", + "https://example.com/context", + "not a url", + "", + ]) { + expect(OracleConstants.isKnown(url)).toBe(false); + } }); }); diff --git a/src/oracle/types.ts b/src/oracle/types.ts index 93cf2c75..db54cab8 100644 --- a/src/oracle/types.ts +++ b/src/oracle/types.ts @@ -21,8 +21,28 @@ export namespace OracleConstants { "https://oracle-robinhood.t0trade.com/context", ] as const; + /** Domains that any of their subdomains is a known oracle host, over https only */ + export const KnownDomains = ["t0trade.com"] as const; + + /** + * Determines if the given oracle url is known, that is either one of the known + * urls or an https url on a known domain or any subdomain of it, the host is + * checked as parsed so a lookalike host like t0trade.com.evil.com is rejected + * @param url - The oracle url + */ export function isKnown(url: string): boolean { - return KnownUrls.some((v) => url.startsWith(v)); + if (KnownUrls.some((v) => url.startsWith(v))) { + return true; + } + try { + const { protocol, hostname } = new URL(url); + return ( + protocol === "https:" && + KnownDomains.some((v) => hostname === v || hostname.endsWith(`.${v}`)) + ); + } catch { + return false; + } } }