Skip to main content
interlace
Plugin: reliabilityRules

no-unsafe-type-narrowing

Report double type assertions through unknown or any that bypass the type checker.

Report a double type assertion through unknown or any (x as unknown as T). It silences the checker exactly where it would have refused the conversion, and narrows a value nothing has checked.

This rule is part of eslint-plugin-reliability.

Quick Summary

AspectDetails
SeverityError (code quality)
Auto-Fix❌ No
CategoryQuality
Suggestions✅ Use a type guard or validate first

Rule Details

The rule reports an assertion whose operand was itself asserted to unknown or any, in every spelling TypeScript has for it:

SpellingExample
as twiceraw as unknown as User
through anyraw as any as User
angle brackets<User>(<unknown>raw)
mixed, <T> outer<User>(raw as unknown)
mixed, as outer(<unknown>raw) as User

A single assertion (raw as User, <User>raw), a widening to unknown, as const and satisfies are the checker's own business and are not reported.

Configuration

OptionTypeDefaultDescription
allowWithCommentbooleanfalseAccept a double assertion with an explanatory comment on the line above or the same line
ignoreInTestsbooleantrueSkip *.test.* / *.spec.* files

With allowWithComment, the comment must say why the assertion is sound (validated, checked, type guard, safe, known, intentional, …). A comment trailing the PREVIOUS statement speaks for that statement, not this one.

Examples

❌ Incorrect

const user = raw as unknown as User;
const user = <User>(<unknown>raw);

✅ Correct

function isUser(value: unknown): value is User {
  return typeof value === 'object' && value !== null && 'id' in value;
}
if (isUser(raw)) use(raw);

const user = raw as User; // the checker still verifies this conversion

// With { allowWithComment: true }
// validated by the schema above
const user = raw as unknown as User;

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Values the file cannot see

Why: a value that comes from another module, a parameter or an unresolved call carries no evidence in this file, and a rule that decides from structure stays silent rather than guess.

// ❌ NOT DETECTED - origin in another module
import { getValue } from './helpers';
processValue(getValue());

Mitigation: apply the same contract inside the helper, where the evidence is visible.

Use this rule

npm install -D oxlint-plugin-reliability
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-reliability"],  "rules": {    "reliability/no-unsafe-type-narrowing": "error"  }}
vite.config.ts
import { defineConfig } from 'vite-plus';export default defineConfig({  lint: {    jsPlugins: ['oxlint-plugin-reliability'],    rules: {      'reliability/no-unsafe-type-narrowing': 'error',    },  },});

oxlint-plugin-reliability re-exports eslint-plugin-reliability/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.

Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.