mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 00:36:35 +00:00
Require operator-declared hostnames before serving MCP HTTP outside loopback, preserving Host/Origin gates. Parse hostname identities once, share typed startup recovery across CLI/MCP, and announce an admitted client URL. Generated AgentEnv bundles declare their client/service names and validate DNS/HTTP name identity before export. Validated through real packaged HTTP requests, direct SDK connection, compiled export refusals, 184 focused tests, static and documentation checks on the refreshed main integration. Final CI and review evidence is recorded in the PR body. BREAKING CHANGE: non-loopback deployments require MCP_ALLOWED_HOSTS or rea mcp --http --allowed-hosts; bundle names must be unchanged HTTP hostnames within the 63-octet DNS label bound. Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
38 lines
1.2 KiB
TypeScript
38 lines
1.2 KiB
TypeScript
import { isIPv6 } from "node:net";
|
|
import { z } from "zod";
|
|
|
|
/** Parse a complete hostname/IP, never URL components, into the SDK's hostname identity. */
|
|
export const normalizeHttpHostname = (host: string): string | undefined => {
|
|
const unbracketed =
|
|
host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
const ipv6 = isIPv6(unbracketed);
|
|
// URL parsing alone discards userinfo, paths and default ports, and decodes escapes.
|
|
if (
|
|
/[\s\x00-\x1F\x7F%\x2F\x5C?#@]/u.test(host) ||
|
|
(!ipv6 && host.includes(":"))
|
|
)
|
|
return undefined;
|
|
try {
|
|
return new URL(`http://${ipv6 ? `[${unbracketed}]` : host}`).hostname;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
/** Canonical HTTP hostname identity, parsed before consumers use it for lookup or matching. */
|
|
export const httpHostnameSchema = z
|
|
.string()
|
|
.transform((source, context) => {
|
|
const hostname = normalizeHttpHostname(source);
|
|
if (hostname !== undefined) return hostname;
|
|
context.addIssue({
|
|
code: "custom",
|
|
message:
|
|
"Expected a host name or IP address without URL components or ports",
|
|
});
|
|
return z.NEVER;
|
|
})
|
|
.brand<"HttpHostname">();
|
|
|
|
export type HttpHostname = z.infer<typeof httpHostnameSchema>;
|