Skip to content

fix(onboarding): mask credential inputs in interactive wizard prompts#76615

Closed
anurag-bg-neu wants to merge 4 commits into
openclaw:mainfrom
anurag-bg-neu:fix/mask-api-key-input
Closed

fix(onboarding): mask credential inputs in interactive wizard prompts#76615
anurag-bg-neu wants to merge 4 commits into
openclaw:mainfrom
anurag-bg-neu:fix/mask-api-key-input

Conversation

@anurag-bg-neu

Copy link
Copy Markdown
Contributor

Summary

  • Problem: API keys, gateway tokens, and gateway passwords entered during the interactive openclaw onboard wizard were echoed in cleartext into the terminal stream — leaving secrets in PowerShell scrollback, Start-Transcript logs, screenshots, screen-shares, and shoulder-surfing range. Reproduced on Windows PowerShell during a normal openclaw onboard --install-daemon session (Gemini API key visible in scrollback after Enter).
  • Why it matters: real-world credential leak vector. Same class as CWE-549 (missing field masking) / CWE-200 (information exposure). Industry-standard CLIs (gh auth login, npm login, aws configure, gcloud auth) all mask credential inputs for this reason.
  • What changed: added a sensitive?: boolean field to WizardTextParams; the clack frontend dispatches to @clack/prompts.password() when it's set; flipped sensitive: true at 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).
  • What did NOT change (scope boundary): how secrets are stored, transmitted, or logged after entry — only the input rendering. No changes to 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)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause

  • Root cause: WizardTextParams did not expose a sensitive flag, so the clack prompter always called text() 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. The WizardStep type already had a sensitive?: boolean field (sketched but unwired), so the design intent was there; this PR completes the plumbing.
  • Missing detection / guardrail: no test asserted that secret-input prompts dispatch to password() rather than text(), or that the sensitive flag propagates from caller through the wizard step contract. Adding session-level + call-site assertions.
  • Contributing context (if known): the wizard plumbing was built up incrementally; the masked-input pathway was scaffolded in WizardStep.sensitive but never wired through WizardTextParams and the clack adapter.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/wizard/session.test.ts (new test) + src/commands/onboard-search.test.ts (tightened existing assertion)
  • Scenario the test should lock in: any wizard step constructed with sensitive: true carries that flag in the emitted WizardStep, and the search-setup flow sets the flag for the credential prompt.
  • Why this is the smallest reliable guardrail: the sensitive flag 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.
  • Existing test that already covers this (if any): setup.plugin-config.test.ts asserts 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.
  • If no new test is added, why not: N/A — added.

User-visible / Behavior Changes

Credential prompts in openclaw onboard (and openclaw onboard --remote) now render as masked-bullet input. The entered value is no longer echoed into the terminal:

  • Enter Gemini API key (and every other model-auth provider key — OpenAI / Anthropic / Z.AI / etc.)
  • Google Gemini API key / Brave API key / Tavily API key / Firecrawl API key / etc. (web-search providers)
  • Gateway token (blank to generate) / Gateway password (both local QuickStart and --remote onboarding)
  • API key (self-hosted OpenAI-compatible providers)
  • 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

Before: o  Enter Gemini API key
        |  AIzaSyCQBy...e4CWsDI         ← cleartext echo persists in scrollback / Start-Transcript

After:  o  Enter Gemini API key
        |  ••••••••••••••••••••••       ← masked; cleartext never rendered

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? Yes — input-rendering only. Cleartext is no longer placed into the terminal stream (and therefore PowerShell scrollback, Start-Transcript logs, screenshots, recordings). Storage / transmission / logging paths after submission are untouched.
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Risk + mitigation: low. The non-sensitive code 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 and WizardStep.sensitive propagation. CODEOWNERS check: none of the modified paths fall under @openclaw/secops ownership globs (/src/secrets/, /src/security/, /src/agents/**/*auth*.ts, /src/gateway/**/*auth*.ts); the change touches src/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

  • OS: Windows 10 (PowerShell 5.1) — original repro environment
  • Runtime/container: Node v24.15
  • Model/provider: Google Gemini (also exercises the same code path as OpenAI / Z.AI / Brave / Tavily / etc. via provider-auth-input.ts and search-setup.ts)
  • Integration/channel (if any): N/A — onboarding wizard
  • Relevant config (redacted): N/A

Steps

  1. Run openclaw onboard --install-daemon in PowerShell.
  2. At Model/auth provider → Google → Google Gemini API key → Enter Gemini API key, paste a fake key (e.g. AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXX — obviously fake).
  3. Continue to Web search → Search provider → Gemini → Google Gemini API key and paste again.
  4. After Enter on each prompt, scroll the terminal back over the entered value. Optionally, Start-Transcript before step 1 and inspect the transcript file afterward.

Expected

  • Prompt renders as ••••... while typing/pasting.
  • After Enter, the value is not displayed in the prompt confirmation line.
  • Terminal scrollback and the Start-Transcript log do not contain the cleartext.

Actual

  • Before this PR: cleartext key visible in scrollback and Start-Transcript.
  • After this PR (per the diff): masked input via @clack/prompts.password(); cleartext is never placed into the terminal stream.

Evidence

  • Failing test/log before + passing after — see the new session.test.ts forwards sensitive flag to the emitted text step test and the tightened onboard-search.test.ts Gemini-key assertion.
  • Trace/log snippets
  • Screenshot/recording — to be added before merge by the contributor; reviewers, please request before/after PowerShell screenshots if not yet attached.
  • Perf numbers (if relevant)

Human Verification (required)

What was personally verified (not just CI):

  • Verified scenarios (automated, on this branch with all changes applied):
    • pnpm tsgo:prod (= tsgo:core && tsgo:extensions) — clean.
    • pnpm check — 0 warnings, 0 errors across oxlint: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 })).
  • Edge cases checked:
    • sensitive: undefined (default) preserves existing text() behavior end-to-end (covered by the plain branch in session.test.ts).
    • provider-auth-input.ts — the existing Use existing key xxx? confirm() path that calls formatApiKeyPreview is untouched; only the fall-through entry prompt is masked.
  • What was NOT verified: end-to-end manual run of openclaw onboard --install-daemon in a fresh Windows PowerShell session against the new build, with Start-Transcript enabled, 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

  • I will reply to or resolve every bot review conversation I address in this PR.
  • I will leave unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yessensitive is an optional field; all existing callers (and any third-party plugin code consuming WizardPrompter) continue to work unchanged.
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A.

Risks and Mitigations

  • Risk: clack's password() does not accept initialValue / placeholder, so sensitive: true text prompts ignore those two WizardTextParams fields.
    • Mitigation: this is the desired behavior for credential entry — pre-filling or hint-suggesting a secret is itself a leak vector. The two prompts in setup.gateway-config.ts and onboard-remote.ts that previously seeded initialValue from process.env.OPENCLAW_GATEWAY_TOKEN will 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. Where provider-auth-input.ts wanted to reuse a stored key, it already prompts Use existing key xxx? via confirm() before falling through to entry; that path is untouched.

AI assist disclosure (per CONTRIBUTING.md)

  • AI-assisted: planning, research, edits, and tests by Claude Opus 4.7 (claude-opus-4-7) via Claude Code. The contributor reviewed every change before commit.
  • Testing degree: lightly tested — automated lanes (typecheck core+extensions, lint, targeted unit tests) all green; end-to-end manual run on Windows PowerShell against a freshly-built CLI not yet performed.
  • Session log / prompts: available on request.
  • Codex review: not run locally (no Codex access in this environment); maintainers' Codex review feedback will be addressed before merge.
  • Contributor confirms understanding of what the code does.

@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations size: XS labels May 3, 2026
@clawsweeper

clawsweeper Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Summary
The PR adds sensitive wizard text plumbing, masks onboarding credential prompts in Clack and the macOS wizard CLI, preserves existing secrets with masked-preview confirmations, and adds regression tests plus a changelog entry.

Reproducibility: yes. Source inspection on current main shows credential onboarding paths call prompter.text(), and the shared Clack adapter renders that through normal text input; I did not run a live terminal transcript.

Next step before merge
Maintainer should inspect or rerun the latest macos-swift logs because the public annotations do not expose a narrow automated repair.

Security
Cleared: No concrete security or supply-chain regression found; the diff narrows terminal secret exposure without adding dependencies, workflow changes, permissions, or new storage/network paths.

Review details

Best 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 prompter.text(), and the shared Clack adapter renders that through normal text input; I did not run a live terminal transcript.

Is this the best way to solve the issue?

Yes, pending CI. The PR uses the existing WizardPrompter and session boundary, aligns with the already-present macOS WizardStep.sensitive/SecureField model, and keeps storage/network behavior out of scope.

Acceptance criteria:

  • swiftlint --config .swiftlint.yml
  • swiftformat --lint apps/macos/Sources --config .swiftformat
  • swift build --package-path apps/macos --product OpenClaw --configuration release
  • pnpm test src/wizard/session.test.ts src/wizard/clack-prompter.test.ts src/wizard/setup.gateway-config.test.ts src/commands/onboard-remote.test.ts src/commands/onboard-search.test.ts

What I checked:

  • Current main has no sensitive text prompt contract: WizardTextParams on current main only includes message, initialValue, placeholder, and validate, so callers cannot request masked shared wizard text input. (src/wizard/prompts.ts:21, 8142e67d633d)
  • Current main Clack text path echoes normal text: createClackPrompter().text always dispatches to @clack/prompts.text() with initialValue and placeholder. (src/wizard/clack-prompter.ts:119, 8142e67d633d)
  • Current main credential call sites use text prompts: Gateway token/password and provider API-key paths call prompter.text() for credential entry on current main. (src/wizard/setup.gateway-config.ts:212, 8142e67d633d)
  • PR head masks the shared Clack sensitive branch: At PR head, params.sensitive routes to @clack/prompts.password() while the normal text branch remains unchanged. (src/wizard/clack-prompter.ts:122, dcfca89199c4)
  • Dependency contract supports password masking: @clack/[email protected] exports password(opts: PasswordOptions) with message, mask, validate, and clearOnError, while TextOptions is the API carrying initialValue and placeholder. (pnpm-lock.yaml:2082, 8142e67d633d)
  • PR head propagates sensitivity to wizard clients: At PR head, WizardSessionPrompter.text copies params.sensitive into emitted WizardStep objects. (src/wizard/session.ts:115, dcfca89199c4)

Likely related people:

  • @steipete: GitHub path history shows original/repeated work on unified onboarding, wizard/session surfaces, remote onboarding cleanup, and macOS app/CLI refactors that surround this PR's behavior. (role: original and adjacent maintainer; confidence: medium; commits: 53baba71faa3, 75cee3d4d1c5, ccbbe1cd9532; files: src/wizard/session.ts, src/wizard/setup.gateway-config.ts, src/commands/onboard-remote.ts)
  • @vincentkoc: Current checkout blame attributes the wizard text contract, Clack prompter branch, session emission, and macOS wizard CLI text handler lines to Vincent Koc, and GitHub history also shows recent type-surface work near remote onboarding. (role: recent current-line maintainer; confidence: medium; commits: 9ef35ea5c71c, 74e7b8d47b18; files: src/wizard/prompts.ts, src/wizard/clack-prompter.ts, src/wizard/session.ts)

Remaining risk / open question:

  • Latest PR head dcfca89199c4 still has a failing macos-swift check in the Swift lint step, and the public annotations do not expose the exact lint rule or diff needed.
  • The latest check-test-types failure points at src/tui/commands.test.ts, outside this PR's changed files, but it remains a failing check to reconcile before merge.
  • I verified the bug and fix path from source and dependency contracts, not with a live Windows PowerShell transcript or macOS TTY run.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 8142e67d633d.

@openclaw-barnacle openclaw-barnacle Bot added app: macos App: macos size: M and removed size: XS labels May 3, 2026
@anurag-bg-neu

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review. New commit bd38ccf689 addresses all three findings.

[P2] Preserve masked prompt defaults — src/wizard/clack-prompter.ts:123-126

You correctly diagnosed the regression. I went with the second option you suggested ("update those callers to keep existing secrets without rendering them") rather than a prompter-level "blank → initialValue" rule. Reasoning: a prompter-level fallback would invert the "Gateway token (blank to generate)" semantic in setup.gateway-config.ts:212 — there blank legitimately means "generate a new random token", not "keep the env value". So the fix lives at each call site, mirroring the pattern already established in provider-auth-input.ts:204-213.

For each of the 3 affected sites, the pattern is now:

  1. Detect existing value (env var / cfg field / passed-in).
  2. If non-empty, ask via prompter.confirm({ message: "Use existing X (<masked-preview>)?", initialValue: true }) using maskApiKey() for the preview.
  3. If user confirms → use existing without showing the masked text prompt.
  4. If user declines (or there is no existing value) → show the masked text prompt with no initialValue.

Touched call sites:

  • src/wizard/setup.gateway-config.ts:212 — Gateway token in advanced flow
  • src/commands/onboard-remote.ts:198 — Gateway token in remote onboarding
  • src/commands/onboard-remote.ts:230 — Gateway password in remote onboarding

New regression tests (failing before, passing after):

  • src/wizard/setup.gateway-config.test.tskeeps OPENCLAW_GATEWAY_TOKEN in advanced flow when user confirms keeping existing
  • src/commands/onboard-remote.test.tskeeps an existing remote gateway token when user confirms via masked-preview prompt
  • src/commands/onboard-remote.test.tskeeps an existing remote gateway password when user confirms via masked-preview prompt

The other masked-prompt call sites (provider-auth-input.ts:215, flows/search-setup.ts:475, provider-self-hosted-setup.ts:211, onboard-skills.ts:212) never passed initialValue to the masked text prompt and need no change.

[P2] Mask session wizard text in the macOS CLI — apps/macos/Sources/OpenClawMacCLI/WizardCommand.swift:445

Fixed. The case "text": branch in promptAnswer now checks step.sensitive == true first, and if set, dispatches to a new readPasswordWithPrompt helper that uses POSIX getpass(3) (already available via the file's Darwin import) to read input without echo. The bracketed [\(initial)] is no longer rendered for sensitive prompts. Call sites that need "keep existing" behavior must do so via a separate confirm step before the sensitive prompt — matching the pattern applied to the TypeScript call sites above.

⚠️ Verification gap: I authored this PR on Windows, where Swift toolchain is not available, so I have not built or run the macOS CLI against this change. The diff is mechanical and reviewed visually. Maintainers with macOS access (or apps/macos CI) can confirm with openclaw-mac wizard against any flow that emits a sensitive: true text step (e.g., the gateway-config or onboard-remote flows touched here) — input should not echo and […] should not be displayed.

[P3] Add the required changelog entry — CHANGELOG.md

Added under ## Unreleased > ### Fixes. Single-line bullet covering both the Node CLI masking and the macOS CLI no-echo handling, attributed per the project's "Thanks @author" convention.


Verification on bd38ccf689

  • pnpm tsgo:core — clean.
  • pnpm check — 0 warnings, 0 errors across all 3 oxlint lanes (core/scripts/extensions) plus all policy guards.
  • vitest.wizard.config.ts setup.gateway-config11/11 pass (10 prior + 1 new).
  • vitest.commands.config.ts onboard-remote12/12 pass (10 prior + 2 new).
  • vitest.commands.config.ts onboard-search15/15 pass (unchanged from prior commit).
  • vitest.full-core-unit-fast.config.ts session.test.ts clack-prompter.test.ts8/8 pass (unchanged).

Swift: not built locally (no Apple toolchain on Windows) — see the verification-gap call-out above.

End-to-end Windows PowerShell Start-Transcript proof is still TODO; I will attach before/after screenshots once captured against a fresh build of pnpm openclaw onboard --install-daemon.

@anurag-bg-neu
anurag-bg-neu force-pushed the fix/mask-api-key-input branch from bd38ccf to 6274de3 Compare May 3, 2026 11:20
@anurag-bg-neu

Copy link
Copy Markdown
Contributor Author

Pushed 6274de3523 (and rebased onto current upstream/main to clear the CHANGELOG.md merge conflict). Two things addressed:

[P2] Avoid truncating macOS wizard credentials — WizardCommand.swift:538

You're right that getpass(3) truncates at _PASSWORD_LEN (128 chars on BSD/macOS), which would silently drop the tail of any longer credential — JWTs, OpenRouter keys, Anthropic keys with extended formats, etc. Replaced the helper with a termios-based reader:

private func readPasswordWithPrompt(_ prompt: String) throws -> String {
    // Use termios to disable echo rather than getpass(3): getpass truncates
    // input at _PASSWORD_LEN (128 chars on BSD/macOS), which would silently
    // discard longer credentials such as JWTs or extended provider tokens.
    print("\(prompt): ", terminator: "")
    fflush(stdout)

    var oldTerm = termios()
    let echoDisabled: Bool
    if isatty(STDIN_FILENO) != 0 && tcgetattr(STDIN_FILENO, &oldTerm) == 0 {
        var newTerm = oldTerm
        newTerm.c_lflag &= ~tcflag_t(ECHO)
        echoDisabled = tcsetattr(STDIN_FILENO, TCSANOW, &newTerm) == 0
    } else {
        echoDisabled = false
    }
    defer {
        if echoDisabled {
            _ = tcsetattr(STDIN_FILENO, TCSANOW, &oldTerm)
        }
        // The user's Enter keystroke wasn't echoed while ECHO was disabled;
        // emit a newline so subsequent output starts on its own line.
        print("")
    }

    guard let line = readLine() else {
        throw WizardCliError.cancelled
    }
    return line
}

Properties:

  • No length capreadLine() reads the full line up to the next newline; Swift's stdin buffer is not bounded by _PASSWORD_LEN.
  • Echo restored on every exit path via defer, including throws and cancellation.
  • Non-TTY safe — when stdin isn't a TTY (isatty == 0) or tcgetattr fails, we skip the echo manipulation and just read; the prompt still appears, no echo state is left dangling.
  • Cursor hygiene — emits a trailing newline so subsequent prompt output isn't on the same visual line as the masked input.

Verification gap is unchanged: I cannot run swift build --product openclaw-mac locally (Windows host, no Apple toolchain). The diff is mechanical and follows the standard termios disable-echo pattern. Maintainers with macOS access should run the acceptance criteria you listed:

cd apps/macos && swift build --product openclaw-mac
# then exercise a sensitive wizard text step with > 128 chars and confirm
# no echo and no truncation

CHANGELOG conflict resolution

Upstream landed 5 new fixes since my last rebase, including a CHANGELOG move (9772ce6ce9). Re-resolved by slotting my entry immediately below the 5 upstream-landed entries — preserves their order verbatim, my entry remains attributed to the same author, no other lines moved.

End-to-end visual verification (Windows PowerShell)

I have local visual confirmation that the masking flow works in pnpm openclaw onboard --install-daemon on Windows PowerShell:

  • Enter Gemini API key renders as bullets (••••...).
  • The "Use existing GEMINI_API_KEY (env: GEMINI_API_KEY, AIza…WsDI)?" masked-preview confirm step fires correctly when an env var is detected, with maskApiKey() showing the safe head/tail preview.
  • Declining the confirm advances to the masked password prompt, where the new value is read without echo.

Screenshot evidence is captured locally and will be attached as a follow-up comment by @anurag-bg-neu.

Branch state

6274de3523 fix(macos-cli): replace getpass(3) with termios no-echo to avoid 128-char credential truncation
72ffd20888 fix(onboarding): preserve credential defaults via masked-preview confirm; mask macOS wizard CLI
de1a9c607f fix(onboarding): mask credential inputs in interactive wizard prompts

3 commits ahead of upstream/main, all focused on the same security concern. Force-pushed with --force-with-lease (lease-safe — refuses if anyone else had pushed in the meantime).

Verification on 6274de3523

  • pnpm tsgo:core — clean.
  • pnpm check — 0 warnings, 0 errors across all 3 oxlint lanes plus all policy guards.
  • setup.gateway-config.test.ts — 11/11 pass.
  • session.test.ts — 6/6 pass.
  • onboard-remote.test.ts — 12/12 pass.
  • onboard-search.test.ts — 15/15 pass.

The handful of pnpm test:changed failures in unrelated paths (src/trajectory/**, src/crestodian/**, src/memory-host-sdk/**, src/proxy-capture/**, test/scripts/blacksmith-testbox-state*, etc.) are pre-existing Windows-host flakes and red on upstream/main independently — none touch any file in this PR.

@anurag-bg-neu

Copy link
Copy Markdown
Contributor Author

Pushed dcfca89199 to address the macos-swift CI failure on 6274de3523.

The 22-second failure window is consistent with .github/workflows/ci.yml:1988-1991 (swiftlint + swiftformat --lint) or a fast-fail early in swift build — not a full build cycle. I narrowed the most likely causes by re-reading my Swift change and the OpenClawMacCLI target settings (apps/macos/Package.swift:65-67, 76-77 enables .upcomingFeature("StrictConcurrency") for Swift 6.2). Two defensive changes:

  • Dropped fflush(stdout). The existing readLineWithPrompt (WizardCommand.swift:520-526) doesn't flush either, and that helper compiles cleanly on this target. stdout is a global mutable variable from Darwin, and even though most modern SDK module maps mark it nonisolated(unsafe), leaning on the established no-flush pattern in the same file is the safest move under strict concurrency.
  • let echoDisabled: Boolvar echoDisabled = false. Drops the branched-let-init and collapses the if/else into a single guarded if. Cleaner under strict mode.
  • Moved print("") inside if echoDisabled. Unrelated UX bug I caught while reviewing: the previous defer always emitted a newline, even when echo was not disabled — in that case the user's Enter was already echoed and the extra newline doubled up. Now the post-prompt newline only fires when we actually disabled echo.

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 dcfca89199 still fails, the fastest path forward is the failed step's last ~50 lines of log — drop them in a comment here and I'll pinpoint the exact rule/line rather than guess again. The acceptance criteria you listed earlier still apply for full sign-off (build product OpenClaw, run swift test, exercise a > 128 char credential).

Will follow up with the Windows PowerShell Start-Transcript masking screenshot once I'm back at that host.

@anurag-bg-neu

Copy link
Copy Markdown
Contributor Author

Closing in favor of a focused replacement at #76693. The technical content for the Node-side masking is equivalent — same diff on src/wizard/**, src/flows/search-setup.ts, src/plugins/provider-{auth-input,self-hosted-setup}.ts, src/commands/onboard-{remote,skills,search}* — collapsed into one squashed commit (a3db64c265) with both before/after Windows PowerShell screenshots inline in the body, and the iterative review history off the maintainer queue.

clawsweeper findings — status in #76693:

  • ✅ [P2] Preserve masked prompt defaults → confirm-then-prompt with maskApiKey() masked preview at the 3 affected call sites (setup.gateway-config.ts:212, onboard-remote.ts:198, 230).
  • ⏭ [P2] Mask session wizard text in macOS CLI → deliberately deferred to a follow-up PR. I authored on Windows with no Apple toolchain to run swift build / swiftlint / swiftformat --lint; prior speculative iterations of the Swift change kept failing macos-swift CI. Staying TDD-strict means I don't keep guessing — a contributor with macOS access can land that fix cleanly. The macOS leak path is documented in fix(onboarding): mask token/credential inputs in CLI wizard prompts #76693's "Out-of-scope leak path (acknowledged)" subsection.
  • ✅ [P3] CHANGELOG entry → added under ## Unreleased > ### Fixes.

Appreciate the patient review on this thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: macos App: macos commands Command implementations size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant