AgentStack
SKILL verified MIT Self-run

Secret Capture

skill-d1dx-secret-capture-skill-secret-capture-skill · by D1DX

Capture a secret from the user via a hidden-input dialog (or pull it from an existing store with --from) and route it to exactly one destination (1Password, macOS Keychain, GitHub secret, Cloudflare Workers secret, Coolify env var, n8n credential, a local .env file, a remote SSH file, or an age-encrypted box keystore) without the value ever appearing in any tool result, log, or chat transcript. A…

No reviews yet
0 installs
2 views
0.0% view→install

Install

$ agentstack add skill-d1dx-secret-capture-skill-secret-capture-skill

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

Are you the author of Secret Capture? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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-kvssh-kv:@:#
  • file-rawssh-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 is 0640 root: (group-readable by the consumer); else 0600 root-only
  • --recipient / $KEYSTORE_RECIPIENT / $KEYSTORE_RECIPIENT_FILE — the age1... recipient; falls back to reading $KEYSTORE_DIR/recipient.pub off 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 dialog
  • NO_GUI — no window server and no TTY; run from a GUI session
  • TIMEOUT — dialog auto-dismissed (osascript 2-min limit)
  • AUTH_FAIL — destination auth failed; fix credentials and retry
  • DUPLICATE — item exists; re-invoke with --rotate or pick a different name
  • FORMAT_MISMATCH--expect regex didn't match
  • ADAPTER_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 --rotate when 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:, or command: 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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.