onboard: clearer security disclaimer, loading spinners, api key placeholder

This commit is contained in:
Patrick Erichsen
2026-04-20 19:38:46 -07:00
committed by Peter Steinberger
parent 49db424c80
commit 7752e3b30f
6 changed files with 65 additions and 35 deletions

View File

@@ -427,7 +427,13 @@ export async function promptDefaultModel(
const resolvedKey = modelKey(resolved.provider, resolved.model);
const configuredKey = configuredRaw ? resolvedKey : "";
const catalog = await loadModelCatalog({ config: cfg, useCache: false });
const catalogProgress = params.prompter.progress("Loading available models");
let catalog: Awaited<ReturnType<typeof loadModelCatalog>>;
try {
catalog = await loadModelCatalog({ config: cfg, useCache: false });
} finally {
catalogProgress.stop();
}
if (catalog.length === 0) {
return promptManualModel({
prompter: params.prompter,
@@ -532,6 +538,7 @@ export async function promptDefaultModel(
message: params.message ?? "Default model",
options,
initialValue,
searchable: true,
});
const selectedValue = selection ?? "";
if (selectedValue === KEEP_VALUE) {
@@ -603,7 +610,13 @@ export async function promptModelAllowlist(params: {
? initialSeeds.filter((key) => allowedKeySet.has(key))
: initialSeeds;
const catalog = await loadModelCatalog({ config: cfg, useCache: false });
const allowlistProgress = params.prompter.progress("Loading available models");
let catalog: Awaited<ReturnType<typeof loadModelCatalog>>;
try {
catalog = await loadModelCatalog({ config: cfg, useCache: false });
} finally {
allowlistProgress.stop();
}
if (catalog.length === 0 && allowedKeys.length === 0) {
const raw = await params.prompter.text({
message:

View File

@@ -214,6 +214,7 @@ export async function ensureApiKeyFromEnvOrPrompt(params: {
const key = await params.prompter.text({
message: params.promptMessage,
placeholder: "API key",
validate: params.validate,
});
const apiKey = params.normalize(key ?? "");

View File

@@ -186,5 +186,6 @@ export function note(message: string, title?: string) {
const columns = resolveNoteColumns(process.stdout.columns);
clackNote(wrapNoteMessage(message, { columns }), stylePromptTitle(title), {
output: createNoteOutput(columns),
format: (line) => line,
});
}

View File

@@ -0,0 +1,40 @@
import chalk from "chalk";
import { formatCliCommand } from "../cli/command-format.js";
import { theme } from "../terminal/theme.js";
export const SECURITY_NOTE_TITLE = "Security disclaimer";
export const SECURITY_CONFIRM_MESSAGE =
"I understand this is personal-by-default and shared/multi-user use requires lock-down. Continue?";
const heading = (text: string) => chalk.bold(text);
export const SECURITY_NOTE_MESSAGE = [
theme.warn("⚠ OpenClaw is in Beta - expect sharp edges"),
"- By default, OpenClaw is a personal agent: one trusted operator boundary.",
"- This bot can read files and run actions if tools are enabled.",
"- A bad prompt can trick it into doing unsafe things.",
"",
heading("How OpenClaw treats trust"),
"- OpenClaw is not a hostile multi-tenant boundary by default.",
"- If multiple users can message one tool-enabled agent, they share that delegated tool authority.",
"",
heading("When not to run OpenClaw"),
"- If youre not comfortable with security hardening and access control, dont run OpenClaw.",
"- Ask someone experienced to help before enabling tools or exposing it to the internet.",
"",
heading("Recommended baseline"),
"- Pairing/allowlists + mention gating.",
"- Multi-user/shared inbox: split trust boundaries (separate gateway/credentials, ideally separate OS users/hosts).",
"- Sandbox + least-privilege tools.",
"- Shared inboxes: isolate DM sessions (session.dmScope: per-channel-peer) and keep tool access minimal.",
"- Keep secrets out of the agents reachable filesystem.",
"- Use the strongest available model for any bot with tools or untrusted inboxes.",
"",
heading("Run regularly"),
formatCliCommand("openclaw security audit --deep"),
formatCliCommand("openclaw security audit --fix"),
"",
heading("Learn more"),
"- https://docs.openclaw.ai/gateway/security",
].join("\n");

View File

@@ -19,6 +19,11 @@ import { defaultRuntime } from "../runtime.js";
import { resolveUserPath } from "../utils.js";
import { WizardCancelledError, type WizardPrompter } from "./prompts.js";
import { resolveSetupSecretInputString } from "./setup.secret-input.js";
import {
SECURITY_CONFIRM_MESSAGE,
SECURITY_NOTE_MESSAGE,
SECURITY_NOTE_TITLE,
} from "./setup.security-note.js";
import type { QuickstartGatewayDefaults, WizardFlow } from "./setup.types.js";
type AuthChoiceModule = typeof import("../commands/auth-choice.js");
@@ -108,41 +113,10 @@ async function requireRiskAcknowledgement(params: {
return;
}
await params.prompter.note(
[
"Security warning — please read.",
"",
"OpenClaw is a hobby project and still in beta. Expect sharp edges.",
"By default, OpenClaw is a personal agent: one trusted operator boundary.",
"This bot can read files and run actions if tools are enabled.",
"A bad prompt can trick it into doing unsafe things.",
"",
"OpenClaw is not a hostile multi-tenant boundary by default.",
"If multiple users can message one tool-enabled agent, they share that delegated tool authority.",
"",
"If youre not comfortable with security hardening and access control, dont run OpenClaw.",
"Ask someone experienced to help before enabling tools or exposing it to the internet.",
"",
"Recommended baseline:",
"- Pairing/allowlists + mention gating.",
"- Multi-user/shared inbox: split trust boundaries (separate gateway/credentials, ideally separate OS users/hosts).",
"- Sandbox + least-privilege tools.",
"- Shared inboxes: isolate DM sessions (`session.dmScope: per-channel-peer`) and keep tool access minimal.",
"- Keep secrets out of the agents reachable filesystem.",
"- Use the strongest available model for any bot with tools or untrusted inboxes.",
"",
"Run regularly:",
"openclaw security audit --deep",
"openclaw security audit --fix",
"",
"Must read: https://docs.openclaw.ai/gateway/security",
].join("\n"),
"Security",
);
await params.prompter.note(SECURITY_NOTE_MESSAGE, SECURITY_NOTE_TITLE);
const ok = await params.prompter.confirm({
message:
"I understand this is personal-by-default and shared/multi-user use requires lock-down. Continue?",
message: SECURITY_CONFIRM_MESSAGE,
initialValue: false,
});
if (!ok) {