fix(onboarding): mask credential inputs in interactive wizard prompts#76615
fix(onboarding): mask credential inputs in interactive wizard prompts#76615anurag-bg-neu wants to merge 4 commits into
Conversation
|
Codex review: needs maintainer review before merge. Summary Reproducibility: yes. Source inspection on current main shows credential onboarding paths call Next step before merge Security Review detailsBest possible solution: Merge the sensitive prompt plumbing after the macOS Swift lint/build/test lane and targeted onboarding tests are green or proven unrelated. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection on current main shows credential onboarding paths call Is this the best way to solve the issue? Yes, pending CI. The PR uses the existing Acceptance criteria:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 8142e67d633d. |
|
Thanks for the careful review. New commit [P2] Preserve masked prompt defaults —
|
…irm; mask macOS wizard CLI
…char credential truncation
bd38ccf to
6274de3
Compare
|
Pushed [P2] Avoid truncating macOS wizard credentials —
|
…ithPrompt pattern
|
Pushed The 22-second failure window is consistent with
Verification gap is the same: I authored this PR on Windows and don't have a Swift toolchain locally, so I'm reading the failure shape from the workflow file rather than from the actual error text. If Will follow up with the Windows PowerShell |
|
Closing in favor of a focused replacement at #76693. The technical content for the Node-side masking is equivalent — same diff on clawsweeper findings — status in #76693:
Appreciate the patient review on this thread. |
Summary
openclaw onboardwizard were echoed in cleartext into the terminal stream — leaving secrets in PowerShell scrollback,Start-Transcriptlogs, screenshots, screen-shares, and shoulder-surfing range. Reproduced on Windows PowerShell during a normalopenclaw onboard --install-daemonsession (Gemini API key visible in scrollback after Enter).gh auth login,npm login,aws configure,gcloud auth) all mask credential inputs for this reason.sensitive?: booleanfield toWizardTextParams; the clack frontend dispatches to@clack/prompts.password()when it's set; flippedsensitive: trueat every onboarding text prompt that receives a credential (8 call sites across model-auth, web-search, gateway token/password, self-hosted provider, and skill API-key prompts).src/secrets/**,src/agents/auth-profiles/**,src/gateway/**/*auth*.ts, or any CODEOWNERS-secops-protected path. Non-interactive onboarding paths bypass the prompter entirely and are unchanged.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause
WizardTextParamsdid not expose asensitiveflag, so the clack prompter always calledtext()regardless of whether the prompt was asking for a credential. Result: every wizard text prompt — including credential entry — rendered cleartext that persisted in the terminal stream. TheWizardSteptype already had asensitive?: booleanfield (sketched but unwired), so the design intent was there; this PR completes the plumbing.password()rather thantext(), or that thesensitiveflag propagates from caller through the wizard step contract. Adding session-level + call-site assertions.WizardStep.sensitivebut never wired throughWizardTextParamsand the clack adapter.Regression Test Plan
src/wizard/session.test.ts(new test) +src/commands/onboard-search.test.ts(tightened existing assertion)sensitive: truecarries that flag in the emittedWizardStep, and the search-setup flow sets the flag for the credential prompt.sensitiveflag is the contract between the wizard runner and the rendering frontend (clack/remote/host). If the flag stops propagating, masking silently regresses everywhere downstream. Asserting at the contract level catches that without needing a TTY-mocked clack.setup.plugin-config.test.tsasserts that sensitive plugin-config fields get skipped from prompting (because masked input wasn't available before). That test is intentionally untouched in this PR — re-enabling sensitive plugin-config prompts is a separate concern and a follow-up.User-visible / Behavior Changes
Credential prompts in
openclaw onboard(andopenclaw onboard --remote) now render as masked-bullet input. The entered value is no longer echoed into the terminal:--remoteonboarding)Enter <SKILL_ENV_VAR>(skill credential entry, e.g. nano-banana / image-gen API keys)No config-shape changes; non-interactive onboarding paths are unchanged.
Diagram
Security Impact (required)
Start-Transcriptlogs, screenshots, recordings). Storage / transmission / logging paths after submission are untouched.sensitivecode path is unchanged for every existing caller. The masked path uses@clack/prompts.password()— same library, same major version (^1.2.0) the project already depends on. Tests assert the dispatch andWizardStep.sensitivepropagation. CODEOWNERS check: none of the modified paths fall under@openclaw/secopsownership globs (/src/secrets/,/src/security/,/src/agents/**/*auth*.ts,/src/gateway/**/*auth*.ts); the change touchessrc/wizard/,src/flows/,src/plugins/provider-{auth-input,self-hosted-setup}.ts,src/commands/onboard-*. Maintainers are welcome to route to secops anyway as a courtesy security review.Repro + Verification
Environment
provider-auth-input.tsandsearch-setup.ts)Steps
openclaw onboard --install-daemonin PowerShell.AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXX— obviously fake).Start-Transcriptbefore step 1 and inspect the transcript file afterward.Expected
••••...while typing/pasting.Start-Transcriptlog do not contain the cleartext.Actual
Start-Transcript.@clack/prompts.password(); cleartext is never placed into the terminal stream.Evidence
session.test.tsforwards sensitive flag to the emitted text steptest and the tightenedonboard-search.test.tsGemini-key assertion.Human Verification (required)
What was personally verified (not just CI):
pnpm tsgo:prod(=tsgo:core && tsgo:extensions) — clean.pnpm check— 0 warnings, 0 errors acrossoxlint:core/oxlint:extensions/oxlint:scripts, plus all policy guards (check:import-cycles,check:temp-path-guardrails,lint:auth:pairing-account-scope,lint:auth:no-pairing-store-group,lint:webhook:no-low-level-body-read,check:deprecated-internal-config-api).node scripts/run-vitest.mjs run --config test/vitest/vitest.full-core-unit-fast.config.ts src/wizard/session.test.ts src/wizard/clack-prompter.test.ts— 6/6 pass, including the new sensitive-propagation test.node scripts/run-vitest.mjs run --config test/vitest/vitest.commands.config.ts src/commands/onboard-search.test.ts— 15/15 pass, including the tightened Gemini-key assertion (expect.objectContaining({ message: entry.textMessage, sensitive: true })).sensitive: undefined(default) preserves existingtext()behavior end-to-end (covered by the plain branch insession.test.ts).provider-auth-input.ts— the existing Use existing key xxx?confirm()path that callsformatApiKeyPreviewis untouched; only the fall-through entry prompt is masked.openclaw onboard --install-daemonin a fresh Windows PowerShell session against the new build, withStart-Transcriptenabled, to visually confirm masked rendering and verify the cleartext is absent from scrollback and the transcript file. The contributor will perform this and attach before/after screenshots before merge; reviewers are welcome to verify independently.Review Conversations
Compatibility / Migration
sensitiveis an optional field; all existing callers (and any third-party plugin code consumingWizardPrompter) continue to work unchanged.Risks and Mitigations
password()does not acceptinitialValue/placeholder, sosensitive: truetext prompts ignore those twoWizardTextParamsfields.setup.gateway-config.tsandonboard-remote.tsthat previously seededinitialValuefromprocess.env.OPENCLAW_GATEWAY_TOKENwill ignore that seed for the interactive prompt, but the env-var fallback at the resolution layer is still consulted, so the practical user experience is unchanged. Whereprovider-auth-input.tswanted to reuse a stored key, it already prompts Use existing key xxx? viaconfirm()before falling through to entry; that path is untouched.AI assist disclosure (per CONTRIBUTING.md)
claude-opus-4-7) via Claude Code. The contributor reviewed every change before commit.