mcp-server-kubernetes
Version:
MCP server for interacting with Kubernetes clusters via kubectl
91 lines (90 loc) • 5.14 kB
TypeScript
import { type ExecFileSyncOptionsWithStringEncoding } from "child_process";
/**
* Validate user-supplied kubectl flags and args. Throws an McpError if any
* dangerous flag is present and the unsafe-flags escape hatch is not set.
*
* The check covers:
* - keys of the `flags` object (e.g. { server: "..." })
* - tokens in the `args` array, in both joined ("--server=x") and split
* ("--server", "x") forms, plus short aliases ("-s").
*/
export declare function assertNoDangerousFlags(flags?: Record<string, unknown>, args?: string[]): void;
/**
* Validate a fully-constructed kubectl/helm argv. Unlike assertNoDangerousFlags
* (which inspects only the free-form `flags`/`args` inputs of kubectl_generic),
* this scans every token in the final argv — including bare positional slots
* such as resource names, node names, and resource types that the individual
* tools push directly. kubectl's pflag parser treats any token beginning with
* "-" as a flag regardless of position, so a tool argument like
* name: "--server=https://attacker" would otherwise redirect the API server
* and leak the operator's bearer token. Throws an McpError on any dangerous
* flag unless ALLOW_KUBECTL_UNSAFE_FLAGS=true.
*/
export declare function assertSafeArgv(args: readonly string[]): void;
/**
* Reject flags that make kubectl read a file from the MCP server's filesystem,
* on remote transports only. No-op under stdio.
*
* kubectl_generic lets a caller assemble an arbitrary kubectl argv, so the
* per-parameter guards the structured tools use (`filename`, `fromFile`,
* `patchFile`, `valuesFile`) have nothing to attach to: the same read is
* reachable as a raw "--from-file=leak=/etc/passwd" token. Under stdio that is
* the operator reading their own files and stays allowed; over SSE/Streamable
* HTTP the path comes from whoever can reach the endpoint but resolves on the
* server host, turning kubectl into an arbitrary file-read oracle (kubeconfig,
* service-account token, /proc/self/environ) — with --dry-run=client, without
* even a cluster.
*
* Takes a fully-constructed argv rather than the `flags`/`args` inputs so that
* flags built from the object form and file-shaped values smuggled through
* positional slots (resourceType, name) are covered by the same scan. Unlike
* assertNoDangerousFlags there is no ALLOW_KUBECTL_UNSAFE_FLAGS escape hatch:
* this mirrors the transport guards in the structured tools, which reject
* server-side paths unconditionally.
*/
export declare function assertNoRemoteFileReads(args: readonly string[]): void;
/**
* Reject a caller-supplied value that a tool is about to place in a *positional*
* argv slot (a resource type, a resource name, a release name, a chart
* reference, a namespace) when it is shaped like a command-line flag.
*
* assertSafeArgv is a deny-list: it can only refuse the flags it knows about,
* so every dangerous flag added upstream is exploitable through any positional
* slot until the list is extended. This guard closes the injection point
* instead of chasing the flags: pflag treats any token starting with "-" as a
* flag no matter where it sits, and none of these operands is ever legitimately
* flag-shaped (Kubernetes resource types and names, helm release names and
* namespaces are RFC 1123 names; chart references are repo/name pairs, paths
* or URLs). Refusing a leading "-" here means caller data can never occupy a
* slot where it would be parsed as a flag.
*
* Honours the same ALLOW_KUBECTL_UNSAFE_FLAGS escape hatch as the flag guard.
*/
export declare function assertNotFlagLike(value: string | undefined, field: string): void;
/**
* Apply kubectl_generic's verb policy to a fully-constructed argv, whose first
* token is the verb.
*
* The flag guards above can only see dash-prefixed tokens, but some kubectl
* verbs reach the same assets through plain operands: "config view --raw"
* prints the operator's credentials, "config set clusters.x.server <url>"
* persistently redirects the kubeconfig, "cp" reads and writes local paths
* given as positionals. Those are matched on the verb instead.
*
* kubectl cp resolves any operand without a ":" on the local machine. Under
* stdio a local *source* is the operator reading their own file, the same as
* "-f", and stays allowed; a local *destination* is a write to a path chosen
* by whoever steers the tool arguments, like --log-file, and is refused. Over
* a remote transport both sides would be the server host, so cp is refused
* outright.
*
* The remote-only checks have no escape hatch, matching
* assertNoRemoteFileReads; the rest honour ALLOW_KUBECTL_UNSAFE_FLAGS.
*/
export declare function assertAllowedGenericCommand(args: readonly string[]): void;
/**
* Drop-in replacement for child_process.execFileSync that scans the argv for
* credential/target-redirecting flags before executing. Tool files import this
* as `execFileSync`, so every kubectl/helm call site is guarded at one place.
*/
export declare function execFileSyncSafe(file: string, args: string[], options: ExecFileSyncOptionsWithStringEncoding): string;