sf-secret-inject
v0.1.0
Published
SF CLI plugin to securely inject secrets into Salesforce External Credential principals
Maintainers
Readme
sf-secret-inject
SF CLI plugin to securely inject secrets into Salesforce External Credential principals.
Salesforce strips secret values from all Metadata API payloads by design. This plugin provides the missing step: after sf project deploy creates your External Credential structure, sf secret inject pushes the actual secret values via the Named Credentials REST API.
Prerequisites
Before injecting secrets, three things must exist in the target org:
- Named Credential — the parent container that defines the callout endpoint
- External Credential + Principal — defines the auth protocol and the identity that holds the secrets
- Permission Set — grants users access to the External Credential principal
Deploy all three with sf project deploy, then assign the permission set:
sf project deploy start --source-dir force-app --target-org myOrg
sf org assign permset --name MyApiCred_Access --target-org myOrgInstallation
sf plugins trust allowlist add -n sf-secret-inject
sf plugins install sf-secret-injectClaude Code skill
The plugin ships with a bundled Claude Code skill that lets Claude assist with the full injection workflow — generating .ec-inject.yaml files, troubleshooting prerequisite errors, and running sf secret inject on your behalf. The skill auto-triggers on phrases like "inject secrets into External Credential", "sf secret inject", or "External Credential principals".
sf secret install-skillThis copies SKILL.md to ~/.claude/skills/sf-secret-inject/. Restart Claude Code to activate it.
Local dev contributors can symlink instead so edits show up live:
ln -sfn "$(pwd)/skill" ~/.claude/skills/sf-secret-injectThe .ec-inject.yaml file
Each External Credential gets a config file co-located with its .externalCredential-meta.xml. It maps field names to secret references — never to secret values. It is safe to commit to source control.
OAuth Client Credentials:
credential: MyApiCred # ExternalCredential DeveloperName
principal: MyPrincipal # Principal principalName
protocol: oauth-client-credentials
secrets:
clientId:
source: system # 'system' = OS keychain, 'env' = environment variable
ref: MyApiCred.ClientID # lookup key passed to the backend
clientSecret:
source: system
ref: MyApiCred.ClientSecretBasic Auth:
credential: MyBasicCred
principal: MyPrincipal
protocol: basic-auth
secrets:
username:
source: env
ref: MyBasicCred.Username # resolves to env var MYBASICCRED_USERNAME
password:
source: env
ref: MyBasicCred.PasswordField names by protocol:
| Protocol | Field names |
|----------|-------------|
| oauth-client-credentials | clientId, clientSecret |
| basic-auth | username, password |
Usage
Store a secret locally (one-time per machine)
sf secret store --credential MyApiCred --field clientId --target-org myOrgPrompts for the value with hidden input. Stored in the OS keychain under service sf-secret-inject, account myOrg.MyApiCred.clientId.
Inject secrets into an org
sf secret inject --config force-app/main/default/externalCredentials/MyApiCred.ec-inject.yaml --target-org myOrgInject all .ec-inject.yaml files found under the current directory:
sf secret inject --all --target-org myOrgList External Credentials in an org
sf secret list --target-org myOrgSecret backends
| Source | How secrets are resolved | Best for |
|--------|--------------------------|----------|
| system | OS keychain scoped to {orgAlias}.{ref} | Local development; multiple sandbox environments on one machine |
| env | Environment variable — ref uppercased with dots replaced by underscores | CI/CD pipelines |
The --source flag overrides the source field for all entries in the YAML file.
CI/CD usage
Store secrets as CI environment variables using the naming convention {REF_UPPERCASE_WITH_UNDERSCORES}:
# GitHub Actions
env:
MYAPICRED_CLIENTID: ${{ secrets.MYAPICRED_CLIENTID }}
MYAPICRED_CLIENTSECRET: ${{ secrets.MYAPICRED_CLIENTSECRET }}Then inject with --source env:
sf secret inject --all --target-org $SF_ORG_ALIAS --source envFull deployment workflow
# 1. Deploy structure
sf project deploy start --source-dir force-app --target-org myOrg
# 2. Assign the permission set
sf org assign permset --name MyApiCred_Access --target-org myOrg
# 3. Inject secrets
sf secret inject --config force-app/main/default/externalCredentials/MyApiCred.ec-inject.yaml --target-org myOrgCommands
sf help [COMMAND]
Display help for sf.
USAGE
$ sf help [COMMAND...] [-n]
ARGUMENTS
[COMMAND...] Command to show help for.
FLAGS
-n, --nested-commands Include all nested commands in the output.
DESCRIPTION
Display help for sf.See code: @oclif/plugin-help
sf secret inject
Inject secrets from a local keychain or environment into a Salesforce External Credential principal.
USAGE
$ sf secret inject -o <value> [--json] [--flags-dir <value>] [--all | --config <value>] [--source system|env]
FLAGS
-o, --target-org=<value> (required) Username or alias of the target org. Not required if the `target-org`
configuration variable is already set.
--all Discover and process all *.ec-inject.yaml files under the current directory. Mutually
exclusive with --config.
--config=<value> Path to the .ec-inject.yaml file that describes this credential injection.
--source=<option> Override the secret backend for all entries in the YAML file. If omitted, each entry uses
the "source" field from the YAML.
<options: system|env>
GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.
DESCRIPTION
Inject secrets from a local keychain or environment into a Salesforce External Credential principal.
Reads secret values from the configured backend (system keychain or environment
variables) and POSTs them to the Named Credentials REST endpoint. If the
principal already exists the command retries with a PUT so the operation is
always idempotent.
Secret values are never logged or echoed to stdout or stderr.
EXAMPLES
$ sf secret inject --config .ec-inject.yaml --target-org myOrg
$ sf secret inject --all --target-org myOrg
$ sf secret inject --config .ec-inject.yaml --target-org myOrg --source env
FLAG DESCRIPTIONS
-o, --target-org=<value>
Username or alias of the target org. Not required if the `target-org` configuration variable is already set.
Alias or username of the org to inject into.See code: src/commands/secret/inject.ts
sf secret install-skill
[DEPRECATED] Install the sf-secret-inject Claude Code skill to ~/.claude/skills/.
USAGE
$ sf secret install-skill [--json] [--flags-dir <value>]
GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.
DESCRIPTION
[DEPRECATED] Install the sf-secret-inject Claude Code skill to ~/.claude/skills/.
Copies the bundled SKILL.md to ~/.claude/skills/sf-secret-inject/ so Claude Code can use it as a skill.
EXAMPLES
$ sf secret install-skillSee code: src/commands/secret/install-skill.ts
sf secret list
List all External Credentials and their principals in the target org.
USAGE
$ sf secret list -o <value> [--json] [--flags-dir <value>]
FLAGS
-o, --target-org=<value> (required) Username or alias of the target org. Not required if the `target-org`
configuration variable is already set.
GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.
DESCRIPTION
List all External Credentials and their principals in the target org.
Queries the Tooling API for External Credential records and prints a table
showing each credential name, authentication protocol, principal names, and
per-principal authentication status.
Use --json for structured output suitable for scripting.
EXAMPLES
$ sf secret list --target-org myOrg
$ sf secret list --target-org myOrg --json
FLAG DESCRIPTIONS
-o, --target-org=<value>
Username or alias of the target org. Not required if the `target-org` configuration variable is already set.
Alias or username of the org to query.See code: src/commands/secret/list.ts
sf secret store
Store a secret in the local keychain (or print the env var name) for later use by sf secret inject.
USAGE
$ sf secret store --credential <value> --field <value> -o <value> [--json] [--flags-dir <value>] [--source
system|env]
FLAGS
-o, --target-org=<value> (required) Username or alias of the target org. Not required if the `target-org`
configuration variable is already set.
--credential=<value> (required) Developer name of the External Credential (e.g. MyApiCred).
--field=<value> (required) Field name to store (e.g. clientId, clientSecret, username, password).
--source=<option> [default: system] Backend to store the secret in. "system" writes to the OS keychain. "env"
prints the env var name you need to export instead.
<options: system|env>
GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.
DESCRIPTION
Store a secret in the local keychain (or print the env var name) for later use by `sf secret inject`.
Prompts for a secret value (input is hidden) and stores it under the scoped
account key "{orgAlias}.{credential}.{field}" in the system keychain service
"sf-secret-inject".
The stored key format directly matches what the YAML "ref" field should
contain, so you can copy the printed account name straight into your
.ec-inject.yaml.
When --source env is specified, no value is stored. The command instead prints
the environment variable name you need to export before running inject.
EXAMPLES
$ sf secret store --credential MyApiCred --field clientSecret --target-org myOrg
$ sf secret store --credential MyApiCred --field clientId --target-org myOrg --source system
$ sf secret store --credential MyApiCred --field clientSecret --target-org myOrg --source env
FLAG DESCRIPTIONS
-o, --target-org=<value>
Username or alias of the target org. Not required if the `target-org` configuration variable is already set.
Alias or username of the org. Used as the scope prefix for the keychain key.See code: src/commands/secret/store.ts
Security notes
- Secret values are never logged to stdout or stderr
- The
.ec-inject.yamlfile contains only lookup references, never secret values — it is safe to commit - The
systembackend scopes keys per org alias, so secrets for different sandboxes never collide on a shared machine sf secret injectis idempotent — POST on first run, PUT on subsequent runs
