From 24b81db69f9e313eba2ff94d39ce89b73e7cc455 Mon Sep 17 00:00:00 2001 From: BathreeNode <101283333+batuhankocyigit@users.noreply.github.com> Date: Thu, 13 Aug 2026 11:43:39 +0300 Subject: [PATCH] fix(caip): reject CAIP-2/CAIP-10 IDs with extra colon-delimited segments --- packages/caip/src/caips/caip-10.test.ts | 41 ++++++++++++++++++++++++- packages/caip/src/caips/caip-10.ts | 5 +-- packages/caip/src/caips/caip-2.test.ts | 11 +++++++ packages/caip/src/caips/caip-2.ts | 5 +-- 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/packages/caip/src/caips/caip-10.test.ts b/packages/caip/src/caips/caip-10.test.ts index 7e688ea1..30b321f3 100644 --- a/packages/caip/src/caips/caip-10.test.ts +++ b/packages/caip/src/caips/caip-10.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest" -import { createCaip10AccountId } from "./index" +import { caip10Parts, createCaip10AccountId } from "./index" describe("createCaip10AccountId", () => { it("creates a caip 10 account ID for EVM address", () => { @@ -37,3 +37,42 @@ describe("createCaip10AccountId", () => { ) }) }) + +describe("caip10Parts", () => { + it("parses a valid EVM CAIP-10 account ID", () => { + const result = caip10Parts( + "eip155:1:0x1234567890123456789012345678901234567890", + ) + expect(result).toEqual({ + namespace: "eip155", + reference: "1", + accountId: "0x1234567890123456789012345678901234567890", + }) + }) + + it("throws for an empty string", () => { + // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- intentionally passing invalid input to exercise runtime validation + expect(() => caip10Parts("" as never)).toThrow( + "Invalid CAIP-10 account ID", + ) + }) + + it("throws when the account address is missing", () => { + // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- intentionally passing invalid input to exercise runtime validation + expect(() => caip10Parts("eip155:1" as never)).toThrow( + "Invalid CAIP-10 account ID", + ) + }) + + it("throws for an account ID with an extra colon-delimited segment", () => { + // Per the CAIP-10 spec, the account_address component cannot contain a + // colon (caip10AccountAddressPattern is `[-.%a-zA-Z0-9]{1,128}`), so a + // fourth colon-delimited segment makes the whole string invalid. A naive + // `split(":")` + destructure silently drops the extra segment instead of + // rejecting the malformed input. + expect(() => + // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- intentionally passing invalid input to exercise runtime validation + caip10Parts("eip155:1:0xabc:evil" as never), + ).toThrow("Invalid CAIP-10 account ID") + }) +}) diff --git a/packages/caip/src/caips/caip-10.ts b/packages/caip/src/caips/caip-10.ts index 12db7ab3..29b9c1df 100644 --- a/packages/caip/src/caips/caip-10.ts +++ b/packages/caip/src/caips/caip-10.ts @@ -48,8 +48,9 @@ export function createCaip10AccountId( } export function caip10Parts(caip: Caip10AccountId): Caip10AccountIdParts { - const [namespace, reference, accountId] = caip.split(":") - if (!namespace || !reference || !accountId) { + const parts = caip.split(":") + const [namespace, reference, accountId] = parts + if (!namespace || !reference || !accountId || parts.length !== 3) { throw new Error("Invalid CAIP-10 account ID") } return { namespace, reference, accountId } diff --git a/packages/caip/src/caips/caip-2.test.ts b/packages/caip/src/caips/caip-2.test.ts index ea5ca719..834ad536 100644 --- a/packages/caip/src/caips/caip-2.test.ts +++ b/packages/caip/src/caips/caip-2.test.ts @@ -41,6 +41,17 @@ describe("caip2Parts", () => { "Invalid CAIP-2 chain ID", ) }) + + it("throws for a chain ID with an extra colon-delimited segment", () => { + // Per the CAIP-2 spec, the reference component cannot contain a colon + // (caip2ReferencePattern is `[-_a-zA-Z0-9]{1,32}`), so a third + // colon-delimited segment makes the whole string invalid. A naive + // `split(":")` + destructure silently drops the extra segment instead + // of rejecting the malformed input. + expect(() => + caip2Parts("eip155:1:evil" as `${string}:${string}`), + ).toThrow("Invalid CAIP-2 chain ID") + }) }) describe("caip2ChainIdRegex", () => { diff --git a/packages/caip/src/caips/caip-2.ts b/packages/caip/src/caips/caip-2.ts index a943ed78..513ee522 100644 --- a/packages/caip/src/caips/caip-2.ts +++ b/packages/caip/src/caips/caip-2.ts @@ -38,9 +38,10 @@ export const caip2ChainIds = { } as const export function caip2Parts(caip: Caip2ChainId): Caip2ChainIdParts { - const [namespace, reference] = caip.split(":") + const parts = caip.split(":") + const [namespace, reference] = parts - if (!namespace || !reference) { + if (!namespace || !reference || parts.length !== 2) { throw new Error("Invalid CAIP-2 chain ID") }