diff --git a/.changeset/harden-parse-struct-tag.md b/.changeset/harden-parse-struct-tag.md new file mode 100644 index 000000000..ceb5b6148 --- /dev/null +++ b/.changeset/harden-parse-struct-tag.md @@ -0,0 +1,5 @@ +--- +'@mysten/sui': patch +--- + +Fix `parseStructTag` to reject malformed inputs: empty address/module/name components (e.g. `::foo::Bar`) and trailing content after type parameters (e.g. `CoinGARBAGE`). diff --git a/packages/sui/src/utils/sui-types.ts b/packages/sui/src/utils/sui-types.ts index 131931b10..ce2a2f419 100644 --- a/packages/sui/src/utils/sui-types.ts +++ b/packages/sui/src/utils/sui-types.ts @@ -125,10 +125,20 @@ export function parseStructTag(type: string): StructTag { } const [address, module] = parts; + + if (!address || !module) { + throw new Error(`Invalid struct tag: ${type}`); + } + const isMvrPackage = isValidNamedPackage(address); const rest = type.slice(address.length + module.length + 4); const name = rest.includes('<') ? rest.slice(0, rest.indexOf('<')) : rest; + + if (!name || (rest.includes('<') && !rest.endsWith('>'))) { + throw new Error(`Invalid struct tag: ${type}`); + } + const typeParams = rest.includes('<') ? splitGenericParameters(rest.slice(rest.indexOf('<') + 1, rest.lastIndexOf('>'))).map( (typeParam) => parseTypeTag(typeParam.trim()), diff --git a/packages/sui/test/unit/types/common.test.ts b/packages/sui/test/unit/types/common.test.ts index f13401d42..66cef72e4 100644 --- a/packages/sui/test/unit/types/common.test.ts +++ b/packages/sui/test/unit/types/common.test.ts @@ -116,6 +116,18 @@ describe('parseStructTag', () => { expect(() => parseStructTag('0x2::foo::Bar')).toThrow('Invalid type tag'); }); + it('rejects struct tags with empty components', () => { + expect(() => parseStructTag('::foo::Bar')).toThrow('Invalid struct tag'); + expect(() => parseStructTag('0x2::::Bar')).toThrow('Invalid struct tag'); + expect(() => parseStructTag('0x2::foo::')).toThrow('Invalid struct tag'); + }); + + it('rejects struct tags with trailing content after type parameters', () => { + expect(() => parseStructTag('0x2::coin::CoinGARBAGE')).toThrow('Invalid struct tag'); + expect(() => parseStructTag('0x2::foo::Bar ')).toThrow('Invalid struct tag'); + expect(() => parseStructTag('0x2::foo::Barxyz')).toThrow('Invalid struct tag'); + }); + it('parses named struct tags correctly', () => { expect(parseStructTag('@mvr/demo::foo::bar')).toMatchInlineSnapshot(` {