Skip to content

bug(desktop): proxy password editing stores the masking sentinel and corrupts credentials #3696

Description

@Sun-GLiang

What happened

Maka Desktop treats the proxy-password masking sentinel (••••••••) as the editable input value.

When an existing proxy password is loaded, the password field's actual value is eight bullet characters rather than an empty value with a placeholder. Typing a replacement password triggers a Settings update on every keystroke. Each response projects the saved credential back to the same eight-character sentinel, causing the newly typed character to disappear.

More seriously, any value that is not exactly equal to the sentinel is accepted as a new credential. Normal typing can therefore store a value shaped like:

••••••••<one newly typed character>

This silently destroys the previous proxy credential.

The adjacent actions are also misleading:

  • Show password reveals the literal eight-character sentinel rather than the newly entered or saved password.
  • Copy password copies the sentinel when clipboard access is available. It cannot copy the saved credential because the real secret is intentionally not returned to the Renderer.

This is deterministic credential corruption, not only a visual masking problem.

Expected behavior:

  • An existing proxy credential is represented as configuration state, not as an editable password string.
  • The input's actual value is empty and its placeholder says that a password is already saved.
  • The user can type a complete replacement password without characters disappearing.
  • Typing updates only a local draft; it does not persist the credential on every keystroke.
  • Blur or Enter submits the complete replacement once.
  • Escape cancels the replacement and preserves the existing credential.
  • An empty draft preserves the existing credential.
  • Show/hide controls only the newly entered draft. It never reveals the saved credential.
  • The proxy-password field does not show a copy action.
  • Copy actions on other password and credential fields remain unchanged.
  • Password content never appears in ordinary Settings storage, logs, errors, or update responses.

How to reproduce

  1. Start Maka Desktop from source.
  2. Open Settings → General → Network.
  3. Enable Proxy server.
  4. Enable Proxy authentication.
  5. Start with an existing saved proxy password.
  6. Observe that the password input contains •••••••• as its actual value.
  7. Click the password field without selecting or clearing the sentinel.
  8. Type a disposable test password normally, one character after another.
  9. Observe that newly entered characters disappear and the field repeatedly returns to eight bullets.
  10. Select Show password.
  11. Observe that the field still shows eight bullet characters rather than the replacement password.
  12. When clipboard permission is available, select Copy password and paste into a safe local scratch field.
  13. Observe that the pasted value is eight bullet characters.
  14. Inspect only redacted credential metadata, or test against an authenticated local proxy.
  15. Observe that the stored credential has been replaced with a value beginning with the eight-character sentinel rather than the complete test password.

Do not use a real proxy password for this reproduction because the current implementation can destroy it.

Environment

  • Maka version or commit: 74dcd56bd (@maka/desktop@0.2.0)
  • OS and version: macOS 15.6 (24G84)
  • Surface: Desktop development build / Runtime Host
  • Renderer URL: http://localhost:5173
  • Node.js version: v24.19.0
  • Proxy: HTTP/HTTPS at 127.0.0.1:7897
  • Proxy authentication: enabled
  • Existing proxy credential: configured

Logs, screenshots, or additional context

Observed development-build evidence

The issue was reproduced through the local Maka Dev UI. The relevant implementation files are unchanged from upstream/main at 74dcd56bd.

After entering a disposable replacement password:

  • the UI returned to eight bullets;
  • the Runtime Host proxy credential was updated at 2026-08-24 15:46:44 CST;
  • the stored value had 9 characters;
  • the stored value began with ••••••••;
  • the stored value was not equal to the pure eight-character sentinel.

The raw credential was not printed or included in diagnostics. The metadata proves that the stored value had the form “masking sentinel plus one character.”

Selecting Show password still displayed eight bullets, confirming that the visibility action operates on the sentinel string rather than the saved credential.

Computer-use automation could not directly verify the clipboard payload because macOS rejected clipboard access and Maka correctly displayed:

Copy failed — The clipboard is unavailable or was denied by the system.

Independent manual reproduction shows that successful clipboard access copies eight bullets. This also follows directly from the current implementation, which copies props.value.

Root cause

apps/desktop/src/main/runtime-host-settings-ipc-main.ts projects a configured credential into the editable settings model:

password: proxyCredential?.configured ? SENSITIVE_PLACEHOLDER : "",

apps/desktop/src/renderer/settings/general-settings-page.tsx binds that value directly to the password input and persists every change:

<PasswordInput
  value={proxyDraft.password}
  onChange={(next) => void updateProxy({ password: next })}
  label={copy.password}
/>

apps/desktop/src/renderer/settings/password-input.tsx treats the supplied value as ordinary editable content and copies it directly:

<TextInput
  type={visible ? 'text' : 'password'}
  value={props.value}
  onChange={(value) => props.onChange(value)}
/>
await navigator.clipboard.writeText(props.value);

The main process preserves only an exact sentinel match. Any sentinel-plus-character value is stored as a new credential:

if (
  proxy.password !== undefined &&
  proxy.password !== SENSITIVE_PLACEHOLDER
) {
  if (proxy.password.length === 0)
    await deleteCredential(client, PROXY_CREDENTIAL);
  else
    await setCredential(client, PROXY_CREDENTIAL, proxy.password);
}

The resulting sequence is:

saved credential
  → Renderer receives "••••••••"
  → user types one character
  → Renderer submits "••••••••x"
  → Runtime Host stores "••••••••x"
  → Settings response returns "••••••••"
  → newly typed character disappears

Suggested direction

Separate “a credential is configured” from “the user is entering a new credential.”

For the proxy settings path:

  • Replace the Renderer-facing password sentinel with a read-only passwordConfigured: boolean.
  • Keep the real secret exclusively in the Runtime Host credential store.
  • Use explicit credential mutations:
    • no credential mutation means keep the current credential;
    • replace carries one complete non-empty secret;
    • delete removes the credential.
  • Keep the replacement password in a local Renderer draft.
  • Commit the complete draft once on blur or Enter.
  • Cancel on Escape.
  • Treat an empty draft as “keep the existing credential.”
  • Deduplicate Enter followed by blur.
  • Make proxy testing wait for a pending replacement and abort the test if saving fails.
  • Serialize credential replacement with disabling proxy authentication so a late replacement cannot recreate a deleted credential.
  • Preserve the show/hide action for the local replacement draft.
  • Add an opt-out to the shared PasswordInput copy action and disable it only for the proxy-password field.
  • Do not change copy behavior for model API keys, web-search credentials, Bot tokens, or other password inputs.

The legacy SENSITIVE_PLACEHOLDER contract can remain temporarily for other credentials; it should no longer participate in the proxy-password read or write path.

Suggested regression coverage

Renderer and component coverage:

  • A configured proxy password produces an empty DOM input value and a localized “saved; enter a new password to replace it” placeholder.
  • The first typed character remains visible and does not contain the sentinel.
  • Sequential typing does not issue per-character Settings updates.
  • Blur submits the complete draft once.
  • Enter submits once, and its following blur does not submit again.
  • Escape cancels without modifying the saved credential.
  • Empty blur preserves the saved credential.
  • Save failure preserves the complete local draft.
  • Show/hide controls the new draft.
  • The proxy-password input has no copy button.
  • Other PasswordInput callers retain their copy button.

Main-process and Runtime Host coverage:

  • Credential presence projects to passwordConfigured.
  • Proxy Settings responses contain neither the secret nor SENSITIVE_PLACEHOLDER.
  • An omitted credential mutation preserves the existing credential.
  • replace writes one complete secret.
  • delete removes the credential.
  • Empty replacement values are rejected.
  • Secrets do not enter the ordinary Settings Store or error output.
  • Proxy testing waits for a pending replacement.
  • Disabling authentication wins over any older in-flight replacement.

Desktop end-to-end coverage:

  1. Seed an isolated Runtime Host with an existing disposable proxy credential.
  2. Open the development Settings UI.
  3. Enter a replacement password normally.
  4. Exercise show/hide.
  5. Commit and reload Settings.
  6. Confirm that the UI reports a configured credential without exposing it.
  7. Use a controlled authenticated local proxy to verify that the complete replacement password is used.

AI assistance

OpenAI Codex was used to reproduce the behavior in the local Maka Desktop development build through Computer Use, inspect only redacted Runtime Host credential metadata, trace the Renderer → Desktop IPC → Runtime Host data flow, and draft this report. The human contributor reviewed the findings and remains responsible for the submission.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions