---
title: Quản lý secret bằng xcconfig - không commit vào Git
impact: CRITICAL
impactDescription: Secret hardcode trong source code bị lộ qua Git history vĩnh viễn, kể cả sau khi xóa. Hàng nghìn app iOS thực tế đã bị leak API key vì vấn đề này.
tags: swift, ios, secrets, api-keys, xcconfig, security, git
---

## Quản lý secret bằng xcconfig - không commit vào Git

API key, client secret, private key không được xuất hiện trong file `.swift`, `.plist` hay bất kỳ file nào được commit. Dùng `.xcconfig` file riêng theo môi trường và thêm vào `.gitignore`. Với runtime secrets (token), lưu trong Keychain.

**Incorrect (secret trong source code):**

```swift
// !! Sẽ bị lộ qua git log dù đã xóa sau này
struct APIConfig {
    static let googleMapsKey = "AIzaSyD-actual-real-key-here"
    static let stripePublishableKey = "pk_live_actual-stripe-key"
    static let firebaseWebAPIKey = "AIzaSyB-firebase-key"
}

// !! Secret trong plist cũng không an toàn nếu commit
// GoogleService-Info.plist với CURRENT_KEY hardcode và commit vào git
```

**Correct (xcconfig + Keychain cho runtime secrets):**

```swift
// Bước 1: Tạo file Secrets.xcconfig (thêm vào .gitignore ngay)
// Secrets.xcconfig (KHÔNG commit):
// GOOGLE_MAPS_API_KEY = AIzaSyD-actual-key
// STRIPE_PUBLISHABLE_KEY = pk_live_key

// Bước 2: Config.xcconfig (commit được, reference tới Secrets)
// #include "Secrets.xcconfig"
// API_KEY = $(GOOGLE_MAPS_API_KEY)

// Bước 3: Info.plist - đọc từ build setting
// <key>GoogleMapsAPIKey</key>
// <string>$(API_KEY)</string>

// Bước 4: Đọc trong Swift code
struct APIConfig {
    static var googleMapsKey: String {
        guard let key = Bundle.main.infoDictionary?["GoogleMapsAPIKey"] as? String,
              !key.isEmpty else {
            #if DEBUG
            fatalError("GoogleMapsAPIKey not configured. Create Secrets.xcconfig")
            #else
            return ""
            #endif
        }
        return key
    }
}

// Bước 5: CI/CD - inject via environment variable
// fastlane/Fastfile:
// xcconfig_contents = "GOOGLE_MAPS_API_KEY = #{ENV['GOOGLE_MAPS_API_KEY']}"
// File.write("Secrets.xcconfig", xcconfig_contents)

// Runtime secrets (OAuth tokens) - lưu Keychain sau khi nhận từ server
class SecureStorage {
    static func saveToken(_ token: String, key: String) throws {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecValueData as String: token.data(using: .utf8)!,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]
        SecItemDelete(query as CFDictionary)
        let status = SecItemAdd(query as CFDictionary, nil)
        guard status == errSecSuccess else {
            throw KeychainError.saveFailed(status)
        }
    }
}
```

**Tools:** `.gitignore`, `git-secrets`, `detect-secrets`, Fastlane environment variables

