Skip to content
Draft
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
5 changes: 5 additions & 0 deletions extensions/bitwarden/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Bitwarden Changelog

## [Security & Fixes] - {PR_MERGE_DATE}

- **Security:** Redact master password from error logs so it is never exposed when using "Copy Last Errors" or sharing bug reports (unlock/login failures now store a sanitized message)
- Fix npm install peer dependency conflict by pinning react-devtools to the version expected by @raycast/api

## [Added PasteUsernameAction] - 2026-01-01

## [Added support for Windows] - 2025-10-15
Expand Down
289 changes: 148 additions & 141 deletions extensions/bitwarden/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion extensions/bitwarden/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"marinsokol",
"jose-elias-alvarez",
"krambono",
"clins1994"
"clins1994",
"0xdhrv"
],
"pastContributors": [
"pomdtr"
Expand Down
5 changes: 3 additions & 2 deletions extensions/bitwarden/src/api/bitwarden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { join, dirname } from "path";
import { chmod, rename, rm } from "fs/promises";
import { decompressFile, removeFilesThatStartWith, unlinkAllSync, waitForFileAvailable } from "~/utils/fs";
import { download } from "~/utils/network";
import { treatError } from "~/utils/debug";
import { captureException } from "~/utils/development";
import { ReceivedSend, Send, SendCreatePayload, SendType } from "~/types/send";
import { prepareSendPayload } from "~/api/bitwarden.helpers";
Expand Down Expand Up @@ -352,7 +353,7 @@ export class Bitwarden {
await this.callActionListeners("login");
return { result: undefined };
} catch (execError) {
captureException("Failed to login", execError);
captureException("Failed to login", treatError(execError));
const { error } = await this.handleCommonErrors(execError);
if (!error) throw execError;
return { error };
Expand Down Expand Up @@ -406,7 +407,7 @@ export class Bitwarden {
await this.callActionListeners("unlock", password, sessionToken);
return { result: sessionToken };
} catch (execError) {
captureException("Failed to unlock vault", execError);
captureException("Failed to unlock vault", treatError(execError, { omitSensitiveValue: password }));
const { error } = await this.handleCommonErrors(execError);
if (!error) throw execError;
return { error };
Expand Down
6 changes: 3 additions & 3 deletions extensions/bitwarden/src/components/UnlockForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const UnlockForm = ({ pendingAction = Promise.resolve() }: UnlockFormProps) => {
} = getUsefulError(error, password);
await showToast(Toast.Style.Failure, "Failed to log in", displayableError);
setUnlockError(treatedError);
captureException("Failed to log in", error);
captureException("Failed to log in", treatedError);
return;
}
}
Expand All @@ -61,7 +61,7 @@ const UnlockForm = ({ pendingAction = Promise.resolve() }: UnlockFormProps) => {
const { displayableError = "Please check your credentials", treatedError } = getUsefulError(error, password);
await showToast(Toast.Style.Failure, "Failed to unlock vault", displayableError);
setUnlockError(treatedError);
captureException("Failed to unlock vault", error);
captureException("Failed to unlock vault", treatedError);
} finally {
setLoading(false);
}
Expand Down Expand Up @@ -91,7 +91,7 @@ const UnlockForm = ({ pendingAction = Promise.resolve() }: UnlockFormProps) => {
icon={showPassword ? Icon.EyeDisabled : Icon.Eye}
title={showPassword ? "Hide Password" : "Show Password"}
onAction={() => setShowPassword((prev) => !prev)}
shortcut={{ macOS: { key: "e", modifiers: ["opt"] }, windows: { key: "e", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "e", modifiers: ["opt"] }, Windows: { key: "e", modifiers: ["alt"] } }}
/>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function VaultActionsSection() {
<ActionPanel.Section title="Vault Actions">
<Action
title="Sync Vault"
shortcut={{ macOS: { key: "r", modifiers: ["opt"] }, windows: { key: "r", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "r", modifiers: ["opt"] }, Windows: { key: "r", modifiers: ["alt"] } }}
icon={Icon.ArrowClockwise}
onAction={vault.syncItems}
/>
Expand All @@ -37,7 +37,7 @@ export function VaultActionsSection() {
title="Lock Vault"
shortcut={{
macOS: { key: "l", modifiers: ["opt", "shift"] },
windows: { key: "l", modifiers: ["alt", "shift"] },
Windows: { key: "l", modifiers: ["alt", "shift"] },
}}
onAction={handleLockVault}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const GeneratePasswordActionPanel = (props: GeneratePasswordActionPanelProps) =>
title="Copy Password"
icon={Icon.Clipboard}
onAction={handleCopy(password)}
shortcut={{ macOS: { key: "enter", modifiers: ["opt"] }, windows: { key: "enter", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "enter", modifiers: ["opt"] }, Windows: { key: "enter", modifiers: ["alt"] } }}
/>
<Action.Paste
title="Paste Password to Active App"
Expand All @@ -34,7 +34,7 @@ const GeneratePasswordActionPanel = (props: GeneratePasswordActionPanelProps) =>
shortcut={{
key: "enter",
macOS: { key: "enter", modifiers: ["opt", "shift"] },
windows: { key: "enter", modifiers: ["alt", "shift"] },
Windows: { key: "enter", modifiers: ["alt", "shift"] },
}}
/>
</>
Expand All @@ -44,7 +44,7 @@ const GeneratePasswordActionPanel = (props: GeneratePasswordActionPanelProps) =>
icon={Icon.ArrowClockwise}
shortcut={{
macOS: { key: "backspace", modifiers: ["opt"] },
windows: { key: "backspace", modifiers: ["alt"] },
Windows: { key: "backspace", modifiers: ["alt"] },
}}
/* avoid passing a reference to onAction because, for some reason, a string
is passed to it, even though the type says otherwise 🤔 */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function CopyTotpAction() {
title="Copy TOTP"
icon={Icon.Clipboard}
onAction={copyTotp}
shortcut={{ macOS: { key: "t", modifiers: ["opt"] }, windows: { key: "t", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "t", modifiers: ["opt"] }, Windows: { key: "t", modifiers: ["alt"] } }}
repromptDescription={`Copying the TOTP of <${selectedItem.name}>`}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function CopyUsernameAction() {
title="Copy Username"
icon={Icon.Person}
onAction={handleCopyUsername}
shortcut={{ macOS: { key: "u", modifiers: ["opt"] }, windows: { key: "u", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "u", modifiers: ["opt"] }, Windows: { key: "u", modifiers: ["alt"] } }}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function FavoriteItemActions() {
title={isLocalFavorite ? "Remove Favorite" : "Mark As Favorite"}
onAction={handleToggleFavorite}
icon={isLocalFavorite ? Icon.StarDisabled : Icon.Star}
shortcut={{ macOS: { key: "f", modifiers: ["opt"] }, windows: { key: "f", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "f", modifiers: ["opt"] }, Windows: { key: "f", modifiers: ["alt"] } }}
/>
)}
{(isBitwardenFavorite || isLocalFavorite) && (
Expand All @@ -39,7 +39,7 @@ function FavoriteItemActions() {
icon={Icon.ArrowUpCircleFilled}
shortcut={{
macOS: { key: "arrowUp", modifiers: ["opt", "shift"] },
windows: { key: "arrowUp", modifiers: ["alt", "shift"] },
Windows: { key: "arrowUp", modifiers: ["alt", "shift"] },
}}
/>
<Action
Expand All @@ -48,7 +48,7 @@ function FavoriteItemActions() {
icon={Icon.ArrowDownCircleFilled}
shortcut={{
macOS: { key: "arrowDown", modifiers: ["opt", "shift"] },
windows: { key: "arrowDown", modifiers: ["alt", "shift"] },
Windows: { key: "arrowDown", modifiers: ["alt", "shift"] },
}}
/>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function OpenUrlInBrowserAction() {
title="Open in Browser"
onAction={handleOpenUrlInBrowser}
icon={Icon.Globe}
shortcut={{ macOS: { key: "o", modifiers: ["opt"] }, windows: { key: "o", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "o", modifiers: ["opt"] }, Windows: { key: "o", modifiers: ["alt"] } }}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function PasteTotpAction() {
onAction={pasteTotp}
shortcut={{
macOS: { key: "t", modifiers: ["opt", "shift"] },
windows: { key: "t", modifiers: ["alt", "shift"] },
Windows: { key: "t", modifiers: ["alt", "shift"] },
}}
repromptDescription={`Pasting the TOTP of <${selectedItem.name}>`}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function PasteUsernameAction() {
repromptDescription={`Pasting the username of <${selectedItem.name}>`}
shortcut={{
macOS: { key: "u", modifiers: ["cmd", "opt"] },
windows: { key: "u", modifiers: ["ctrl", "alt"] },
Windows: { key: "u", modifiers: ["ctrl", "alt"] },
}}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function ShowNotesAction() {
icon={Icon.Eye}
onAction={showNotes}
repromptDescription={`Showing the notes of <${selectedItem.name}>`}
shortcut={{ macOS: { key: "n", modifiers: ["opt"] }, windows: { key: "n", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "n", modifiers: ["opt"] }, Windows: { key: "n", modifiers: ["alt"] } }}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function ShowDetailsScreen<TDetails extends Constraint>(props: ShowDetailsScreen
shortcutKey
? {
macOS: { key: shortcutKey, modifiers: ["opt"] },
windows: { key: shortcutKey, modifiers: ["alt"] },
Windows: { key: shortcutKey, modifiers: ["alt"] },
}
: undefined
}
Expand Down
4 changes: 2 additions & 2 deletions extensions/bitwarden/src/create-login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ function CreateLoginComponent() {
icon={showPassword ? Icon.EyeDisabled : Icon.Eye}
title={showPassword ? "Hide Password" : "Show Password"}
onAction={togglePasswordVisibility}
shortcut={{ macOS: { key: "e", modifiers: ["opt"] }, windows: { key: "e", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "e", modifiers: ["opt"] }, Windows: { key: "e", modifiers: ["alt"] } }}
/>
<Action
icon={Icon.Key}
title="Generate Password"
onAction={generatePassword}
shortcut={{ macOS: { key: "g", modifiers: ["opt"] }, windows: { key: "g", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "g", modifiers: ["opt"] }, Windows: { key: "g", modifiers: ["alt"] } }}
/>
<DebuggingBugReportingActionSection />
</ActionPanel>
Expand Down
8 changes: 4 additions & 4 deletions extensions/bitwarden/src/search-sends.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ function SearchSendsCommandContent() {
title="Create New Send"
target={<CreateSendCommand onSuccess={onCreateSuccess} />}
icon={Icon.NewDocument}
shortcut={{ macOS: { key: "n", modifiers: ["opt"] }, windows: { key: "n", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "n", modifiers: ["opt"] }, Windows: { key: "n", modifiers: ["alt"] } }}
/>
<Action
title={syncAction.title}
Expand Down Expand Up @@ -390,21 +390,21 @@ function SearchSendsCommandContent() {
title="Remove Password"
onAction={() => onRemovePassword(send.id)}
icon={Icon.LockUnlocked}
shortcut={{ macOS: { key: "p", modifiers: ["opt"] }, windows: { key: "p", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "p", modifiers: ["opt"] }, Windows: { key: "p", modifiers: ["alt"] } }}
/>
)}
<Action.Push
title="Edit Send"
target={<CreateSendCommand send={send} onSuccess={onEditSuccess} />}
icon={Icon.Pencil}
shortcut={{ macOS: { key: "e", modifiers: ["opt"] }, windows: { key: "e", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "e", modifiers: ["opt"] }, Windows: { key: "e", modifiers: ["alt"] } }}
/>
<Action
title="Delete Send"
style={Action.Style.Destructive}
onAction={() => onDelete(send.id)}
icon={Icon.Trash}
shortcut={{ macOS: { key: "d", modifiers: ["opt"] }, windows: { key: "d", modifiers: ["alt"] } }}
shortcut={{ macOS: { key: "d", modifiers: ["opt"] }, Windows: { key: "d", modifiers: ["alt"] } }}
/>
{sendManagementActionSection}
<DebuggingBugReportingActionSection />
Expand Down
17 changes: 13 additions & 4 deletions extensions/bitwarden/src/utils/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ export function treatError(error: unknown, options?: { omitSensitiveValue: strin
try {
const execaError = error as ExecaError;
let errorString: string | undefined;
if (execaError?.stderr) {
errorString = execaError.stderr;
} else if (error instanceof Error) {
if (error instanceof Error) {
// Include message first (execa puts full command there, which can contain passwords)
errorString = `${error.name}: ${error.message}`;
const stderrStr = typeof execaError?.stderr === "string" ? execaError.stderr : "";
if (stderrStr && !errorString.includes(stderrStr)) {
errorString += `\n${stderrStr}`;
}
} else if (isObject(error)) {
errorString = JSON.stringify(error);
} else {
Expand All @@ -24,6 +27,12 @@ export function treatError(error: unknown, options?: { omitSensitiveValue: strin
}
}

/** Escapes special regex characters so the string can be used literally in RegExp. */
function escapeForRegExp(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

export function omitSensitiveValueFromString(value: string, sensitiveValue: string) {
return value.replace(new RegExp(sensitiveValue, "i"), "[REDACTED]");
if (!sensitiveValue) return value;
return value.replace(new RegExp(escapeForRegExp(sensitiveValue), "gi"), "[REDACTED]");
}
Loading