Unverified 提交 26687a6f authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

fix: trim whitespace from API keys and secrets in encrypt/decrypt (#2518)

## Summary - Trims whitespace from API keys and secrets during both encryption and decryption in `src/main/settings.ts` - Prevents issues caused by accidental leading/trailing whitespace or newlines in API keys (e.g., from copy-paste) - Adds comprehensive tests for the `encrypt`, `decrypt`, and `readSettings` whitespace trimming behavior ## Test plan - [x] Unit tests added for `encrypt()` trimming whitespace before encrypting - [x] Unit tests added for `decrypt()` trimming whitespace from both plaintext and electron-safe-storage secrets - [x] Unit tests added for `readSettings()` trimming whitespace from decrypted API keys - [x] All 784 existing tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2518" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > <sup>[Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) is generating a summary for commit d24002f59f18428d78ce55d11e82d4800425d3eb. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Trim whitespace from API keys and secrets during encryption, decryption, and settings reads to prevent auth failures from copy‑pasted spaces or newlines. Ensures consistent storage and use of secrets across providers. - **Bug Fixes** - encrypt(): trims input before encrypting or storing plaintext - decrypt(): trims decrypted strings for both electron-safe-storage and plaintext - readSettings(): trims whitespace from decrypted and plaintext secrets - Added unit tests for encrypt, decrypt, and readSettings trimming behavior <sup>Written for commit d24002f59f18428d78ce55d11e82d4800425d3eb. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> Co-authored-by: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
上级 d857371a
......@@ -2,7 +2,12 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import fs from "node:fs";
import path from "node:path";
import { safeStorage } from "electron";
import { readSettings, getSettingsFilePath } from "@/main/settings";
import {
readSettings,
getSettingsFilePath,
encrypt,
decrypt,
} from "@/main/settings";
import { getUserDataPath } from "@/paths/paths";
import { UserSettings } from "@/lib/schemas";
......@@ -222,6 +227,61 @@ describe("readSettings", () => {
);
});
it("should trim whitespace from decrypted API keys", () => {
const mockFileContent = {
providerSettings: {
openai: {
apiKey: {
value: "encrypted-api-key",
encryptionType: "electron-safe-storage",
},
},
},
};
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(JSON.stringify(mockFileContent));
mockSafeStorage.decryptString.mockReturnValue(
" decrypted-api-key-with-spaces\n",
);
const result = readSettings();
expect(result.providerSettings.openai.apiKey).toEqual({
value: "decrypted-api-key-with-spaces",
encryptionType: "electron-safe-storage",
});
});
it("should trim whitespace from plaintext secrets", () => {
const mockFileContent = {
githubAccessToken: {
value: " plaintext-token-with-spaces\n",
encryptionType: "plaintext",
},
providerSettings: {
openai: {
apiKey: {
value: "\nplaintext-api-key\n",
encryptionType: "plaintext",
},
},
},
};
mockFs.existsSync.mockReturnValue(true);
mockFs.readFileSync.mockReturnValue(JSON.stringify(mockFileContent));
const result = readSettings();
expect(result.githubAccessToken?.value).toBe(
"plaintext-token-with-spaces",
);
expect(result.providerSettings.openai.apiKey?.value).toBe(
"plaintext-api-key",
);
});
it("should handle secrets without encryptionType", () => {
const mockFileContent = {
githubAccessToken: {
......@@ -414,6 +474,51 @@ describe("readSettings", () => {
});
});
describe("encrypt", () => {
it("should trim whitespace before encrypting", () => {
const result = encrypt(" my-api-key\n");
// In test builds, encryption falls back to plaintext
expect(result.value).toBe("my-api-key");
});
it("should trim trailing newlines", () => {
const result = encrypt("sk-abc123\n\n");
expect(result.value).toBe("sk-abc123");
});
it("should not alter values without whitespace", () => {
const result = encrypt("sk-abc123");
expect(result.value).toBe("sk-abc123");
});
});
describe("decrypt", () => {
it("should trim whitespace from plaintext secrets", () => {
const result = decrypt({
value: " my-api-key\n",
encryptionType: "plaintext",
});
expect(result).toBe("my-api-key");
});
it("should trim whitespace from electron-safe-storage secrets", () => {
mockSafeStorage.decryptString.mockReturnValue(" decrypted-key\n");
const result = decrypt({
value: Buffer.from("encrypted").toString("base64"),
encryptionType: "electron-safe-storage",
});
expect(result).toBe("decrypted-key");
});
it("should not alter values without whitespace", () => {
const result = decrypt({
value: "sk-abc123",
encryptionType: "plaintext",
});
expect(result).toBe("sk-abc123");
});
});
function scrubSettings(result: UserSettings) {
return {
...result,
......
......@@ -250,21 +250,22 @@ export function writeSettings(settings: Partial<UserSettings>): void {
}
export function encrypt(data: string): Secret {
const trimmed = data.trim();
if (safeStorage.isEncryptionAvailable() && !IS_TEST_BUILD) {
return {
value: safeStorage.encryptString(data).toString("base64"),
value: safeStorage.encryptString(trimmed).toString("base64"),
encryptionType: "electron-safe-storage",
};
}
return {
value: data,
value: trimmed,
encryptionType: "plaintext",
};
}
export function decrypt(data: Secret): string {
if (data.encryptionType === "electron-safe-storage") {
return safeStorage.decryptString(Buffer.from(data.value, "base64"));
return safeStorage.decryptString(Buffer.from(data.value, "base64")).trim();
}
return data.value;
return data.value.trim();
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论