---
summary: "SSRF guard for base_url overrides and a consent gate on destructive/side-effect ntfy_manage_message and ntfy_publish_message calls"
breaking: true
security: true
---

# 2.3.0 — 2026-07-29

## Added

- **`NTFY_BLOCK_PRIVATE_HOSTS`** (default `false`) — when set, a per-call `base_url` override that is not a registered server must resolve to a public address and its redirects are refused; registered `NTFY_SERVERS` / `NTFY_BASE_URL` entries are exempt. ([#6](https://github.com/cyanheads/ntfy-mcp-server/issues/6))
- **`base-url-guard.ts`** — `assertAbsoluteHttpUrl` (always on) and `assertPublicHost` (opt-in, behind the flag above) validate a `base_url` override before it reaches `fetch`. `BASE_URL_PATTERN` is advertised as the `pattern` on all three tools' `base_url` schema. `isBaseUrlRejection` (`error-classifier.ts`) lets `ntfy_publish_message` and `ntfy_fetch_messages` pass a locally-rejected override through untouched instead of relabeling it via the upstream keyword classifiers.
- **`confirmAction`** (`src/mcp-server/tools/utils/confirm-action.ts`) — a shared `ctx.elicit` consent gate. `ntfy_manage_message` prompts before every `clear`/`delete`; `ntfy_publish_message` prompts only when the input carries `email`, `call`, a `broadcast` action, or an `http` action, naming each target in the prompt. Declining, cancelling, or an unparseable response fails the call with a new `consent_declined` error reason. ([#13](https://github.com/cyanheads/ntfy-mcp-server/issues/13))

## Changed

- **`base_url`** on all three tools now enforces `BASE_URL_PATTERN` (`^$|^https?:\/\/\S+$`) at the schema level — a previously-accepted value like `ftp://…` or a bare hostname is now rejected before the call reaches `NtfyService`. ([#6](https://github.com/cyanheads/ntfy-mcp-server/issues/6))
- **`ntfy_manage_message`** and **`ntfy_publish_message`** calls carrying a destructive or out-of-band side effect now require user confirmation on clients that support MCP elicitation before the upstream call fires. ([#13](https://github.com/cyanheads/ntfy-mcp-server/issues/13))
- **`release:github`** added as a `package.json` script alias, matching the framework template.

## Security

- SSRF hardening on the per-call `base_url` override: with `NTFY_BLOCK_PRIVATE_HOSTS` set, the host is resolved and rejected if any address falls in a reserved range (IPv4 `0.0.0.0/8`, `10/8`, `100.64/10` — RFC 6598 mesh space such as Tailscale — `127/8`, `169.254/16`, `172.16/12`, `192.168/16`; IPv6 `::`, `::1`, `fc00::/7`, `fe80::/10`), and redirects are refused on that request so a public host cannot 302 into private space. Registered servers are exempt. **This is opt-in and off by default** — set the env var to enable it. **Not covered:** the guard does not pin the resolved address, so a DNS-rebinding time-of-check/time-of-use window remains between the guard's lookup and `fetch`'s own resolution; closing it needs a custom dispatcher this project has no dependency for. ([#6](https://github.com/cyanheads/ntfy-mcp-server/issues/6))
- The consent gate on destructive/side-effect calls is a STDIO guarantee only: the Streamable HTTP transport builds a fresh `McpServer` per request, so a client's elicitation capability never reaches the tool call and gated operations proceed on the tool annotations alone. Tracked upstream as [cyanheads/mcp-ts-core#312](https://github.com/cyanheads/mcp-ts-core/issues/312). ([#13](https://github.com/cyanheads/ntfy-mcp-server/issues/13))
