secure-keys is a Ruby CLI that generates a SecureKeys.xcframework for iOS apps. It reads secret values from macOS Keychain on local machines or environment variables in CI, encrypts those values with AES-256-GCM, writes a Swift API, builds an XCFramework, and optionally adds that framework to an Xcode target.
Install with Homebrew:
brew tap derian-cordoba/secure-keys
brew install derian-cordoba/secure-keys/secure-keys
Install with RubyGems:
gem install secure-keys
Install with Bundler:
gem 'secure-keys'
bundle install
From an iOS project root:
security add-generic-password -a "secure-keys" -s "secure-keys" -w "apiKey,githubToken"
security add-generic-password -a "secure-keys" -s "apiKey" -w "your-api-key"
security add-generic-password -a "secure-keys" -s "githubToken" -w "your-github-token"
secure-keys
The command creates .secure-keys/SecureKeys.xcframework.
Use the generated framework from Swift:
import SecureKeys
let apiKey = SecureKey.apiKey.decryptedValue
let githubToken = key(for: .githubToken)
secure-keys does not create third-party API keys for providers such as GitHub, Firebase, Stripe, or AWS. Those secrets must be created in their owning service first.
The tool generates an iOS framework that contains encrypted copies of the secret values you provide:
SecureKey enum with encrypted byte arrays..secure-keys/SecureKeys.xcframework.The default Keychain service and account identifier is secure-keys.
Store the list of secret names:
security add-generic-password -a "secure-keys" -s "secure-keys" -w "githubToken,apiKey"
Store each secret value under the same Keychain service:
security add-generic-password -a "secure-keys" -s "githubToken" -w "your-github-token"
security add-generic-password -a "secure-keys" -s "apiKey" -w "your-api-key"
Use a custom Keychain identifier:
export SECURE_KEYS_IDENTIFIER="my-app-secrets"
security add-generic-password -a "$SECURE_KEYS_IDENTIFIER" -s "$SECURE_KEYS_IDENTIFIER" -w "githubToken,apiKey"
security add-generic-password -a "$SECURE_KEYS_IDENTIFIER" -s "githubToken" -w "your-github-token"
security add-generic-password -a "$SECURE_KEYS_IDENTIFIER" -s "apiKey" -w "your-api-key"
Use a custom delimiter:
export SECURE_KEYS_DELIMITER="|"
security add-generic-password -a "secure-keys" -s "secure-keys" -w "githubToken|apiKey"
CI mode is enabled automatically when common CI variables are present, including CI=true and GITHUB_ACTIONS=true. You can also force it:
secure-keys --ci
Set the list of secret names in SECURE_KEYS_IDENTIFIER:
export SECURE_KEYS_IDENTIFIER="github-token,api_key,firebaseToken"
Set each secret value as an environment variable. Secret names are looked up exactly first, then normalized by converting - to _ and uppercasing.
export GITHUB_TOKEN="your-github-token"
export API_KEY="your-api-key"
export FIREBASETOKEN="your-firebase-token"
The SECURE_KEYS_ prefix is also supported for secret values:
export SECURE_KEYS_API_KEY="your-api-key"
You can store the list in a dedicated environment variable instead:
export CUSTOM_SECRET_LIST="github-token,api_key"
secure-keys --ci --identifier CUSTOM_SECRET_LIST
Use a custom delimiter in CI:
export SECURE_KEYS_DELIMITER="|"
export SECURE_KEYS_IDENTIFIER="github-token|api_key|firebaseToken"
secure-keys --help
Usage: secure-keys [--options]
-h, --help Use the provided commands to select the params
--ci Enable CI mode (default: false)
-d, --delimiter DELIMITER The delimiter to use for the key access (default: ",")
--[no-]generate Generate the SecureKeys.xcframework
-i, --identifier IDENTIFIER The identifier to use for the key access (default: "secure-keys")
--verbose Enable verbose mode (default: false)
-v, --version Show the secure-keys version
--xcframework Add the xcframework to the target
Examples:
secure-keys
secure-keys --verbose
secure-keys --ci --identifier CUSTOM_SECRET_LIST
secure-keys --identifier "my-app-secrets" --delimiter "|"
secure-keys -i "my-app-secrets" -d "|"
secure-keys env manages distinct secret sets for multiple environments — development, staging, and production — each with its own identifier, key list, secret source, and output path. Configuration is driven by a .secure-keys.yml file in your project root.
Create a default configuration file:
secure-keys env init
This writes .secure-keys.yml to the current directory with a development/staging/production template. Edit the file to match your project’s secrets:
environments:
development:
identifier: my-app-dev # Keychain service name
delimiter: ","
source: keychain # Read from macOS Keychain
keys:
- apiKey
- debugToken
- analyticsKey
output: .secure-keys/development
staging:
identifier: my-app-staging
delimiter: ","
source: environment # Read from environment variables
keys:
- apiKey
- analyticsKey
output: .secure-keys/staging
production:
identifier: my-app-prod
delimiter: ","
source: environment
keys:
- apiKey
- analyticsKey
output: .secure-keys/production
| Field | Purpose |
|---|---|
identifier |
Keychain service name (local) or environment variable holding the key list (CI) |
delimiter |
Character that separates key names in the Keychain value (default: ,) |
source |
keychain for local development, environment for CI/CD |
keys |
List of secret names to encrypt into the xcframework |
output |
Directory where SecureKeys.xcframework is written |
Install development secrets into the Keychain once:
IDENTIFIER="my-app-dev"
security add-generic-password -a "$IDENTIFIER" -s "$IDENTIFIER" \
-w "apiKey,debugToken,analyticsKey" -U
security add-generic-password -a "$IDENTIFIER" -s "apiKey" -w "<your-api-key>" -U
security add-generic-password -a "$IDENTIFIER" -s "debugToken" -w "<your-debug-token>" -U
security add-generic-password -a "$IDENTIFIER" -s "analyticsKey" -w "<your-analytics-key>" -U
Generate the development xcframework:
secure-keys env generate development
The framework is written to .secure-keys/development/SecureKeys.xcframework.
For environments with source: environment, export the key list and each secret value as environment variables before running generate:
export MY_APP_STAGING="apiKey,analyticsKey"
export apiKey="your-staging-api-key"
export analyticsKey="your-staging-analytics-key"
secure-keys env generate staging
The key-list variable name is the identifier value with - replaced by _ and uppercased (e.g. my-app-staging → MY_APP_STAGING). Individual secret values follow the same lookup order as the core CLI: exact name first, then normalized (- → _, uppercased), then with the SECURE_KEYS_ prefix.
Usage: secure-keys env [subcommand] [--options]
-h, --help Show help for the env command
Subcommands:
init Create a default .secure-keys.yml configuration file
list List all configured environments
generate [name] Generate an xcframework for the given environment
diff <a> <b> Compare two configured environments
env initsecure-keys env init
Creates .secure-keys.yml in the current directory from a default template. Exits with code 1 if the file already exists.
env listsecure-keys env list
Prints all environment names defined in .secure-keys.yml.
env generate# Generate for a specific environment
secure-keys env generate development
secure-keys env generate staging
secure-keys env generate production
# Generate for all configured environments at once
secure-keys env generate --all
Each environment’s xcframework is written to its configured output directory. Running --all generates one xcframework per environment without overwriting outputs from previous environments.
Usage: secure-keys env generate [name] [--options]
-h, --help Show help for the env generate subcommand
--all Generate xcframeworks for all configured environments (default: false)
env diffsecure-keys env diff development production
Compares two environments and reports differences in their configuration and key lists. Useful before a release to confirm that staging and production are in sync.
Comparing: development → production
──────────────────────────────────────────────────────────────────────
Configuration: identical
Keys (development: 3, production: 2):
✓ apiKey
✓ analyticsKey
− debugToken (only in development)
──────────────────────────────────────────────────────────────────────
Each generate call writes its xcframework to the output path defined for that environment:
.secure-keys/
├── development/
│ └── SecureKeys.xcframework
├── staging/
│ └── SecureKeys.xcframework
└── production/
└── SecureKeys.xcframework
Link the appropriate xcframework in Xcode under General → Frameworks, Libraries, and Embedded Content. Use one Xcode scheme per environment, each pointing to the matching .secure-keys/<environment>/SecureKeys.xcframework.
Add .secure-keys/ to .gitignore to avoid committing generated xcframeworks:
.secure-keys/
Generate the framework only:
secure-keys
Generate and add the framework to an Xcode target:
secure-keys --xcframework --target "YourTargetName" --add
Replace an existing framework reference:
secure-keys --xcframework --target "YourTargetName" --replace
Add an already generated framework without rebuilding:
secure-keys --no-generate --xcframework --target "YourTargetName"
Select a project explicitly:
secure-keys --xcframework --target "YourTargetName" --xcodeproj "/path/to/YourProject.xcodeproj"
The same options can be configured with environment variables:
export SECURE_KEYS_XCFRAMEWORK_TARGET="YourTargetName"
export SECURE_KEYS_XCFRAMEWORK_ADD=true
export SECURE_KEYS_XCFRAMEWORK_REPLACE=false
export SECURE_KEYS_XCFRAMEWORK_XCODEPROJ="/path/to/YourProject.xcodeproj"
secure-keys --xcframework
Short environment variable names are also supported:
export XCFRAMEWORK_TARGET="YourTargetName"
export XCFRAMEWORK_ADD=true
export XCFRAMEWORK_REPLACE=false
export XCFRAMEWORK_XCODEPROJ="/path/to/YourProject.xcodeproj"
If you do not use --xcframework, add the framework manually:
General..secure-keys/SecureKeys.xcframework to Frameworks, Libraries, and Embedded Content.Build Settings.$(SRCROOT)/.secure-keys to Framework Search Paths.The generated framework exposes SecureKey, key(for:), key(_:), and a String.secretKey helper.
import SecureKeys
let apiKey = SecureKey.apiKey.decryptedValue
let githubToken = key(for: .githubToken)
let sameGithubToken = key(.githubToken)
let keyFromString: SecureKey = "apiKey".secretKey
let valueFromString = "apiKey".secretKey.decryptedValue
let staticValue = String.key(for: .apiKey)
Generated key names are camelized for Swift enum cases:
api-key -> SecureKey.apiKey
githubToken -> SecureKey.githubToken
The main output is:
.secure-keys/SecureKeys.xcframework
Temporary Swift package and build files are created under .secure-keys during generation and removed after the framework is built.
.secure-keys/SecureKeys.xcframework unless your release process intentionally requires it..env files or raw secret values.secure-keys ships both a CLI and a Ruby API for validating individual secret values and scanning source files or git diffs for accidentally exposed credentials.
Scan the current directory:
secure-keys validate scan
Scan a specific path:
secure-keys validate scan ./src
Scan only staged git changes (useful as a pre-commit hook):
secure-keys validate scan --staged
Save the report as JSON:
secure-keys validate scan --output report.json
Override the file extensions and exclusions:
secure-keys validate scan --extensions .rb,.swift,.go --excludes vendor,tmp,build
Enable verbose output:
secure-keys validate scan --verbose
Full option reference:
Usage: secure-keys validate scan [path] [--options]
-h, --help Show help for the scan subcommand
--staged Scan staged git changes instead of a directory (default: false)
-o, --output FILE Save the scan report as JSON to FILE
--extensions Comma-separated file extensions to scan (e.g. .rb,.swift)
--excludes Comma-separated directory names to exclude from the scan
--verbose Enable verbose output (default: false)
Exit codes:
| Code | Meaning |
|---|---|
0 |
Scan completed with no findings |
1 |
One or more secrets were detected |
SecureKeys::Validation::Validator checks a single value against a set of security rules and returns a ValidationResult.
require 'validation/validator'
validator = SecureKeys::Validation::Validator.new
result = validator.validate(key: :api_key, value: ENV['API_KEY'])
puts result.summary # ✅ api_key — no issues / ❌ api_key — 2 issue(s)
puts result.valid? # true / false
puts result.severity_level # :ok | :warning | :error | :critical
result.print # formatted report to stdout
Available validation options:
| Option | Type | Default | Description |
|---|---|---|---|
check_entropy |
Boolean | false |
Flag low-entropy (repetitive) values |
allow_production |
Boolean | false |
Skip the production-key warning |
warn_on_pattern |
Boolean | false |
Emit an informational notice when a pattern matches |
result = validator.validate(
key: :stripe_key,
value: ENV['STRIPE_KEY'],
options: { check_entropy: true, warn_on_pattern: true }
)
Detect the type of a secret value:
info = validator.detect_type(value: 'ghp_abc...')
# => { type: :github_token, description: "GitHub Personal Access Token", severity: :high, ... }
Get provider-specific security recommendations:
validator.recommendations(key: :githubToken)
# => ["Use GitHub Personal Access Tokens with minimal required scopes", ...]
SecureKeys::Validation::Scanner scans source files or git diffs for credentials that match any of the 25+ built-in patterns.
Scan a directory:
require 'validation/scanner'
scanner = SecureKeys::Validation::Scanner.new
result = scanner.scan_directory(path: '.')
puts result.clean? # true if no findings
puts result.files_count # number of files scanned
result.findings.each { |f| puts f.to_s }
result.print if !result.clean?
Scan only staged git changes (useful in a pre-commit hook):
result = scanner.scan_git_diff # staged only (default)
result = scanner.scan_git_diff(staged_only: false) # staged + unstaged
Customize the scan at initialization or per call:
scanner = SecureKeys::Validation::Scanner.new(
options: {
extensions: ['.rb', '.swift', '.go'],
excludes: ['vendor', 'node_modules', '.git'],
max_depth: 5
}
)
Filter findings by severity:
result.by_severity(severity: :critical).each { |f| puts f.to_s }
The scanner recognizes the following secret types out of the box:
| Pattern | Severity |
|---|---|
| GitHub personal / OAuth / App / refresh token | high |
| AWS access key ID | critical |
| AWS secret access key | critical |
| Google Cloud API key | high |
| Google OAuth token | high |
| Stripe secret key (live / test) | critical |
| Stripe publishable / restricted key | medium / high |
| Slack bot / app / webhook token | high / medium |
| JWT token | medium |
| PEM / RSA / EC / OpenSSH private key | critical |
| Generic API key assignment | medium |
| Generic secret / password assignment | medium |
| Firebase API key | medium |
| Twilio API key / Account SID | high / low |
| SendGrid API key | high |
| Mailchimp API key | medium |
| Square access token | high |
| PayPal Braintree access token | critical |
| Heroku API key | high |
| Base64-encoded secret | low |
| Suspicious assignment (catch-all) | low |
All thresholds can be overridden with environment variables. Both the bare name and the SECURE_KEYS_ prefix are supported.
| Environment variable | Default | Description |
|---|---|---|
SECURE_KEYS_API_KEY_LENGTH |
20 |
Minimum API key length |
SECURE_KEYS_TOKEN_LENGTH |
20 |
Minimum token length |
SECURE_KEYS_SECRET_LENGTH |
16 |
Minimum secret length |
SECURE_KEYS_PASSWORD_LENGTH |
12 |
Minimum password length |
SECURE_KEYS_KEY_LENGTH |
16 |
Minimum generic key length |
SECURE_KEYS_SCAN_EXTENSIONS |
.swift,.rb,.py,.js,... |
Comma-separated file extensions to scan |
SECURE_KEYS_SCAN_EXCLUDES |
.git,node_modules,Pods,... |
Comma-separated names to exclude |
SECURE_KEYS_MAX_SCAN_DEPTH |
10 |
Maximum directory traversal depth |
SECURE_KEYS_MIN_ENTROPY_THRESHOLD |
3.0 |
Shannon entropy threshold for check_entropy |
Error fetching the key from Keychain
Verify the Keychain service, account, and identifier values. For the default configuration, the list must be stored with account secure-keys and service secure-keys.
Error fetching the key from ENV variables
Verify CI mode is enabled and that each configured key has a matching environment variable. For example, github-token maps to GITHUB_TOKEN.
xcodebuild fails
Verify Xcode command line tools are installed and selected:
xcode-select -p
SecureKeys.xcframework is not found by Xcode
Verify $(SRCROOT)/.secure-keys is present in Framework Search Paths and that the framework is attached to the correct target.
Install dependencies:
bundle install
Run the test suite:
bundle exec rspec
Run the local CLI:
bundle exec ./bin/secure-keys --help
This project is licensed under the MIT License.