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 28
Dynamic module resolution with external input
NPS-B74BA9E48017
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.
dynamic module loading with external input
NPS-7A2BD5D24621
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.
dynamic import with computed input
NPS-DDC826B76E27
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.
Encoded payload in command-line arguments
NPS-C9EF49B9448D
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.
Suppression of security-relevant warnings
NPS-1DDD73BA8EE3
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.
Module loading hook installation
NPS-DDA8B16F2073
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.
Dynamic module loading / import-time side effect
NPS-BAAE871A199F
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.
Dynamic module loading
NPS-76A88418D2E1
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.
TODO comment indicating unresolved design
NPS-7E9E7FD38CD8
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.
Use of internal Node.js primordials
NPS-DB04E2652ABA
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.
Top-level code execution on import
NPS-2F73AAE77AEB
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.
File system access and stat caching
NPS-4B4EBF5FB0E9
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.
Potential for path traversal via package.json fields
NPS-D006034372AC
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.
file system access
NPS-1400FF7EC891
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.
environment-dependent behavior
NPS-2757B2828C2E
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.
warning emission
NPS-609AC56D435E
The code emits deprecation warnings via process.emitWarning, which is not malicious but could be used to spam output.
Dynamic module loading
NPS-A28C6F0B5D17
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.
Environment variable access
NPS-2893037D233B
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.
child process spawning
NPS-8508D34934F0
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.
legitimate dynamic code execution
NPS-84459502D551
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.
Encoded payload decoding
NPS-2EAC9CA824F3
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.
Process argument manipulation
NPS-92D3430F7763
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.
Direct manipulation of internal Node.js process events
NPS-817CAF1BF3B3
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.
Dynamic module loading via require
NPS-8A8D63CBAFB1
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.
Dynamic module loading
NPS-33301E7A96BF
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.
Top-level code execution on import
NPS-9F7693E21A53
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.
Import-time execution
NPS-1317570DCA12
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.
Dynamic module loading
NPS-60809625E395
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
| File | Verdict | What the reviewer saw |
|---|---|---|
| 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. |
Show 25 more files
| File | Verdict | What the reviewer saw |
|---|---|---|
| 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 |
Frequently asked questions
Is ts-node safe to use?
No confirmed malware was found in ts-node@10.9.2, but the review flagged 7 medium, 21 low severity findings for risky patterns worth checking before you rely on it.
Does ts-node contain malware?
No malware was identified in ts-node@10.9.2 when Togoder Security scanned it on Oct 4, 2026. A new version can still introduce malicious code, so scan the exact versions in your lockfile.
How was ts-node checked?
Togoder Security downloaded the published npm package and had an AI model read its 50 source files, looking for install scripts, credential access, network exfiltration, obfuscation, backdoors and crypto-wallet theft. The results are cached by file hash and shown here.
How do I scan ts-node together with the rest of my dependencies?
Upload your lockfile at https://security.togoder.click/scan or call the API documented at https://security.togoder.click/api-docs. Files that have already been scanned, like the ones in ts-node@10.9.2, cost nothing.