May 9, 2026·7 min read·3 visits
The `@profullstack/mcp-server` package (versions <= 1.4.12) is vulnerable to unauthenticated OS Command Injection. The `domain_lookup` module unsafely concatenates user-supplied input into a shell command, enabling remote code execution.
A critical unauthenticated OS Command Injection vulnerability (CWE-78) exists in the `@profullstack/mcp-server` npm package, specifically within the `domain_lookup` module. The vulnerability allows remote attackers to execute arbitrary commands on the host system via crafted HTTP requests.
The @profullstack/mcp-server package provides a modular server architecture for the Model Context Protocol (MCP). The software utilizes various modules to extend functionality. The domain_lookup module, designed to check domain availability, relies on the external tldx command-line utility to perform these queries.
The vulnerability manifests as a severe OS Command Injection (CWE-78) flaw within this module. The application exposes unauthenticated HTTP POST endpoints (/domain-lookup/check and /domain-lookup/bulk) that accept JSON payloads. These payloads contain user-supplied arrays of domain names or search keywords.
The server fails to sanitize or validate these inputs before processing them. The unvalidated inputs propagate directly to the underlying operating system shell, creating a highly permissive attack surface. Any network-adjacent or remote attacker capable of routing HTTP traffic to the application port (default 3000) can exploit this condition.
The root cause of this vulnerability lies in the improper handling of user input during shell command construction within mcp_modules/domain_lookup/src/service.js. The buildTldxCommand function is responsible for assembling the command string passed to the operating system.
The function constructs this shell command by directly concatenating user-supplied input (domains or keywords) into a single string. It utilizes JavaScript template literals and the .join(' ') method to append the array contents directly after the tldx binary invocation. The software performs no input validation, escaping, or parameterization on this data prior to concatenation.
Following construction, the application passes this unsanitized string to execAsync(). In Node.js, child_process.exec (and its asynchronous wrappers) spawns a system shell (/bin/sh on Unix-like systems, cmd.exe on Windows) to parse and execute the command string. Because the string is interpreted by a shell environment, any included shell metacharacters are evaluated according to the shell's syntactic rules.
This architectural pattern violates the principle of secure command invocation. By utilizing a shell interpreter rather than passing arguments directly to the binary executable via an array, the software allows the input context to shift from data to executable control instructions.
The vulnerable code path initiates in the buildTldxCommand function. The function takes the user-provided array, joins the elements with spaces, and interpolates them directly into the shell command string.
// Vulnerable Implementation
buildTldxCommand(keywords, options = {}) {
// Direct concatenation of user input 'keywords' into the command string
let command = `tldx ${keywords.join(' ')}`;
if (options.prefixes?.length) {
command += ` --prefixes ${options.prefixes.join(',')}`;
}
return command;
}The constructed command string is subsequently passed to the checkDomainAvailability method. This method acts as the execution sink, invoking execAsync(command). Because the string contains user-controlled metacharacters, the underlying shell interprets them as secondary commands rather than literal arguments to tldx.
// Execution Sink
async checkDomainAvailability(domains, options = {}) {
try {
const command = this.buildTldxCommand(domains, options);
// Sink: execAsync interprets shell metacharacters
const { stdout, stderr } = await execAsync(command);
// ...The fundamental fix requires decoupling the user input from shell evaluation. The remediation replaces child_process.exec with child_process.spawn or child_process.execFile. These functions accept the executable path as a discrete argument and the parameters as a dedicated array. The operating system handles argument passing directly to the target process, bypassing shell tokenization entirely.
// Recommended Remediation
const { spawn } = require('child_process');
async checkDomainAvailability(domains, options = {}) {
try {
// Bypasses shell interpretation by passing arguments as an array
const child = spawn('tldx', domains);
// ... handle stdout/stderr eventsExploitation requires no authentication and minimal network prerequisites. An attacker only needs the ability to send HTTP POST requests to an exposed instance of @profullstack/mcp-server. The server commonly binds to 0.0.0.0 on port 3000, making it accessible across local networks or the public internet depending on deployment configurations.
The attack vector involves crafting a specific JSON payload directed at either /domain-lookup/check or /domain-lookup/bulk. The attacker embeds shell control operators (such as ;, |, &, or $()) within the domains or keywords array. These operators instruct the shell environment to terminate the preceding tldx command and begin executing the injected instruction sequence.
The following proof-of-concept demonstrates exploitation via the /domain-lookup/check endpoint. The payload terminates the intended domain lookup with a semicolon, executes the echo command to write a file to the /tmp directory, and neutralizes any trailing application-appended arguments using a hash character (#) as a shell comment.
curl -X POST http://<target>:3000/domain-lookup/check \
-H 'Content-Type: application/json' \
-d '{"domains":["example.com; echo RCE_CONFIRMED > /tmp/rce.txt; #"]}'Execution of this payload results in the application constructing the command string tldx example.com; echo RCE_CONFIRMED > /tmp/rce.txt; #. The /bin/sh interpreter parses this string sequentially, launching tldx against example.com, followed immediately by the attacker's echo command. The server process executes the injected command with the privileges of the Node.js runtime environment.
Successful exploitation of this vulnerability yields full, unauthenticated Remote Code Execution (RCE) on the host operating system. The attacker executes commands within the context and privilege level of the user account running the Node.js application process.
The vulnerability compromises the confidentiality of the system. An attacker can read arbitrary files accessible to the application user, including source code, configuration files, environment variables, database credentials, and cryptographic keys. This data exfiltration enables extensive lateral movement across connected network environments.
Integrity and availability are similarly compromised. The attacker possesses the capability to modify system state, alter application data, install persistent backdoor mechanisms, or deploy secondary malware payloads. Furthermore, the attacker can terminate the server process entirely or consume system resources to induce a denial-of-service condition.
The CVSS v3.1 base score of 9.8 reflects the severity of this flaw. The metrics confirm a network-based attack vector, low attack complexity, no required privileges, and no requisite user interaction. The lack of authentication combined with arbitrary code execution represents the maximum theoretical impact for a software vulnerability.
Organizations utilizing @profullstack/mcp-server must immediately upgrade their deployments to a patched version once released by the maintainers. Vulnerable versions encompass all releases up to and including 1.4.12. Administrators should monitor package repositories for the official update addressing GHSA-v6wj-c83f-v46x.
Developers must replace instances of child_process.exec or execAsync with child_process.spawn or child_process.execFile when handling externally influenced data. Passing arguments as an array directly to the system kernel neutralizes command injection attacks, as the input is never evaluated by a shell interpreter. This architectural change eliminates the CWE-78 vulnerability class entirely for this code path.
Applications should implement strict input validation on all user-supplied data. For domain names, developers should enforce a strict allowlist regex conforming to RFC 1035, permitting only alphanumeric characters, dots, and hyphens. Rejecting any input containing shell metacharacters provides a strong defense-in-depth measure.
Administrators should enforce the Principle of Least Privilege by running the Node.js process as a dedicated, unprivileged service account rather than root. Additionally, implementing global authentication middleware restricts access to the modular endpoints, drastically reducing the accessible attack surface.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
@profullstack/mcp-server profullstack | <= 1.4.12 | - |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-78 |
| Attack Vector | Network |
| CVSS Score | 9.8 (Critical) |
| Impact | Arbitrary Remote Code Execution |
| Exploit Status | Proof of Concept Available |
| Privileges Required | None |
The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.