# ts-node@10.9.2 security report (npm)

- Verdict: **Needs review** (risk level: medium)
- Scanned: 2026-10-04T16:41:35.000Z
- Files reviewed: 50
- Findings: 7 medium, 21 low severity findings
- Report: https://security.togoder.click/npm/ts-node
- Source: Togoder Security (https://security.togoder.click), AI source-code review

## Summary

Togoder Security scanned the npm package ts-node@10.9.2 on Oct 4, 2026. An AI review of 50 source files produced 7 medium, 21 low severity findings. The overall verdict is medium: the findings flag risky but common patterns (dynamic code, unsafe defaults, broad file or network access) rather than confirmed malware.

## Findings

### [medium] Dynamic module resolution with external input

Finding ID: `NPS-B74BA9E48017`

File: `dist-raw/node-internal-modules-cjs-loader.js:180`

The code implements a custom CommonJS loader that resolves module paths based on user-supplied package.json fields and request strings. While this mirrors Node.js internal behavior, the presence of custom resolution logic that reads and interprets package.json 'main', 'exports', and 'imports' fields could be abused if an attacker can control package.json contents (e.g., via a malicious dependency) to load unintended files. However, this is a standard pattern for TS-Node-like tools and not inherently malicious.

### [medium] dynamic module loading with external input

Finding ID: `NPS-7A2BD5D24621`

File: `dist-raw/node-internal-modules-esm-resolve.js`

The code implements an ES module resolver that dynamically constructs module paths and resolves specifiers from user input (e.g., package.json exports/imports fields, specifiers). While this is expected for a resolver, it could be abused if malicious package.json files are present, potentially leading to loading unintended modules.

### [medium] dynamic import with computed input

Finding ID: `NPS-DDC826B76E27`

File: `dist-raw/runmain-hack.js:8`

The function takes an arbitrary entryPointPath and calls import() on its file URL. If an attacker can control entryPointPath, this enables loading and executing arbitrary JavaScript modules from any path, potentially bypassing Module.runMain restrictions.

### [medium] Encoded payload in command-line arguments

Finding ID: `NPS-C9EF49B9448D`

File: `dist/child/argv-payload.js:5`

The module exports an argPrefix constant ('--brotli-base64-config=') and compress/decompress functions that serialize and deserialize arbitrary objects to/from base64-encoded Brotli data intended to be passed via process.argv. While the code itself does not perform any exfiltration, credential harvesting, or execution, it provides a generic mechanism for embedding opaque serialized configuration data into command-line arguments. This pattern can be abused by other parts of the package to pass hidden or obfuscated payloads through process argv, which is a common technique in malicious packages to smuggle data past casual inspection. The decompress function uses JSON.parse on untrusted input, which could be leveraged for prototype pollution or unexpected object injection if the parsed object is later merged or used unsafely.

### [medium] Suppression of security-relevant warnings

Finding ID: `NPS-1DDD73BA8EE3`

File: `dist/child/child-require.js`

The `onWarning` function specifically filters out `ExperimentalWarning` messages that match a regex for `--loader` or `Custom ESM Loaders`. This suppresses warnings that Node.js emits when using experimental ESM loader hooks. While this is a common technique to reduce noise, suppressing such warnings could hide the use of custom loaders that might be used to load malicious code or alter module resolution.

### [medium] Module loading hook installation

Finding ID: `NPS-DDA8B16F2073`

File: `dist/cjs-resolve-hooks.js:9`

The file installs custom CommonJS module resolution hooks by overriding Module._resolveFilename and Module._findPath. This behavior is typical of ts-node's experimental resolver and is gated behind tsNodeService.options.experimentalResolver, but monkeypatching Node's internal module loader is a sensitive operation that can alter how dependencies are resolved. If the tsNodeService is influenced by untrusted input, it could potentially redirect module resolution to attacker-controlled code.

### [medium] Dynamic module loading / import-time side effect

Finding ID: `NPS-BAAE871A199F`

File: `register/index.js:1`

The file requires the parent module ('../') and immediately invokes its register() method at the top level. This means that importing this file triggers execution of arbitrary code defined in the parent package's register() function. While the current file itself contains no obvious malicious payload, the pattern of executing a register() function at import time is a common vector for side effects such as registering hooks, installing backdoors, or performing network/file operations. The actual behavior depends entirely on the implementation of register() in the parent module, which is not shown and should be audited.

### [low] Dynamic module loading

Finding ID: `NPS-76A88418D2E1`

File: `child-loader.mjs:4`

Uses createRequire() and require() to dynamically load a module from the dist directory. While this pattern is commonly used in ESM/CJS interoperability layers, dynamic require() can be abused to load arbitrary or computed modules and bypass static analysis. The file imports a local package-relative path, which limits the immediate risk, but the pattern warrants review.

### [low] TODO comment indicating unresolved design

Finding ID: `NPS-7E9E7FD38CD8`

File: `child-loader.mjs:4`

The comment 'TODO why use require() here? I think we can just `import`' indicates the author is uncertain about the dynamic require usage. Unresolved dynamic loading patterns are a code smell and could hide future malicious changes if the required path is later altered.

### [low] Use of internal Node.js primordials

Finding ID: `NPS-DB04E2652ABA`

File: `dist-raw/node-internal-modules-cjs-loader.js:7`

The code imports internal Node.js primordials and internal bindings (e.g., node-primordials, node-internalBinding-fs). This is typical for tools that patch Node.js internals (like ts-node) but indicates deep integration that could break or be abused in future Node versions. Not malicious by itself.

### [low] Top-level code execution on import

Finding ID: `NPS-2F73AAE77AEB`

File: `dist-raw/node-internal-modules-cjs-loader.js:30`

The module executes top-level code (e.g., reading options, initializing caches) when imported. This is normal for a module loader but means any side effects (like reading environment options via getOptionValue) occur at import time. No malicious side effects observed.

### [low] File system access and stat caching

Finding ID: `NPS-4B4EBF5FB0E9`

File: `dist-raw/node-internal-modules-cjs-loader.js:50`

The code performs extensive file system operations (stat, realpathSync, readPackage) to resolve modules. This is expected for a module loader but could be leveraged for reconnaissance (probing file existence) if the attacker controls the request paths. No direct exfiltration or credential harvesting is present.

### [low] Potential for path traversal via package.json fields

Finding ID: `NPS-D006034372AC`

File: `dist-raw/node-internal-modules-cjs-loader.js:130`

The tryPackage function resolves the 'main' field of package.json to a file path without strict validation. If a malicious package.json contains a 'main' field like '../../etc/passwd', it could attempt to load arbitrary files. However, this is standard Node.js behavior and not a vulnerability introduced by this code. The code does not sanitize but relies on Node's resolution rules.

### [low] file system access

Finding ID: `NPS-1400FF7EC891`

File: `dist-raw/node-internal-modules-esm-resolve.js`

The code uses fs.statSync and fs.realpathSync to check file existence and resolve paths, including accessing node_modules directories and package.json files. This is typical for a module resolver, but excessive file system probing could be a concern if the input is untrusted.

### [low] environment-dependent behavior

Finding ID: `NPS-2757B2828C2E`

File: `dist-raw/node-internal-modules-esm-resolve.js`

The resolver uses process.versions.node and getOptionValue to adjust behavior based on Node.js version and command-line flags. This is standard, but it could be used to conditionally enable malicious behavior in other contexts.

### [low] warning emission

Finding ID: `NPS-609AC56D435E`

File: `dist-raw/node-internal-modules-esm-resolve.js`

The code emits deprecation warnings via process.emitWarning, which is not malicious but could be used to spam output.

### [low] Dynamic module loading

Finding ID: `NPS-A28C6F0B5D17`

File: `dist-raw/node-options.js:30`

The code uses require('arg') to load an external dependency. While 'arg' is a legitimate argument parsing library, this introduces a supply chain risk if the dependency is compromised.

### [low] Environment variable access

Finding ID: `NPS-2893037D233B`

File: `dist-raw/node-options.js:44`

The code reads process.env.NODE_OPTIONS and process.env.NODE_PENDING_DEPRECATION, which is expected for a Node.js options parser but could be leveraged to influence runtime behavior if an attacker controls these environment variables.

### [low] child process spawning

Finding ID: `NPS-8508D34934F0`

File: `dist/bin.js:38`

The code spawns child processes via callInChild for ESM support. This is a documented feature of ts-node and not a backdoor or reverse shell.

### [low] legitimate dynamic code execution

Finding ID: `NPS-84459502D551`

File: `dist/bin.js:582`

The code uses require() and Module.runMain() for normal CLI operation, which is expected for a Node.js runtime tool like ts-node; no malicious eval or obfuscated payloads are present.

### [low] Encoded payload decoding

Finding ID: `NPS-2EAC9CA824F3`

File: `dist/child/child-entrypoint.js:6`

The code reads a base64-encoded argument from process.argv[2] and decompresses it via decompress(). While this is a legitimate IPC mechanism used by ts-node to pass serialized state to child processes, encoded/decompressed payloads are commonly used in malware to hide intent and bypass static scanning. In this context the decoded data is used to reconstruct CLI state rather than execute hidden logic, but the pattern warrants attention.

### [low] Process argument manipulation

Finding ID: `NPS-92D3430F7763`

File: `dist/child/child-entrypoint.js:15`

The code rewrites process.argv[2] with a re-compressed payload when running as CLI, then proceeds to bootstrap via bin_1.bootstrap(state). This re-injects serialized state into subsequent child process invocations. If an attacker could control the initial payload (e.g., by influencing argv), the decompressed state would be trusted and used directly. No validation of the decompressed payload's schema or origin is performed.

### [low] Direct manipulation of internal Node.js process events

Finding ID: `NPS-817CAF1BF3B3`

File: `dist/child/child-require.js`

The code directly accesses and modifies the private `_process._events.warning` array/object instead of using the public API (`process.on`, `process.removeListener`). This is fragile, unsupported, and can break other listeners. It is not inherently malicious, but it is a code smell and could be used to silently suppress warnings that might alert a user to malicious behavior.

### [low] Dynamic module loading via require

Finding ID: `NPS-8A8D63CBAFB1`

File: `dist/cjs-resolve-hooks.js:7`

The code uses require('module') to import Node's built-in module system at runtime. While this is a legitimate use for accessing the loader internals, it indicates dynamic module loading behavior that should be considered within a broader supply-chain risk assessment.

### [low] Dynamic module loading

Finding ID: `NPS-33301E7A96BF`

File: `register/transpile-only.js:1`

The file requires '../' (a relative parent module) and invokes its .register() method at the top level. This executes code immediately upon import and can load arbitrary transpilation plugins depending on the parent module's implementation. While this is the standard pattern for ts-node/register/transpile-only.js, it demonstrates runtime code execution and dynamic module resolution that could be abused if the parent package is compromised or resolved unexpectedly.

### [low] Top-level code execution on import

Finding ID: `NPS-9F7693E21A53`

File: `register/transpile-only.js:1`

The require('../') call and subsequent .register() invocation run automatically when this file is imported, with no guard. This is a common entrypoint pattern for register hooks, but it means simply requiring this module triggers behavior (transpiler registration) without explicit user action.

### [low] Import-time execution

Finding ID: `NPS-1317570DCA12`

File: `register/type-check.js:1`

The file executes code at import time by calling require('../').register(...). While this pattern is typical for library registration hooks, it does invoke the package's register function immediately upon loading, which could execute arbitrary behavior depending on the implementation of the required module.

### [low] Dynamic module loading

Finding ID: `NPS-60809625E395`

File: `register/type-check.js:1`

The use of require('../') resolves a relative parent module dynamically at runtime. This is standard Node.js behavior, but the parent module's register implementation is not shown and could contain further logic. No obfuscation, external network calls, credential harvesting, or shell execution is present in this specific file.

## Files reviewed

- `child-loader.mjs` (medium): The file is a small ESM/CJS loader shim with no overt malicious behavior, but its use of dynamic require() for a dist module is a minor code-smell worth reviewing.
- `dist-raw/node-internal-modules-cjs-loader.js` (medium): The code is a modified copy of Node.js internal CJS loader used by ts-node; it exhibits complex module resolution and file system access typical of such tools, with no clear malicious patterns like exfiltration or backdoors.
- `dist-raw/node-internal-modules-esm-resolve.js` (medium): The code is a legitimate Node.js ES module resolver with no clear malicious intent, but it performs dynamic module resolution and file system access that could be abused if the environment or package.json files are compromised.
- `dist-raw/node-options.js` (medium): The code appears to be a legitimate reimplementation of Node.js internal options parsing, with no clear malicious patterns, though it does access environment variables and dynamically load a dependency.
- `dist-raw/runmain-hack.js` (medium): Uses dynamic import() with an externally supplied path, which could load and execute arbitrary code if the input is attacker-controlled.
- `dist/child/argv-payload.js` (medium): The file contains a utility for encoding/decoding arbitrary objects into command-line arguments via Brotli+base64, which is not inherently malicious but enables obfuscated payload smuggling and should be reviewed in the context of how it is used elsewhere in the package.
- `dist/child/child-entrypoint.js` (medium): This is a legitimate ts-node child-process entrypoint that decodes a base64 state payload from argv and bootstraps the CLI, but the encoded-payload pattern and unvalidated deserialization are worth noting even though no clear malicious behavior is present.
- `dist/child/child-require.js` (medium): The code safely manipulates Node.js process warning events to suppress specific experimental loader warnings; while not malicious, it uses private internals and hides security-relevant warnings, warranting caution.
- `dist/cjs-resolve-hooks.js` (medium): The file overrides Node.js internal CommonJS resolver functions (a gated, ts-node-style hook), which is legitimate but modifies module resolution behavior and warrants review of the calling context.
- `register/index.js` (medium): Top-level execution of a register() function from a parent module may hide malicious side effects that are not visible in this file.
- `register/transpile-only.js` (medium): This appears to be a legitimate ts-node-style register hook for transpile-only mode; the only concerns are the inherent top-level dynamic require and auto-registration, which are low-risk and expected for this package type.
- `register/type-check.js` (medium): The file contains only a benign registration call with no overt malicious patterns, though it does execute code at import time via a relative require.
- `dist-raw/node-internal-constants.js` (safe): Cleared by Jev triage; no further analysis needed
- `dist-raw/node-internal-errors.js` (safe): Cleared by Jev triage; no further analysis needed
- `dist-raw/node-internal-modules-cjs-helpers.js` (safe): No malicious patterns detected; the code is a legitimate copy of Node.js internal module helper for exposing built-in modules.
- `dist-raw/node-internal-modules-esm-get_format.js` (safe): No malicious patterns detected; the code is a direct copy of Node.js internal ESM get_format logic with benign module format resolution.
- `dist-raw/node-internal-modules-package_json_reader.js` (safe): No malicious patterns detected
- `dist-raw/node-internal-repl-await.js` (safe): No malicious patterns detected; this is a legitimate copy of Node.js internal REPL await transformation logic.
- `dist-raw/node-internalBinding-fs.js` (safe): Cleared by Jev triage; no further analysis needed
- `dist-raw/node-nativemodule.js` (safe): Cleared by Jev triage; no further analysis needed
- `dist-raw/node-primordials.js` (safe): Cleared by Jev triage; no further analysis needed
- `dist/bin-cwd.js` (safe): No malicious patterns detected in this thin wrapper that delegates to ./bin with a cwd mode flag.
- `dist/bin-esm.js` (safe): No malicious patterns detected
- `dist/bin-script-deprecated.js` (safe): No malicious patterns detected; the file is a simple deprecation notice that delegates to a local module.
- `dist/bin-script.js` (safe): No malicious patterns detected in the provided entry-point wrapper; it simply imports and invokes the package's main function.
- `dist/bin-transpile.js` (safe): No malicious patterns detected
- `dist/bin.js` (safe): The code is the legitimate ts-node CLI entry point; no malicious patterns such as data exfiltration, credential harvesting, obfuscation, or backdoors were detected.
- `dist/child/child-loader.js` (safe): This is a legitimate ESM loader child process module that proxies Node.js loader hooks; no malicious patterns detected.
- `dist/child/spawn-child.js` (safe): This is a legitimate ts-node internal utility for spawning a child Node.js process with inherited stdio and signal forwarding, containing no malicious patterns.
- `dist/configuration.js` (safe): No malicious patterns detected; the code is a legitimate ts-node configuration utility that reads and merges TypeScript config files without exfiltration, obfuscation, or suspicious process/network activity.
- `dist/esm.js` (safe): No malicious patterns detected; the code is a legitimate ts-node ESM loader implementation.
- `dist/file-extensions.js` (safe): No malicious patterns detected
- `dist/index.js` (safe): No malicious patterns detected; the code is the legitimate ts-node runtime with expected compiler, resolver, and loader-hook behavior.
- `dist/module-type-classifier.js` (safe): The file is a benign TypeScript module type classifier with no malicious patterns, network calls, code execution, or credential harvesting.
- `dist/node-module-type-classifier.js` (safe): No malicious patterns detected
- `dist/repl.js` (safe): No malicious patterns detected; the code implements a legitimate TypeScript REPL using vm.Script and Node's repl module as expected for ts-node.
- `dist/resolver-functions.js` (safe): No malicious patterns detected; the code implements TypeScript module resolution functions without any data exfiltration, credential harvesting, obfuscation, or other suspicious behaviors.
- `dist/transpilers/swc.js` (safe): No malicious patterns detected; the code is a legitimate SWC transpiler integration for ts-node that only performs dynamic module resolution of explicitly configured or default SWC compiler packages without exfiltration, obfuscation, or suspicious behavior.
- `dist/transpilers/types.js` (safe): No malicious patterns detected
- `dist/ts-compiler-types.js` (safe): No malicious patterns detected
- `dist/ts-internals.js` (safe): No malicious patterns detected; the code is a TypeScript compiler utility library with no data exfiltration, credential harvesting, obfuscation, or suspicious behavior.
- `dist/ts-transpile-module.js` (safe): No malicious patterns detected; the code is a legitimate TypeScript transpilation module wrapper with no external network, filesystem, or process manipulation.
- `dist/tsconfig-schema.js` (safe): No malicious patterns detected; the file is a trivial CommonJS module marker with only a source map reference.
- `dist/tsconfigs.js` (safe): No malicious patterns detected
- `dist/util.js` (safe): No malicious patterns detected; the code contains standard utility functions for module resolution, version comparison, and caching without any exfiltration, credential harvesting, obfuscation, or suspicious behavior.
- `esm.mjs` (safe): The file is a standard ESM compatibility shim that uses createRequire to load a local relative module and re-export its hooks; no malicious patterns detected.
- `esm/transpile-only.mjs` (safe): No malicious patterns detected; the file only sets up ESM loader hooks via a local module using createRequire.
- `register/files.js` (safe): The file only registers a 'files' option with the package's own dist module; no malicious patterns detected.
- `transpilers/swc-experimental.js` (safe): Cleared by Jev triage; no further analysis needed
- `transpilers/swc.js` (safe): Cleared by Jev triage; no further analysis needed

AI analysis is guidance, not a guarantee. Methodology: https://security.togoder.click/methodology
