GitHub Security Advisory: GHSA-v6wj-c83f-v46x
Package: @profullstack/mcp-server (NPM)
Severity: CRITICAL (CVSS 9.8)
CWE: CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
Vulnerable versions: <= 1.4.12
Patched in: unpatched

Summary: @profullstack/mcp-server vulnerable to OS Command Injection in domain_lookup Module

Details:
<html>
<body>
<!--StartFragment--><html><head></head><body><h1>Security Advisory: OS Command Injection in <code>profullstack/mcp-server</code> <code>domain_lookup</code> Module</h1>

Field | Value
-- | --
Project | profullstack/mcp-server
Repository | https://github.com/profullstack/mcp-server
Affected Commit | 2e8ea[PII_REDACTED]67ad54e31dba2e8198ebf7cf9
Affected Module | mcp_modules/domain_lookup
Affected Endpoints | POST /domain-lookup/check, POST /domain-lookup/bulk
Vulnerability Type | CWE-78: OS Command Injection
CVSS 3.1 Score | 9.8 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Authentication Required | None
Default Network Exposure | Bind address 0.0.0.0, no global authentication middleware
Validated | 2026-04-21 (initial), 2026-04-28 (re-confirmed)


<hr>
<h2>Summary</h2>
<p>The <code>domain_lookup</code> module assembles a shell command string by concatenating user-controlled input (<code>domains</code> / <code>keywords</code>) and passes it to <code>execAsync()</code>. Both HTTP endpoints reach the same sink. Because there is no argument quoting, escaping, or allowlist — and no authentication on the server — an unauthenticated remote attacker can execute arbitrary OS commands as the server process.</p>
<hr>
<h2>Affected Code</h2>
<ul>
<li><code>index.js:27</code> — server binds to <code>0.0.0.0</code>, no global auth middleware.</li>
<li><code>mcp_modules/domain_lookup/index.js:52</code> — registers <code>POST /domain-lookup/check</code>.</li>
<li><code>mcp_modules/domain_lookup/index.js:55</code> — registers <code>POST /domain-lookup/bulk</code>.</li>
<li><code>mcp_modules/domain_lookup/src/service.js:19, :20</code> — <code>buildTldxCommand()</code> concatenates user input into the shell string.</li>
<li><code>mcp_modules/domain_lookup/src/service.js:114, :115, :142</code> — <code>execAsync(command)</code> sink reached from both routes.</li>
</ul>
<hr>
<h2>Vulnerable Code</h2>
<p><strong>File:</strong> <code>mcp_modules/domain_lookup/src/service.js</code></p>
<p><strong>Step 1 — User input concatenated directly into a shell string:</strong></p>
<pre><code class="language-js">buildTldxCommand(keywords, options = {}) {
  let command = `tldx ${keywords.join(' ')}`;

  if (options.prefixes?.length) {
    command += ` --prefixes ${options.prefixes.join(',')}`;
  }
}
</code></pre>
<p><strong>Step 2 — That shell string is executed as-is:</strong></p>
<pre><code class="language-js">async checkDomainAvailability(domains, options = {}) {
  try {
    const command = this.buildTldxCommand(domains, options);
    const { stdout, stderr } = await execAsync(command);
</code></pre>
<p>There is no sanitization between Step 1 and Step 2. Shell metacharacters (<code>;</code>, <code>|</code>, <code>$()</code>, etc.) in user input are interpreted by <code>/bin/sh</code> at execution time.</p>
<hr>
<h2>Proof of Concept</h2>
<p>Tested against a local Docker build of the affected commit (<code>0.0.0.0:13000-&gt;3000/tcp</code>).</p>
<h3>PoC A — <code>POST /dom