Skip to main content
interlace
Plugin: import-nextRules

no-extraneous-dependencies

Forbid the use of extraneous packages not listed in package.json

šŸ’¼ This rule is enabled in the following configs: recommended, typescript. šŸ’” This rule is manually fixable by editor suggestions.

Rule Details

Reports a runtime import of a package the nearest manifest does not declare. Such an import works only by accident (a hoisted transitive dependency) and breaks for anyone who installs the package.

The manifest is the nearest package.json that declares a package (a name or any dependency field); a nameless {"type":"module"} scope marker in a sub-folder is skipped. packageJsonPath or packageJson replace the lookup.

A package is allowed when it is listed in dependencies, or in devDependencies, optionalDependencies, peerDependencies, bundledDependencies / bundleDependencies (npm accepts both spellings) while the matching option is true (the default). With devDependencies: false, a package listed only in devDependencies is reported as a dev dependency used in production. The package's own name is always allowed (Node self-reference).

Never checked: Node builtins (fs/promises, node:fs), relative and absolute paths, #imports, URL schemes (virtual:x), and names matched by ignore (exact) or allowPatterns (regular expressions).

Type-only imports are erased at compile time and never reported: import type, export type ... from, import type x = require(), and an import or re-export whose every specifier is an inline type, unless verbatimModuleSyntax is true, under which tsc keeps it as import {} from 'pkg'.

Every runtime spelling is checked: import, side-effect imports, export { a } from, export * from, export * as ns from, require(), import() and TS import x = require().

resolutionStrategy: 'workspace' (alias 'monorepo') additionally allows the literal @workspace/ and @company/ scopes and reads no other manifest; for your own workspace scopes use allowPatterns.

{ "name": "app", "dependencies": { "react": "^19.0.0" } }
// āŒ Incorrect
import axios from 'axios';
export * from 'lodash';

// āœ… Correct
import React from 'react';
import type { Schema } from 'zod';
import { readFile } from 'node:fs/promises';
import { feature } from 'app/feature';

Options

[
  {
    "type": "object",
    "properties": {
      "devDependencies": {
        "type": "boolean",
        "default": true,
        "description": "Allow imports from devDependencies."
      },
      "optionalDependencies": {
        "type": "boolean",
        "default": true,
        "description": "Allow imports from optionalDependencies."
      },
      "peerDependencies": {
        "type": "boolean",
        "default": true,
        "description": "Allow imports from peerDependencies."
      },
      "bundledDependencies": {
        "type": "boolean",
        "default": true,
        "description": "Allow imports from bundledDependencies."
      },
      "packageJsonPath": {
        "type": "string",
        "description": "Path to package.json file to use."
      },
      "packageJson": {
        "type": "object",
        "description": "Direct package.json content for testing."
      },
      "resolutionStrategy": {
        "type": "string",
        "enum": [
          "strict",
          "workspace",
          "monorepo"
        ],
        "default": "strict",
        "description": "Dependency resolution strategy. 'strict' allows only packages declared in the nearest package.json. 'workspace' and 'monorepo' are aliases of each other: they additionally allow the literal '@workspace/' and '@company/' scopes, and do NOT read workspace-root or sibling manifests; for real monorepo scopes use allowPatterns."
      },
      "allowPatterns": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "description": "Regex patterns for packages to allow even if not in package.json."
      },
      "ignore": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "description": "Specific package names to ignore (don't report as missing)."
      },
      "verbatimModuleSyntax": {
        "type": "boolean",
        "default": false,
        "description": "Check inline-type-only imports (`import { type X } from 'pkg'`) as runtime imports. Enable when the project compiles with TypeScript's `verbatimModuleSyntax`, under which tsc emits `import {} from 'pkg'` for them. Statement-level `import type` is always ignored."
      }
    },
    "additionalProperties": false
  }
]

Implementation

OWASP Foundation

  • Category: A00:2021 - General Security

Use this rule

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

oxlint-plugin-import-next re-exports eslint-plugin-import-next/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.