active

Secure CLI Execution

Safe
System VerifiedSafe

Teaches agents to run CLI commands, scripts, and tool invocations without leaking secrets through stdout, process arguments, shell history, or execution traces. Covers env-var hygiene, stdin-based secret passing, history suppression, log discipline, and tool-specific patterns for curl, git, Docker, and CI/CD pipelines.

@api/secure-cli-execution

security
cli
secrets
devops
agent-safety

Secure CLI Execution

Teaches agents to run CLI commands, scripts, and tool invocations without exposing secrets, API keys, or credentials through stdout, process argument lists, shell history, execution traces, or any other observable channel.

Why This Matters

Secrets can leak through more channels than most agents account for:

ChannelExample leakRisk
stdout/stderrecho $API_KEYCaptured in traces, logs, terminal history
Process argscurl --token abc123Visible in ps aux, /proc/<pid>/cmdline
Shell historyany command with secret in argPersisted to ~/.bash_history
Temp filesecho $SECRET > /tmp/keyWorld-readable by default, persists on disk
Env var dumpsenv or printenvPrints every variable including secrets
Error messagesstack traces that include configFramework errors may echo config values

Core Rules

Rule 1 — Never pass secrets as command arguments

Process argument lists are public on POSIX systems.

bash
# BAD — visible in ps aux and shell history
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/data
npx some-cli --api-key "$STRIPE_KEY" deploy
docker build --build-arg SECRET_KEY="$SECRET_KEY" .

# GOOD — reference the env var; the shell expands it but it never touches args
# curl reads Authorization from env via a config file
curl -K <(echo "header = \"Authorization: Bearer $TOKEN\"") https://api.example.com/data

# Or use a tool that reads directly from env
STRIPE_KEY="$STRIPE_KEY" npx some-cli deploy

Rule 2 — Reference env vars; never echo or print their values

Checking that a variable is set does not require printing it.

bash
# BAD — emits the secret value
echo "Using key: $ANTHROPIC_API_KEY"
echo $STRIPE_SECRET_KEY
printenv AWS_SECRET_ACCESS_KEY

# GOOD — check presence without revealing value
if [ -z "$ANTHROPIC_API_KEY" ]; then
  echo "ANTHROPIC_API_KEY is not set" >&2
  exit 1
fi
echo "ANTHROPIC_API_KEY is set (${#ANTHROPIC_API_KEY} chars)"

# Check length only — confirms the var exists and is non-trivial
echo "Key length: $(printenv ANTHROPIC_API_KEY | wc -c) chars"

Rule 3 — Use stdin patterns for password-style inputs

Many CLIs accept secrets via stdin to avoid argument exposure.

bash
# BAD — password in args
docker login --username user --password "$DOCKER_PASSWORD" registry.example.com
gh auth login --with-token "$GH_TOKEN"

# GOOD — pipe through stdin
echo "$DOCKER_PASSWORD" | docker login --username user --password-stdin registry.example.com
echo "$GH_TOKEN" | gh auth login --with-token

# GOOD — process substitution (bash/zsh only)
git clone https://user:$(cat <(printenv GIT_PASSWORD))@github.com/org/repo.git

Rule 4 — Suppress shell history for sensitive sessions

bash
# Prevent the current session's commands from being saved
unset HISTFILE

# Or prefix commands with a space (requires HISTCONTROL=ignorespace in bashrc)
 export SECRET_KEY="actual-value-here"   # leading space — not saved to history

# For a block of sensitive operations
(
  unset HISTFILE
  # ... sensitive commands here ...
)

Rule 5 — Secure temporary credential files

When a tool absolutely requires a file on disk:

bash
# BAD — world-readable temp file, never cleaned up
echo "$PRIVATE_KEY" > /tmp/key.pem
some-tool --key /tmp/key.pem

# GOOD — restricted permissions, guaranteed cleanup
KEY_FILE=$(mktemp)
chmod 600 "$KEY_FILE"
trap "rm -f '$KEY_FILE'" EXIT
echo "$PRIVATE_KEY" > "$KEY_FILE"
some-tool --key "$KEY_FILE"
# trap fires on exit — file always removed

Rule 6 — Log outcomes, never secret values

Execution traces and logs should record what happened, not which credential was used.

bash
# BAD — logs the token
echo "Calling API with token: $API_TOKEN"
curl -v -H "Authorization: Bearer $API_TOKEN" https://api.example.com

# GOOD — log the outcome
echo "Calling API..." >&2
RESPONSE=$(curl -s -o /dev/stdout -w "%{http_code}" \
  -H "Authorization: Bearer $API_TOKEN" https://api.example.com)
STATUS="${RESPONSE: -3}"
BODY="${RESPONSE:0:${#RESPONSE}-3}"
echo "API response: HTTP $STATUS"

Rule 7 — Never hardcode secrets in scripts or skill content

Scripts that are committed to version control, shared, or published as skills must contain zero hardcoded credentials. Use env var references everywhere.

bash
# BAD — hardcoded in script (never do this)
STRIPE_KEY="sk_live_<your-key-here>"
curl -u "$STRIPE_KEY:" https://api.stripe.com/v1/charges

# GOOD — always from environment
: "${STRIPE_SECRET_KEY:?STRIPE_SECRET_KEY must be set}"
curl -u "$STRIPE_SECRET_KEY:" https://api.stripe.com/v1/charges

Tool-Specific Patterns

curl

bash
# Use a netrc file (chmod 600) instead of --user flag
echo "machine api.example.com login token password $API_KEY" > ~/.netrc
chmod 600 ~/.netrc
curl --netrc https://api.example.com/endpoint

# Use --config with process substitution to avoid the header in args
curl -K <(echo "header = \"Authorization: Bearer $TOKEN\"") https://api.example.com

git

bash
# Use a credential helper — never embed tokens in remote URLs
git config --global credential.helper store   # or 'osxkeychain' / 'manager'
echo "https://token:$GH_TOKEN@github.com" | git credential approve

# For CI: use GIT_ASKPASS
export GIT_ASKPASS=echo
export GIT_PASSWORD="$GH_TOKEN"
git clone https://user@github.com/org/repo.git

Docker

bash
# Pass secrets at runtime, not build time
# BAD — baked into the image layer
docker build --build-arg API_KEY="$API_KEY" .

# GOOD — mount a secret at build time (BuildKit, never in image layers)
DOCKER_BUILDKIT=1 docker build --secret id=api_key,env=API_KEY .
# In Dockerfile: RUN --mount=type=secret,id=api_key ...

# GOOD — inject at runtime only
docker run -e API_KEY="$API_KEY" myimage

GitHub Actions / CI

yaml
# Secrets are masked in logs automatically when referenced via secrets context
- name: Call API
  env:
    API_KEY: ${{ secrets.API_KEY }}
  run: |
    # $API_KEY is masked in output — GitHub replaces it with ***
    curl -H "Authorization: Bearer $API_KEY" https://api.example.com

# Add extra masking for derived values
- run: |
    DERIVED="${API_KEY:0:8}-suffix"
    echo "::add-mask::$DERIVED"
    echo "Derived prefix set"

Node.js / Python

javascript
// Node — read from process.env, never log the value
const apiKey = process.env.ANTHROPIC_API_KEY
if (!apiKey) throw new Error('ANTHROPIC_API_KEY is required')
console.log(`API key configured (${apiKey.length} chars)`)  // length only
python
# Python — use os.environ, validate early, never print
import os
api_key = os.environ["ANTHROPIC_API_KEY"]  # raises KeyError if missing
print(f"API key configured ({len(api_key)} chars)")  # length only

When Operating as an Agent in a CLI Environment

If you are an AI agent executing shell commands, follow these additional rules:

  1. Assume traces are visible. Verification traces, stdout captures, and debug logs may be read by other users. Treat every line of output as potentially public.

  2. Prefer -z checks over echo. When verifying env vars exist, use [ -n "$VAR" ] or [ -z "$VAR" ] — never echo $VAR.

  3. Report key length, not key value. In trace output, confirm a credential is present by its length: "API key: ${#ANTHROPIC_API_KEY} chars set".

  4. Never confirm the value matches. Don't log "key starts with sk-ant-" — that's half the secret. Log "key format validated" or "key accepted by API".

  5. Treat AbortError / timeout as secret-safe. If a network call fails, log the HTTP status and error message, not the headers that were sent.

  6. Scope secrets to the narrowest environment. Set env vars inside subshells (SECRET=x command) rather than exporting to the whole session.

Checklist Before Running Sensitive Commands

  • Is the secret referenced as $VAR_NAME rather than its literal value?
  • Will the command appear in ps aux with the secret visible?
  • Does any echo or print statement emit the actual secret value?
  • Are temp files created with chmod 600 and a trap ... EXIT cleanup?
  • Does the log output contain only outcomes (HTTP status, boolean pass/fail)?
  • Will the shell history record the secret? (use unset HISTFILE if needed)

Attribution

Part of the SkillSlap security toolkit. Maintained by the community. Published at skillslap.com — install via MCP or skillslap install secure-cli-execution.

Dormant$0/mo

$20 more to next tier

Info

Created February 21, 2026
Version 1.0.0
Agent-invoked
Terminal output

Embed

Add this skill card to any webpage.

<iframe src="https://skillslap.com/skill/b3c23b09-2d47-411f-bcc4-5dcaaffcf8be/embed"
        width="400" height="200"
        style="border:none;border-radius:12px;"
        title="SkillSlap Skill: Secure CLI Execution">
</iframe>