Prevent Unsafe Casts and Non-Null Assertions in TypeScript

Catches type assertions, double casts through unknown, non-null assertions, and silenced type errors that hide real bugs in TypeScript.

What it flags

Flaggedsrc/users/public-user.ts
type UserRow = { id: string; email: string; password_hash: string; created_at: Date };
type PublicUser = { id: string; email: string; createdAt: string };

export function toPublicUser(row: UserRow): PublicUser {
  // compiles, but ships password_hash and leaves createdAt undefined
  return row as unknown as PublicUser;
}

export function findPublicUser(rows: UserRow[], id: string): PublicUser {
  return toPublicUser(rows.find((row) => row.id === id)!);
}
Passessrc/users/public-user.ts
type UserRow = { id: string; email: string; password_hash: string; created_at: Date };
type PublicUser = { id: string; email: string; createdAt: string };

export function toPublicUser(row: UserRow): PublicUser {
  return {
    id: row.id,
    email: row.email,
    createdAt: row.created_at.toISOString(),
  };
}

export function findPublicUser(rows: UserRow[], id: string): PublicUser | undefined {
  const row = rows.find((candidate) => candidate.id === id);
  return row ? toPublicUser(row) : undefined;
}

Why it matters

A type assertion tells the compiler to trust the author's claim about a value, so a wrong cast compiles cleanly and fails later as an undefined property access far from the cast. A double assertion through unknown can also leak data: casting a database row to a public response type keeps every column at runtime, including the ones the type omits. These escapes look harmless in a diff because the code around them type-checks.

Agent instructions

cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.

AGENTS.md

Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.

## Prevent Unsafe Casts and Non-Null Assertions in TypeScript

Applies to `**/*.ts`, `**/*.tsx`, `**/*.mts`, `**/*.cts`.

Review TypeScript changes for type escape hatches that make the compiler accept code it cannot verify.

Flag changes that:
- Use `as` to assert a specific type on values from outside the program or on broad types such as `unknown`, `any`, `string`, or `Record<string, unknown>` without a runtime check first.
- Use double assertions such as `as unknown as T` or `as any as T` to force a conversion the compiler rejected.
- Add non-null assertions (`!`) to values that can be null or undefined at runtime, such as `Array.prototype.find` results, `document.querySelector` results, `Map.get` without a prior `has`, or optional fields.
- Write a type predicate (`value is T`) or assertion function (`asserts value is T`) whose body does not check the properties the type claims.
- Silence the checker with an eslint-disable comment for `@typescript-eslint/no-explicit-any` or a `no-unsafe-*` rule, or with `@ts-expect-error`, without a comment that names the specific error and why it cannot be fixed.

Do not flag:
- `as const`, `satisfies`, and widening assertions to a supertype, which claim nothing the compiler has not checked.
- Non-null assertions right after a check the compiler cannot follow, such as `map.get(key)!` following `map.has(key)` in the same scope.
- Assertions in test files and fixtures that build partial objects for mocks.

Checks files matching **/*.ts, **/*.tsx, **/*.mts, **/*.cts.

More TypeScript agents

See all TypeScript code review standards

Run this agent on every pull request

cubic reviews every pull request against the agents you turn on and comments on the lines that break them.

Catch this in your code