Install
$ agentstack add skill-d1dx-secret-capture-skill-secret-capture-skill ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Secret capture
Invoke this skill whenever you need the user to supply a credential. Never ask the user to paste a secret into chat or the terminal — use this skill instead.
When to use
- Onboarding a new MCP, integration, or service that needs an API key
- Rotating an existing credential
- Creating a new n8n credential, Cloudflare Worker secret, GitHub secret, Coolify env var
- Storing a personal API key the user will consume from their shell / agents (→ 1Password)
- Any time mid-task you realize "I need a credential here"
Decision rule — which destination
One secret, one home. Pick based on who consumes the value:
- User consumes it (terminal, agents, MCPs, HTTP calls from the user's machine) →
1password - System consumes it natively (n8n runs the workflow that uses this token, GitHub Actions runs the workflow, Cloudflare Worker runtime reads it, Coolify app reads the env var) → the matching target adapter
Never store the same secret in two places.
Invocation
The skill is a bash script. Invoke via the Bash tool:
bash ~/.claude/skills/secret-capture/scripts/capture.sh --target [target-flags] [--rotate] [--expect ] [--from ]
You pass only the destination metadata. You never pass, read, or handle the value. By default the script opens a hidden-input dialog, captures the value, pipes it directly to the destination, and returns a reference on stdout. With --from, it pulls the value from an existing store instead of prompting (see [Sourcing the value with --from](#sourcing-the-value-with---from)).
Targets
1password
bash capture.sh --target 1password \
--vault --item --field \
[--category API_CREDENTIAL] [--rotate]
--category accepts the canonical UPPERCASE_UNDERSCORE template id (API_CREDENTIAL, LOGIN, PASSWORD, DATABASE, SECURE_NOTE, SERVER) or a human-readable equivalent ("API Credential", "api-credential") — the adapter normalizes to the canonical form before passing to op item create. Default: API_CREDENTIAL.
Returns: op:////
Use when the user will consume this secret from their machine (op read ...).
keychain
bash capture.sh --target keychain --service --account [--rotate] [--silent-read]
Returns: keychain:
Use for macOS-local CLI tools or scripts that will read it via security find-generic-password.
Pass --silent-read when a background / non-interactive process reads the secret (a cron job, an MCP-launch wrapper, any unattended reader). It opens the item's ACL partition list to Apple-signed CLI tools (apple-tool:,apple:) so /usr/bin/security can read the value without a blocking Keychain auth dialog. Without it, the first non-interactive read pops a GUI prompt that an unattended reader can't answer and fails with "auth prompt dismissed". Setting the ACL requires unlocking the keychain, so it prompts once for your login password at capture time (best-effort — the secret is still stored if you skip the prompt; only silent read is lost).
gh-secret
# repo scope
bash capture.sh --target gh-secret --scope repo --repo --name
# org scope
bash capture.sh --target gh-secret --scope org --org --name
# environment scope
bash capture.sh --target gh-secret --scope env --repo --env --name
Returns:
- repo:
gh-secret:/# - org:
gh-secret:org/# - env:
gh-secret://env/#
Use for GitHub Actions secrets.
wrangler
bash capture.sh --target wrangler --worker --name
Returns: wrangler-secret:#
Use for Cloudflare Workers secrets.
coolify
bash capture.sh --target coolify --coolify-instance --app-uuid --key [--preview]
Returns: coolify-env:#
Instance alias is looked up in the user's config under defaults.coolify.instances. (URL + API key source) — mirrors the n8n adapter's multi-instance shape. Use one alias per Coolify instance (e.g. production, staging, dsh).
Uses Coolify REST directly (bypasses MCP — the MCP response echoes the value back into the transcript). The --preview flag scopes the env var to preview deploys when set.
n8n
bash capture.sh --target n8n --instance --name --type [--data-field ]
Returns: n8n-cred: (id=)
Instance alias is looked up in the user's config (URL + API key). Credential type matches n8n's type name (e.g., anthropicApi, httpHeaderAuth, openAiApi).
env-file
bash capture.sh --target env-file --file --key
Returns: env-file:#
Writes to a local .env file with mode 0600. Creates the file if missing.
ssh
# KEY=VALUE into a remote dotenv / env file:
bash capture.sh --target ssh \
--ssh-host [--ssh-user ] \
--mode file-kv --remote-path --key [--chmod 600]
# Raw value into a remote file (PEM, cert, single-token):
bash capture.sh --target ssh \
--ssh-host [--ssh-user ] \
--mode file-raw --remote-path [--chmod 600]
Returns:
file-kv→ssh-kv:@:#file-raw→ssh-file:@:
Use when you need to inject a secret into a remote server without reading it yourself. Common cases: dotenv on a VPS, docker-compose .env, systemd EnvironmentFile=, TLS key/cert drops, single-token files read by a daemon.
Authentication: uses your ssh-agent (1Password SSH Agent, system agent, or similar). No private key file is read. First-connect uses StrictHostKeyChecking=accept-new.
Atomicity: value is piped over ssh stdin, written to .sc-new with umask 077, chmod'd, then mv'd into place. No partial-write window.
Security: value never touches argv, env, or any shell variable. End-to-end pipe: local stdin → local temp (0600, shredded) → ssh stdin → remote cat > .sc-new → remote mv. Single quotes in the value (file-kv mode) are escaped via sed on the local side.
Idempotency: checks the remote file before writing. file-kv checks for the KEY= prefix; file-raw checks for file existence. Duplicate → DUPLICATE (exit 6) unless --rotate is passed.
keystore
bash capture.sh --target keystore --service --keystore-host \
[--recipient ] [--keystore-dir ] [--keystore-group ] \
[--ssh-user ] [--rotate] [--from ]
Returns: keystore::/.age
Age-encrypts the value to a recipient public key and ships the encrypted blob to a remote keystore host over SSH, landing at /.age. A separate runtime consumer (e.g. keyrun) decrypts it there with the matching age identity — this adapter only provisions.
Resolution (env or flag, nothing baked in):
--keystore-host/$KEYSTORE_HOST— ssh host (required)--keystore-dir/$KEYSTORE_DIR— remote dir (default/etc/keystore)--keystore-group/$KEYSTORE_GROUP— when set, the blob is0640 root:(group-readable by the consumer); else0600root-only--recipient/$KEYSTORE_RECIPIENT/$KEYSTORE_RECIPIENT_FILE— theage1...recipient; falls back to reading$KEYSTORE_DIR/recipient.puboff the host
Security: the value is age-encrypted locally before it leaves the machine — only ciphertext crosses the network. Plaintext never touches argv, env, a shell variable, or the remote host in the clear. Encrypting needs only the public recipient (no secret to provision).
Idempotency: an existing .age is left intact; pass --rotate to overwrite (DUPLICATE, exit 6, otherwise).
> Tip: if you provision many keys to one keystore, wrap this adapter in a small script that exports your KEYSTORE_* defaults so callers just run provision [--from op://…].
Sourcing the value with --from
By default the value comes from the hidden-input dialog. Pass --from to pull it from an existing store instead — useful for shipping a secret that already lives somewhere (e.g. 1Password → keystore) without a human re-typing it:
# bare op:// reference (convenience — mapped to the op: scheme)
bash capture.sh --target keystore --service vultr --keystore-host \
--from op://Vault/vultr-api/credential
# any source-spec also works: op: / keychain: / env: / file: / command:
bash capture.sh --target env-file --file .env --key TOKEN --from keychain:my-token
The pulled value is routed through the same flow as a dialog capture: optional --expect validation still applies (no interactive retry — a fixed source either matches or returns FORMAT_MISMATCH), a single trailing newline is stripped (so an op read value arrives clean), and the value is shredded on exit. An empty resolution returns AUTH_FAIL (exit 8).
Rotation
Add --rotate to any invocation. The adapter switches from create → edit/update/overwrite. If the record doesn't exist, it fails with NOT_FOUND.
Format validation
Pass --expect to sanity-check the captured value before writing:
bash capture.sh --target 1password --vault Personal --item openai-key --field credential --expect openai
Shapes supported out of the box: openai, anthropic, github-pat, aws-access-key, stripe, cloudflare-token, slack. On mismatch, the dialog reopens once; if still mismatch, returns FORMAT_MISMATCH.
Return values
Every successful invocation prints only a reference string on stdout. The agent uses the reference to consume the secret later without ever seeing the value:
# Bad — the agent asks the user to paste the value
# Good — the agent invokes the skill, gets a reference, uses the reference
ref=$(bash capture.sh --target keychain --service anthropic-cli --account $(whoami))
# → ref = "keychain:anthropic-cli"
# Later, when consuming:
security find-generic-password -s anthropic-cli -w | some-cli --stdin
# The value flows through a pipe; the agent still never sees it.
Error codes (stderr)
CANCELLED— user cancelled the dialogNO_GUI— no window server and no TTY; run from a GUI sessionTIMEOUT— dialog auto-dismissed (osascript 2-min limit)AUTH_FAIL— destination auth failed; fix credentials and retryDUPLICATE— item exists; re-invoke with--rotateor pick a different nameFORMAT_MISMATCH—--expectregex didn't matchADAPTER_ERROR— subcommand failed; check adapter preflight
None of these ever contain the value.
Hygiene guarantees
- Value never on argv (readable via
ps) - Value never in env (readable via
/proc//environ) - Value never in shell history
- Value never in any stdout the agent sees
- Tempfiles are 0600 and shredded on exit
Do NOT
- Never ask the user to paste a secret into chat or the terminal
- Never construct a
curl/ CLI command that contains the value as an argument - Never store the same secret in two places — pick 1Password OR the target system
- Never log the return value to a file that could be shared
- Never skip
--rotatewhen overwriting — use it explicitly so the intent is logged
Configuration
The skill reads ~/.config/secret-capture/config.yaml. Users configure:
- Which adapters are enabled
- Default vault / account / scope
- Where to source API credentials the skill itself needs (Coolify API token, n8n API key) — via
keychain:,env:,file:,op:, orcommand:sources - Dialog timeout, TTY fallback, format validation strictness
See [config.example.yaml](../config.example.yaml) in the skill repo for the full shape.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: D1DX
- Source: D1DX/secret-capture-skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.