Mesrai
Back to blog
// essayTechnical Deep Dive

Array.prototype.includes vs .indexOf on Mixed Types

Real PR catch: number array, string argument — includes always false. Coerce-at-boundary fix.

Mesrai TeamAugust 14, 20268 min read

A filter function checking if a value is in a list. Author passed a string into `Array<number>.includes`. Strict mode would have caught it; the project's tsconfig was looser, so it compiled and returned false at runtime.

The vulnerable diff

tslib/filter.ts
// lib/filter.ts
const ALLOWED_PORTS: number[] = [80, 443, 8080];

function isAllowedPort(port: unknown): boolean {
  // BUG: port is string ("443" from URL query) — includes returns false.
  return ALLOWED_PORTS.includes(port as any);
}

What is wrong

Array `includes` and `indexOf` use strict equality (`===`) at runtime. `[1,2,3].includes("1")` is always false because `1 !== "1"`. TypeScript's strict mode catches this at compile time by typing the parameter of `Array<number>.includes` as `number`. Older or less-strict configs allowed any argument, leaving the runtime mismatch invisible.

The attack

Symptom: filter returns nothing, no error, looks like an empty filter.

textexploit
ALLOWED_PORTS.includes("443")  → false
ALLOWED_PORTS.includes(443)    → true
// String came from URL.searchParams.get('port'), which always returns string.

The fix is to convert the type before the check or to widen the array to accept both.

Mesrai's review comment

textreview-comment.txt
mesraipilot · Bot · reviewed 20 sec ago

[mesrai] [code-review] [Language] [Type] [low]

Mixed types in `includes`. ALLOWED_PORTS is number[] but port is
unknown/string. Runtime returns false silently.

Convert at the boundary:

  function isAllowedPort(port: unknown): boolean {
    const n = Number(port);
    return Number.isFinite(n) && ALLOWED_PORTS.includes(n);
  }

Or widen the array if the use case calls for it:

  const ALLOWED_PORTS: (number | string)[] = [80, "80", 443, "443"];

Prefer the conversion approach — single source of truth in the typed
array.

The fix

tslib/filter.ts (fixed)
// lib/filter.ts — fixed
const ALLOWED_PORTS: number[] = [80, 443, 8080];

function isAllowedPort(port: unknown): boolean {
  const n = Number(port);
  if (!Number.isFinite(n)) return false;
  return ALLOWED_PORTS.includes(n);
}

Coerce to number, validate it parsed, then check. The check operates on aligned types.

Why human review missed it

Mixed-type `includes` is mostly caught by strict TypeScript but recurs in legacy codebases and at trust boundaries. Mesrai catches the pattern by tracing the array element type and the argument type.

Related rules + further reading

Mesrai rule pack: language/includes-type-mismatch — flags includes/indexOf with mismatched element type.

TypeScript: strict mode and `noImplicitAny`.

Common in URL query handling where everything is string.

Takeaway

Strict mode would catch this. Mesrai catches it regardless of strictness. Convert at the boundary, check on aligned types.

// try

See it on your next PR.

Free for individuals. Install in two minutes. Mesrai reviews every commit.