Quellcode durchsuchen

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 vor 2 Wochen
Ursprung
Commit
7f26f68326

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

@@ -263,13 +263,11 @@
263 263
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
264 264
 				INFOPLIST_KEY_NSMainStoryboardFile = Main;
265 265
 				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
266
-				INFOPLIST_KEY_OPENAI_API_KEY = "$(OPENAI_API_KEY)";
267 266
 				LD_RUNPATH_SEARCH_PATHS = (
268 267
 					"$(inherited)",
269 268
 					"@executable_path/../Frameworks",
270 269
 				);
271 270
 				MARKETING_VERSION = 1.0;
272
-				OPENAI_API_KEY = "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";
273 271
 				PRODUCT_BUNDLE_IDENTIFIER = "MQL-DEV.App-for-Indeed";
274 272
 				PRODUCT_NAME = "App for Indeed";
275 273
 				REGISTER_APP_GROUPS = YES;
@@ -302,13 +300,11 @@
302 300
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
303 301
 				INFOPLIST_KEY_NSMainStoryboardFile = Main;
304 302
 				INFOPLIST_KEY_NSPrincipalClass = NSApplication;
305
-				INFOPLIST_KEY_OPENAI_API_KEY = "$(OPENAI_API_KEY)";
306 303
 				LD_RUNPATH_SEARCH_PATHS = (
307 304
 					"$(inherited)",
308 305
 					"@executable_path/../Frameworks",
309 306
 				);
310 307
 				MARKETING_VERSION = 1.0;
311
-				OPENAI_API_KEY = "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";
312 308
 				PRODUCT_BUNDLE_IDENTIFIER = "MQL-DEV.App-for-Indeed";
313 309
 				PRODUCT_NAME = "App for Indeed";
314 310
 				REGISTER_APP_GROUPS = YES;

+ 32 - 6
App for Indeed/OpenAIConfiguration.swift

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

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

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