← All articles

Secrets in Subprocesses and Shell Scripts: The Credential Leak Hiding in Plain Sight

June 22, 2026

The Problem Nobody Talks About: Credentials Passed on the Command Line

When developers think about credential leaks, they picture a plaintext API key committed to a Git repo or left in a .env file. Far fewer think about what happens when a secret is passed as a command-line argument to a shell script or subprocess call.

Here's the uncomfortable truth: on any Unix-like system, every running process's full argument list — including arguments that contain secrets — is visible to every other user on the same host via /proc or tools like ps. That window may only be open for milliseconds, but on a shared CI runner, a container host, or a bastion box, milliseconds can be enough.

How Secrets Leak Through Processes and Shell Scripts

1. Visible in ps and /proc

When you run something like:

curl -H "Authorization: Bearer $MY_SECRET_TOKEN" https://api.example.com/data

…the shell expands $MY_SECRET_TOKEN before curl even starts. The fully expanded string — including your token — is now in that process's argument vector. Any user who runs ps aux at that moment can read it. In containerized environments using shared kernels, the exposure surface can be wider than teams realize.

2. Shell History Files

If a developer runs a curl command with an inline secret interactively, that command is written to ~/.bash_history, ~/.zsh_history, or equivalent. History files are often left with permissions that allow them to be read by other processes, backed up into artifacts, or accidentally included in container images built from a developer's home directory.

3. Script Files Stored in Repos or Wikis

Automation scripts frequently hardcode secrets "just temporarily" during debugging, then get committed or pasted into an internal wiki. A common pattern:

#!/bin/bash
# deploy.sh
aws s3 sync ./dist s3://my-bucket --region us-east-1 \
  --aws-access-key-id AKIAIOSFODNN7EXAMPLE \
  --aws-secret-access-key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Even if this never hits a public repo, it may land in an internal Confluence page, a shared Slack snippet, or a backup. Once a secret is in plain text in a file, the blast radius is unpredictable.

4. Environment Variables Inherited by Child Processes

Environment variables are a safer alternative to command-line arguments — they aren't typically visible in ps aux — but they carry their own risks. Child processes inherit the full environment of their parent by default. If you spawn a third-party subprocess or a shell script you don't fully control, every export-ed secret in your environment goes along for the ride.

5. Logs That Capture Command Output

CI/CD systems often log the exact commands they run. A pipeline step like:

run: echo "Deploying with key $STRIPE_SECRET_KEY"

…or a debug set -x at the top of a script will faithfully print every expanded variable to the build log, which is often retained for weeks and accessible to everyone on the team — or beyond, if the repo is public.

Concrete Steps to Stop Shell Script Credential Leaks

Step 1: Never Pass Secrets as Positional Arguments

Prefer environment variables or temporary files with restricted permissions (chmod 600) over command-line arguments. Most well-designed CLIs support reading secrets from environment variables or stdin. For example, aws reads AWS_ACCESS_KEY_ID from the environment; you don't need to pass it as a flag.

Step 2: Sanitize Child Process Environments

Before spawning a subprocess you don't control, explicitly clear or scope the environment. In Python, for instance:

import subprocess, os

clean_env = {k: v for k, v in os.environ.items()
             if k not in ("STRIPE_SECRET_KEY", "DATABASE_URL")}

subprocess.run(["third-party-tool", "--arg", "value"], env=clean_env)

In shell scripts, use env -i to start with a clean environment and only pass what the subprocess actually needs.

Step 3: Remove Secrets from Shell History

Add a space before any command containing a secret (in bash, commands prefixed with a space are not saved to history when HISTCONTROL=ignorespace is set). Better yet, set HISTCONTROL=ignoreboth in your shell profile. Audit existing history files periodically and consider setting HISTSIZE=0 on shared bastion hosts.

Step 4: Audit Scripts for Inline Secrets Before They Leave Your Machine

Before committing any shell script, run a local secret scan. At minimum, grep for patterns that look like credentials:

# Quick-and-dirty local check
grep -rE "(AKIA[0-9A-Z]{16}|sk_live_[a-zA-Z0-9]+|ghp_[a-zA-Z0-9]+)" ./scripts/

This catches obvious patterns, but regex alone misses many credential formats. A dedicated scanner covers entropy-based detection, provider-specific patterns, and context-aware false-positive filtering that manual grep cannot.

Step 5: Use set -x Responsibly in CI

Debug tracing (set -x) is invaluable locally but dangerous in automated pipelines where logs are stored. Either disable it in CI entirely, or temporarily suppress tracing around sensitive commands:

set +x           # disable tracing
MY_SECRET=$(get_secret_from_vault)
set -x           # re-enable tracing

Better still, configure your CI platform to mask specific environment variable values in logs — GitHub Actions, GitLab CI, and CircleCI all support this for registered secret variables.

Step 6: Rotate Any Secret That May Have Been Exposed

If you find a secret that was passed on the command line, logged, or embedded in a script, treat it as compromised. Rotate it immediately, regardless of how brief the exposure window may have been. Shared CI hosts, log aggregation systems, and noisy monitoring dashboards create more opportunities for interception than developers typically account for.

SOC 2 and HIPAA Implications

Both SOC 2 (CC6.1, CC6.3) and HIPAA Security Rule §164.312(a)(1) require organizations to implement access controls and audit mechanisms for systems handling sensitive data. Credentials passed in process arguments, stored in shell history, or embedded in scripts all represent uncontrolled access paths that auditors will flag when they review your deployment and automation practices. Documenting that you actively scan for these patterns — and have remediation procedures — is increasingly expected as a baseline, not a bonus.

Scan Your Codebase and Scripts Now

Shell scripts, CI configurations, and automation tooling rarely get the same security scrutiny as application code, yet they often carry the most privileged credentials in your entire stack. A systematic scan takes far less time than responding to a breach.

To see where your own repos and configuration files stand, run a free GhostCred scan — it covers scripts, dotfiles, and config files alongside application code, and maps any findings to SOC 2 and HIPAA controls so your next audit conversation starts from a position of confidence rather than guesswork.

See what's exposed in your own code.

Run a free scan