Jelajahi Sumber

Store OpenAI API key as obfuscated bytes instead of plaintext.

Resolve from OPENAI_API_KEY for local debug or an embedded default, and remove the key from build settings and Info.plist injection.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 2 bulan lalu
induk
melakukan
7f26f68326

+ 0 - 4
App for Indeed.xcodeproj/project.pbxproj

@@ -263,13 +263,11 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				INFOPLIST_KEY_NSMainStoryboardFile = Main;
 				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
-				INFOPLIST_KEY_OPENAI_API_KEY = "$(OPENAI_API_KEY)";
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/../Frameworks",
 				);
 				MARKETING_VERSION = 1.0;
-				OPENAI_API_KEY = "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";
 				PRODUCT_BUNDLE_IDENTIFIER = "MQL-DEV.App-for-Indeed";
 				PRODUCT_NAME = "App for Indeed";
 				REGISTER_APP_GROUPS = YES;
@@ -302,13 +300,11 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				INFOPLIST_KEY_NSMainStoryboardFile = Main;
 				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
-				INFOPLIST_KEY_OPENAI_API_KEY = "$(OPENAI_API_KEY)";
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/../Frameworks",
 				);
 				MARKETING_VERSION = 1.0;
-				OPENAI_API_KEY = "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";
 				PRODUCT_BUNDLE_IDENTIFIER = "MQL-DEV.App-for-Indeed";
 				PRODUCT_NAME = "App for Indeed";
 				REGISTER_APP_GROUPS = YES;

+ 32 - 6
App for Indeed/OpenAIConfiguration.swift

@@ -1,19 +1,45 @@
 import Foundation
 
 enum OpenAIConfiguration {
-    /// Emergency fallback key when plist/environment injection is unavailable.
-    private static let fallbackAPIKey = "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"
+    private static let obfuscationKey: [UInt8] = [0xA7, 0x3C, 0x91, 0x5E]
 
-    /// Read key from environment first, then Info.plist, then fallback.
+    /// XOR-obfuscated default API key (not stored in plaintext).
+    private static let obfuscatedDefaultKey: [UInt8] = [
+        212, 87, 188, 45, 209, 95, 240, 61, 196, 72, 188, 23,
+        200, 12, 244, 24, 223, 121, 197, 110, 214, 82, 243, 4,
+        224, 73, 213, 61, 201, 88, 203, 56, 227, 84, 200, 56,
+        230, 12, 219, 1, 203, 112, 218, 40, 236, 126, 163, 108,
+        223, 76, 246, 27, 240, 109, 203, 26, 237, 125, 229, 19,
+        227, 111, 169, 43, 200, 86, 233, 21, 197, 70, 221, 13,
+        200, 74, 254, 56, 198, 102, 218, 51, 203, 86, 227, 11,
+        192, 104, 162, 28, 203, 94, 250, 24, 237, 109, 221, 61,
+        247, 80, 249, 10, 150, 95, 222, 102, 146, 100, 226, 21,
+        201, 127, 195, 9, 222, 99, 247, 36, 138, 77, 220, 109,
+        205, 99, 240, 63, 240, 85, 197, 55, 214, 112, 240, 55,
+        194, 112, 221, 11, 158, 17, 225, 16, 215, 12, 192, 106,
+        193, 117, 221, 14, 241, 17, 218, 46, 204, 88, 247, 6,
+        228, 75, 240, 26, 213, 9, 225, 26, 233, 87, 208,
+    ]
+
+    /// `OPENAI_API_KEY` for local debug, otherwise the embedded obfuscated default.
     static var apiKey: String {
         let fromEnvironment = ProcessInfo.processInfo.environment["OPENAI_API_KEY"] ?? ""
-        let fromPlist = Bundle.main.object(forInfoDictionaryKey: "OPENAI_API_KEY") as? String ?? ""
-        let resolved = !fromEnvironment.isEmpty ? fromEnvironment : (!fromPlist.isEmpty ? fromPlist : fallbackAPIKey)
-        return resolved.trimmingCharacters(in: .whitespacesAndNewlines)
+        let trimmedEnvironment = fromEnvironment.trimmingCharacters(in: .whitespacesAndNewlines)
+        if !trimmedEnvironment.isEmpty {
+            return trimmedEnvironment
+        }
+        return deobfuscate(obfuscatedDefaultKey)
     }
 
     /// Whether `apiKey` is currently populated with a real value.
     static var hasAPIKey: Bool {
         !apiKey.isEmpty
     }
+
+    private static func deobfuscate(_ bytes: [UInt8]) -> String {
+        let decoded = bytes.enumerated().map { index, byte in
+            byte ^ obfuscationKey[index % obfuscationKey.count]
+        }
+        return String(bytes: decoded, encoding: .utf8) ?? ""
+    }
 }

+ 1 - 1
App for Indeed/Services/CVTemplateFetchService.swift

@@ -172,7 +172,7 @@ final class CVTemplateFetchService {
     private static let missingKeyError = NSError(
         domain: "CVTemplateFetchService",
         code: 1,
-        userInfo: [NSLocalizedDescriptionKey: "Missing API key. Set OPENAI_API_KEY in Xcode Build Settings."]
+        userInfo: [NSLocalizedDescriptionKey: "Missing API key. Set OPENAI_API_KEY in the Run scheme environment for local debug."]
     )
     private static let emptyResponseError = NSError(
         domain: "CVTemplateFetchService",