Pulumi Security Testing: State File Credential Exposure, IaC Drift, and Provider Secret Leakage

Pulumi is an infrastructure-as-code platform that uses general-purpose programming languages (Python, TypeScript, Go) to define cloud infrastructure. Unlike Terraform's HCL, Pulumi's state files contain not just resource references but full resource outputs — including database connection strings, generated passwords, TLS private keys, and API tokens — stored in the state backend (S3, Azure Blob, or Pulumi Cloud). Anyone with read access to the S3 bucket hosting Pulumi state can extract all infrastructure credentials without any Pulumi authentication; Pulumi's pulumi stack output --show-secrets command decrypts secrets for any stack member; Pulumi.*.yaml config files committed to repositories may contain encrypted secrets whose encryption key (stored in the stack) can be used to decrypt them; and Pulumi's Automation API allows programmatic stack manipulation from within application code — a compromised application can call stack.up() to modify infrastructure. This guide covers systematic Pulumi security assessment.

Table of Contents

  1. State Backend Access and Credential Extraction
  2. Stack Output Secret Exposure
  3. Config File Encryption Key Abuse
  4. Provider Credential Exposure
  5. Automation API Privilege Escalation
  6. Pulumi Security Hardening

State Backend Access and Credential Extraction

# Pulumi state is stored in the backend (S3, Azure Blob, GCS, Pulumi Cloud)
# S3 backend: s3://your-bucket/org/project/stack/.pulumi/stacks/

# List Pulumi state files in S3 backend
aws s3 ls s3://pulumi-state-bucket/ --recursive 2>/dev/null | \
  grep "\.json$" | head -20
# Returns: .pulumi/stacks/production.json, .pulumi/stacks/staging.json etc.

# Download and examine a state file (state files are JSON, may contain plaintext secrets)
aws s3 cp s3://pulumi-state-bucket/.pulumi/stacks/production.json /tmp/state.json 2>/dev/null && \
  python3 -c "
import json
with open('/tmp/state.json') as f:
    state = json.load(f)
resources = state.get('checkpoint', {}).get('latest', {}).get('resources', [])
print(f'Resources: {len(resources)}')
for r in resources:
    outputs = r.get('outputs', {})
    for k, v in outputs.items():
        if isinstance(v, str) and len(v) > 8:
            if any(x in k.lower() for x in ['password', 'key', 'secret', 'token', 'cert', 'connection']):
                print(f'  {r[\"urn\"].split(\"::\")[-1]} .{k} = {v[:50]}...')
" 2>/dev/null | head -30

Stack Output Secret Exposure

# Pulumi stack outputs can be marked as secret but any stack member can read them
# via: pulumi stack output --show-secrets

# List stack outputs (shows placeholders for secrets without --show-secrets)
pulumi stack output 2>/dev/null

# Reveal all secrets (requires stack access — tests excessive stack membership)
pulumi stack output --show-secrets 2>/dev/null | head -20
# Outputs database_url, api_key, tls_private_key etc. in plaintext

# Via Pulumi REST API (Pulumi Cloud backend)
PULUMI_ACCESS_TOKEN=$(cat ~/.pulumi/credentials.json 2>/dev/null | \
  python3 -c "import json,sys; print(json.load(sys.stdin).get('accessTokens',{}).get('https://api.pulumi.com',''))" 2>/dev/null)

# List all stacks in org
curl -s "https://api.pulumi.com/api/stacks" \
  -H "Authorization: token ${PULUMI_ACCESS_TOKEN}" 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
stacks = data.get('stacks', [])
print(f'Stacks: {len(stacks)}')
for s in stacks:
    print(f\"  {s.get('orgName','?')}/{s.get('projectName','?')}/{s.get('stackName','?')}\")
" 2>/dev/null

Pulumi Security Hardening

Pulumi Security Hardening Checklist:
Security TestMethodRisk
State file in S3 contains plaintext credentialsaws s3 cp state.json — parse outputs for password/key/token fieldsCritical
Stack output secrets readable by all memberspulumi stack output --show-secrets — decrypts all secret outputs for any stack memberHigh
Pulumi.*.yaml committed with encrypted secretsgit log --all --grep=secret Pulumi*.yaml — find secrets in git historyHigh
Provider credentials in environment not rotatedReview CI/CD env vars — static AWS/Azure keys used as Pulumi provider credsHigh
Automation API allows stack modifications from app codeReview app code for AutoStack/LocalWorkspace calls — can modify infra from compromised appHigh
PULUMI_ACCESS_TOKEN in ~/.pulumi/credentials.jsonRead credentials file on developer machines — token scoped to all org stacksMedium

Automate Pulumi Security Testing

Ironimo tests Pulumi IaC deployments for state file credential exposure in S3 backends, stack output secrets readable by all team members, Pulumi.*.yaml files with encrypted secrets committed to repositories, provider credentials stored as static environment variables, Automation API enabling infrastructure modification from compromised application code, and PULUMI_ACCESS_TOKEN scope allowing org-wide stack access from developer machines.

Start free scan