Skip to content

fix(sio): reset regex lastIndex when matching dynamic namespaces#5519

Open
spokodev wants to merge 1 commit into
socketio:mainfrom
spokodev:w33/socketio-regex-lastindex
Open

fix(sio): reset regex lastIndex when matching dynamic namespaces#5519
spokodev wants to merge 1 commit into
socketio:mainfrom
spokodev:w33/socketio-regex-lastindex

Conversation

@spokodev

@spokodev spokodev commented Jul 2, 2026

Copy link
Copy Markdown

A dynamic namespace registered with a RegExp that carries the global (g) or
sticky (y) flag rejects connections intermittently.

Server.of and the parent-namespace matcher call .test() on the user supplied
regex as if it were a stateless predicate. Per the ECMAScript spec,
RegExp.prototype.test() advances lastIndex when the regex has the g or y
flag and resumes from it on the next call, so a shared regex object returns
alternating results across connections.

Concretely, io.of(/^\/room-\d+$/g) connects /room-1, then rejects /room-2
with connect_error: "Invalid namespace", then connects /room-3, and so on:

const re = /^\/room-\d+$/g
re.test('/room-1') // true
re.test('/room-2') // false (wrong)
re.test('/room-3') // true

The pattern is correct; only the leftover lastIndex state causes the rejection.

The fix resets lastIndex to 0 immediately before each .test() at both call
sites. It is a no-op for regexes without the g or y flag, so existing
behavior is unchanged.

@darrachequesne

Copy link
Copy Markdown
Member

@spokodev hi! Could you please explain your use case regarding global regular expressions?

@spokodev

spokodev commented Jul 3, 2026

Copy link
Copy Markdown
Author

Fair question. I am not deliberately setting a global flag on a namespace regex; the value here is defensive. Server.of accepts whatever RegExp the caller passes, and if that object carries g or y (a shared or reused regex, one produced by a helper or config, or a copy-pasted literal), .test() advances its lastIndex and the namespace then matches only every other connection. That is a silent, order-dependent failure that is hard to trace back to the flag. Resetting lastIndex before matching keeps the result independent of flags the caller did not intend to affect namespace routing. If you would rather guard against it another way (for example rejecting global/sticky regexes at registration), I can adjust.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants