Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions apps/kimi-code/src/cli/sub/server/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export interface EnsureDaemonOptions {
allowRemoteShutdown?: boolean;
/** Keep the PTY `/api/v1/terminals/*` routes enabled on a non-loopback bind. */
allowRemoteTerminals?: boolean;
/** Disable bearer-token auth on every route (`--dangerous-bypass-auth`). */
dangerousBypassAuth?: boolean;
/** Keep the daemon alive instead of idle-killing it (`--keep-alive`). */
keepAlive?: boolean;
/** Extra `Host` header values to allow through the DNS-rebinding check. */
allowedHosts?: readonly string[];
/** Idle-shutdown grace in ms for the spawned daemon (daemon mode only). */
Expand Down Expand Up @@ -202,6 +206,8 @@ interface SpawnDaemonChildOptions {
insecureNoTls?: boolean;
allowRemoteShutdown?: boolean;
allowRemoteTerminals?: boolean;
dangerousBypassAuth?: boolean;
keepAlive?: boolean;
allowedHosts?: readonly string[];
idleGraceMs?: number;
}
Expand Down Expand Up @@ -235,6 +241,12 @@ export function spawnDaemonChild(options: SpawnDaemonChildOptions): ChildProcess
if (options.allowRemoteTerminals === true) {
args.push('--allow-remote-terminals');
}
if (options.dangerousBypassAuth === true) {
args.push('--dangerous-bypass-auth');
}
if (options.keepAlive === true) {
args.push('--keep-alive');
}
if (options.idleGraceMs !== undefined) {
args.push('--idle-grace-ms', String(options.idleGraceMs));
}
Expand Down Expand Up @@ -320,6 +332,8 @@ export async function ensureDaemon(options: EnsureDaemonOptions = {}): Promise<E
insecureNoTls: options.insecureNoTls,
allowRemoteShutdown: options.allowRemoteShutdown,
allowRemoteTerminals: options.allowRemoteTerminals,
dangerousBypassAuth: options.dangerousBypassAuth,
keepAlive: options.keepAlive,
allowedHosts: options.allowedHosts,
idleGraceMs: options.idleGraceMs,
});
Expand Down
96 changes: 80 additions & 16 deletions apps/kimi-code/src/cli/sub/server/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ export function buildRunCommand(cmd: Command, options: { defaultOpen: boolean })
'--allowed-host <host...>',
'Extra Host header value to allow through the DNS-rebinding check. Repeat or comma-separate; a leading dot matches a domain suffix (e.g. .example.com).',
)
.option(
'--keep-alive',
'Keep the server running instead of exiting after 60s with no connected clients. Implied automatically by --host / --allowed-host, and always on in --foreground mode.',
false,
)
.option(
'--insecure-no-tls',
'Allow a non-loopback bind without a TLS-terminating reverse proxy. Defaults to true; only relevant for non-loopback binds.',
Expand All @@ -134,6 +139,11 @@ export function buildRunCommand(cmd: Command, options: { defaultOpen: boolean })
'On a non-loopback bind, keep the PTY /api/v1/terminals/* routes enabled (default: disabled → 404). Remote shell is high risk.',
false,
)
.option(
'--dangerous-bypass-auth',
'Disable bearer-token auth on every REST and WebSocket route, and advertise it via /api/v1/meta so the web UI connects without a token. Only use on a trusted network or behind your own authenticating proxy.',
false,
)
.option(
'--log-level <level>',
`Server log level: ${VALID_LOG_LEVELS.join('|')}. Omit to keep logs off.`,
Expand Down Expand Up @@ -183,13 +193,27 @@ export async function handleRunCommand(
await startServerDaemon(parsed);
return;
}
// Foreground is always keep-alive: a server attached to the operator's
// terminal must never idle-kill itself. Background daemons respect the
// derived `--keep-alive` flag.
const runOptions: ParsedServerOptions =
opts.foreground === true ? { ...parsed, keepAlive: true } : parsed;
// Resolve the persistent token once: it is printed in the ready banner and
// rides in the opened Web UI URL's `#token=` fragment (M5.5). Falls back to
// the plain origin / no token line when unavailable.
// the plain origin / no token line when unavailable. When auth is bypassed,
// the token is meaningless and is intentionally NOT shown or carried in the
// opened URL.
const writeReady = (result: { origin: string; reused?: boolean; host?: string }): void => {
const { origin } = result;
const host = result.host ?? parsed.host;
const token = deps.resolveToken?.();
// When a daemon is reused, this command's flags were NOT applied to the
// already-running server. Don't trust the requested `--dangerous-bypass-auth`
// for display/open: treat the server as token-protected so we never hide a
// token the user actually needs, nor claim bypass for a server that is
// authenticating. (Probing the running server's `/meta` would give its real
// mode; we conservatively assume non-bypass on reuse.)
const effectiveBypass = result.reused === true ? false : parsed.dangerousBypassAuth;
const token = effectiveBypass ? undefined : deps.resolveToken?.();
let output = '';
if (result.reused === true) {
// A daemon was already running, so this command's --host/--port/etc. did
Expand All @@ -202,23 +226,24 @@ export async function handleRunCommand(
? formatReadyBanner(origin, host, {
token,
networkAddresses: deps.networkAddresses,
dangerousBypassAuth: effectiveBypass,
})
: formatReadyLine(origin, token);
: formatReadyLine(origin, token, effectiveBypass);
deps.stdout.write(output);
if (opts.open === true) {
deps.openUrl(token !== undefined ? buildWebUrl(origin, token) : origin);
}
};
if (opts.foreground === true) {
const run = deps.startServerForeground ?? startServerForeground;
await run(parsed, {
await run(runOptions, {
onReady: (origin) => {
writeReady({ origin, reused: false, host: parsed.host });
},
});
return;
}
const result = await deps.startServerBackground(parsed);
const result = await deps.startServerBackground(runOptions);
writeReady(result);
}

Expand All @@ -230,8 +255,30 @@ function formatReuseNotice(origin: string): string {
);
}

function formatReadyLine(origin: string, token: string | undefined): string {
return `Kimi server: ${buildOpenableUrl(origin, token)}\n`;
function formatReadyLine(
origin: string,
token: string | undefined,
dangerousBypassAuth = false,
): string {
const notice = dangerousBypassAuth
? `${formatDangerNoticeLines().join('\n')}\n`
: '';
return `${notice}Kimi server: ${buildOpenableUrl(origin, token)}\n`;
}

/**
* Red, impossible-to-miss notice emitted when `--dangerous-bypass-auth`
* disables the bearer-token gate. Shared by the full ready banner and the
* compact one-line output so the warning always shows regardless of log level.
*/
function formatDangerNoticeLines(): string[] {
const danger = (text: string): string => chalk.hex(darkColors.error)(text);
const dangerBold = (text: string): string => chalk.bold.hex(darkColors.error)(text);
return [
` ${dangerBold('⚠ DANGER: authentication is DISABLED (--dangerous-bypass-auth).')}`,
` ${danger('Anyone who can reach this port gets full access. Only continue if you understand the risk.')}`,
` ${danger(`If you are unsure, run `)}${dangerBold('kimi server kill')}${danger(' now to stop this process.')}`,
];
}

/**
Expand All @@ -251,6 +298,8 @@ export async function startServerBackground(
insecureNoTls: options.insecureNoTls,
allowRemoteShutdown: options.allowRemoteShutdown,
allowRemoteTerminals: options.allowRemoteTerminals,
dangerousBypassAuth: options.dangerousBypassAuth,
keepAlive: options.keepAlive,
allowedHosts: options.allowedHosts,
idleGraceMs: options.idleGraceMs,
});
Expand Down Expand Up @@ -296,14 +345,18 @@ async function runServerInProcess(
let running: RunningServer | undefined;
let stopping = false;

const idle = mode.daemon
? createIdleShutdownHandler({
graceMs: options.idleGraceMs,
onIdle: () => {
void shutdown('idle');
},
})
: undefined;
// Idle auto-shutdown is only for the on-demand personal daemon. It is skipped
// in foreground mode (`mode.daemon` is false) and whenever `--keep-alive` is
// set — explicitly, or implied by `--host` / `--allowed-host`.
const idle =
mode.daemon && !options.keepAlive
? createIdleShutdownHandler({
graceMs: options.idleGraceMs,
onIdle: () => {
void shutdown('idle');
},
})
: undefined;

async function shutdown(reason: string): Promise<void> {
if (stopping) return;
Expand All @@ -330,6 +383,7 @@ async function runServerInProcess(
insecureNoTls: options.insecureNoTls,
allowRemoteShutdown: options.allowRemoteShutdown,
allowRemoteTerminals: options.allowRemoteTerminals,
dangerousBypassAuth: options.dangerousBypassAuth,
allowedHosts: options.allowedHosts,
webAssetsDir: serverWebAssetsDir(),
coreProcessOptions: {
Expand All @@ -356,7 +410,9 @@ async function runServerInProcess(
});

const readyFields = mode.daemon
? { address: running.address, idleGraceMs: options.idleGraceMs }
? options.keepAlive
? { address: running.address, idleShutdown: 'disabled' as const }
: { address: running.address, idleGraceMs: options.idleGraceMs }
: { address: running.address };
running.logger.info(readyFields, mode.daemon ? 'daemon ready' : 'server ready');

Expand Down Expand Up @@ -421,6 +477,8 @@ interface FormatReadyBannerOptions {
token?: string;
/** Non-loopback interface addresses to list for a wildcard bind. */
networkAddresses?: NetworkAddress[];
/** When true, render a red danger notice (auth is disabled). */
dangerousBypassAuth?: boolean;
}

function formatReadyBanner(
Expand Down Expand Up @@ -452,6 +510,12 @@ function formatReadyBanner(
'',
];

if (opts.dangerousBypassAuth === true) {
// Red, impossible-to-miss notice: the bearer-token gate is off, so anyone
// who can reach this port gets full session / filesystem / shell access.
lines.push(...formatDangerNoticeLines(), '');
}

// Access links.
for (const { label: text, url: href } of accessUrlLines(
host,
Expand Down
27 changes: 25 additions & 2 deletions apps/kimi-code/src/cli/sub/server/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,18 @@ export interface ParsedServerOptions {
allowRemoteShutdown: boolean;
/** Allow PTY `/api/v1/terminals/*` routes on a non-loopback bind. */
allowRemoteTerminals: boolean;
/** Disable bearer-token auth on every route (`--dangerous-bypass-auth`). */
dangerousBypassAuth: boolean;
/** Extra `Host` header values to allow through the DNS-rebinding check. */
allowedHosts: readonly string[];
/**
* Keep the server running instead of idle-killing it after 60s with no
* connected clients (`--keep-alive`). Also implied automatically by a
* non-default bind (`--host`) or a proxy/tunnel setup (`--allowed-host`),
* and always on in `--foreground` mode. Only the daemon mode consults this —
* foreground never idle-kills regardless.
*/
keepAlive: boolean;
/** Internal: run as an idle-exiting background daemon instead of foreground. */
daemon: boolean;
/** Internal: idle-shutdown grace in ms (daemon mode only). */
Expand All @@ -69,24 +79,37 @@ export interface ServerCliOptions {
allowRemoteShutdown?: boolean;
/** Allow remote terminals on a non-loopback bind (`--allow-remote-terminals`). */
allowRemoteTerminals?: boolean;
/** Disable bearer-token auth on every route (`--dangerous-bypass-auth`). */
dangerousBypassAuth?: boolean;
/** Extra `Host` header values to allow (`--allowed-host`). */
allowedHost?: string[];
/** Keep the server running instead of idle-killing it (`--keep-alive`). */
keepAlive?: boolean;
/** Internal flag set by the daemon spawner (`kimi web`). */
daemon?: boolean;
/** Internal flag set by the daemon spawner / tests. */
idleGraceMs?: string;
}

export function parseServerOptions(opts: ServerCliOptions): ParsedServerOptions {
const host = parseHost(opts.host);
const allowedHosts = parseAllowedHostArgs(opts.allowedHost);
// `--keep-alive` is explicit, but also implied by a non-default bind
// (`--host`) or a proxy/tunnel setup (`--allowed-host`). Foreground mode is
// forced keep-alive later in `handleRunCommand`.
const keepAlive =
opts.keepAlive === true || host !== DEFAULT_SERVER_HOST || allowedHosts.length > 0;
return {
host: parseHost(opts.host),
host,
port: parsePort(opts.port, '--port', DEFAULT_SERVER_PORT),
logLevel: parseLogLevel(opts.logLevel ?? DEFAULT_FOREGROUND_LOG_LEVEL),
debugEndpoints: opts.debugEndpoints === true,
insecureNoTls: opts.insecureNoTls !== false,
allowRemoteShutdown: opts.allowRemoteShutdown === true,
allowRemoteTerminals: opts.allowRemoteTerminals === true,
allowedHosts: parseAllowedHostArgs(opts.allowedHost),
dangerousBypassAuth: opts.dangerousBypassAuth === true,
allowedHosts,
keepAlive,
daemon: opts.daemon === true,
idleGraceMs: parseIdleGraceMs(opts.idleGraceMs),
};
Expand Down
Loading
Loading