/** * @fileoverview Validation for the per-call `base_url` override. Two layers, * deliberately split: * * - **Always on** — `assertAbsoluteHttpUrl` parses the override and rejects * anything that is not an absolute `http:`/`https:` URL. The advertised * contract is "absolute URL", so a malformed value fails at the boundary * with an actionable message instead of surfacing as an opaque `fetch` * error several layers down. * - **Opt-in (`NTFY_BLOCK_PRIVATE_HOSTS`)** — `assertPublicHost` resolves the * host and rejects loopback, private, link-local, and unspecified * addresses. Off by default so stdio and homelab deployments that point at * a LAN ntfy keep working; on for operators exposing the server as a public * HTTP utility, where a model-supplied `base_url` is an SSRF vector. * * @module services/ntfy/base-url-guard */ /** * Pattern advertised as `pattern` on every tool's `base_url` field, so the * absolute-URL requirement is machine-readable rather than prose-only. One * regex node (not a union) keeps a bad value on a single error message instead * of an `invalid_union` dump. The empty-string branch is for form-based * clients that submit `""` for an untouched optional field; handlers read that * as "no override". */ export declare const BASE_URL_PATTERN: RegExp; /** Recovery hint shared by every `base_url` rejection. */ export declare const BASE_URL_HINT = "Pass an absolute `http://` or `https://` URL (e.g. `https://ntfy.example.com`), or omit `base_url` to use the configured server."; /** * Turn a tool's raw `base_url` argument into the override `NtfyService` * expects: the empty string a form-based client submits for an untouched * optional field means "no override", not a base URL of `""`. Trailing slashes * come off here as well as in the service, because a handler renders the * override into the canonical topic URL it returns. */ export declare function normalizeBaseOverride(raw: string | undefined): string | undefined; /** * Marks a `base_url` rejection as locally raised rather than reported by ntfy. * Tool handlers classify upstream failures by code, and a local * `ValidationError` would otherwise be read as an upstream complaint about some * other argument — `isBaseUrlRejection` in `error-classifier.ts` is how they * tell the two apart. `NtfyService` stamps its redirect refusal with it too. */ export declare const REJECTION_MARKER: { readonly baseUrlRejected: true; }; /** * True when the literal address falls in a reserved range. * * IPv4-mapped IPv6 is handled by `BlockList` itself: it matches a mapped * address against the IPv4 rules in either textual form (`::ffff:127.0.0.1` * and `::ffff:7f00:1` are the same address, and WHATWG URL parsing emits only * the second), while leaving mapped *public* addresses alone — so there is no * need to block all of `::ffff:0:0/96`. The mapped cases in * `base-url-guard.test.ts` pin that behavior. */ export declare function isReservedAddress(address: string): boolean; /** * Parse a `base_url` override and enforce the `http:`/`https:` scheme. * Unconditional — a relative or non-HTTP value can never reach a working ntfy * server, so it is a caller mistake worth naming precisely. */ export declare function assertAbsoluteHttpUrl(raw: string): URL; /** * Reject a host that resolves to a non-public address. Every resolved address * is checked, not just the first, so a hostname with one private A record * among several cannot slip through. * * **Residual time-of-check/time-of-use window.** The resolution here is not * the one `fetch` performs: a hostname whose DNS answer changes between the * two calls (DNS rebinding) can still be dereferenced. Closing it needs the * connection pinned to the address validated here — a custom dispatcher this * server has no dependency for, and Bun's `fetch` accepts none. Redirects are * blocked separately on this path (`redirect: 'error'` in `NtfyService`), which * covers the far easier variant of the same escape. */ export declare function assertPublicHost(url: URL): Promise; //# sourceMappingURL=base-url-guard.d.ts.map