Skip to content

OpSec Mistakes - Street-Level Ops

Real-world workflows for detecting, preventing, and remediating operational security failures.

Secret Detection in Git History

# Scan entire git history with gitleaks
gitleaks detect --source . --verbose

# Output:
# Finding:     AWS Access Key ID
# Secret:      AKIA...
# File:        scripts/deploy.sh
# Line:        15
# Commit:      a3b4c5d6
# Author:      dev@company.com

# Check if a specific commit contains secrets
gitleaks detect --source . --log-opts="a3b4c5d6..HEAD"

# Scan only staged changes (pre-commit hook)
gitleaks protect --staged

# Install gitleaks as a pre-commit hook
cat > .git/hooks/pre-commit << 'HOOK'
#!/usr/bin/env bash
gitleaks protect --staged --verbose
HOOK
chmod +x .git/hooks/pre-commit

# Alternative: trufflehog for deeper analysis
trufflehog git file://. --only-verified

War story: A dev pushed an AWS key to a public repo. Within 4 minutes, crypto miners had spun up 200 GPU instances. AWS bill: $14,000 before CloudTrail alerts fired. Bots scrape GitHub's public event stream in real time. Rotate first, clean history second.

Remediating a Committed Secret

# 1. IMMEDIATELY rotate the secret (assume compromised)
#    AWS: deactivate the access key
aws iam update-access-key --access-key-id AKIA... --status Inactive --user-name deploy-user
aws iam create-access-key --user-name deploy-user  # Create new key

# 2. Remove from git history with git filter-repo
pip install git-filter-repo
git filter-repo --invert-paths --path scripts/deploy.sh

# Or use BFG Repo-Cleaner for targeted secret removal
bfg --replace-text secrets.txt repo.git

# 3. Force push (coordinate with team — this rewrites public history)
git push --force-with-lease --all

# 4. Invalidate cached copies
#    - CI/CD caches
#    - Container images built from that commit
#    - Artifact registries

IAM Permission Audit

# AWS: Find overly broad IAM policies
aws iam list-policies --scope Local --query 'Policies[*].PolicyName'

# Check for wildcard permissions
aws iam get-policy-version --policy-arn arn:aws:iam::123456:policy/MyPolicy \
  --version-id v1 | jq '.PolicyVersion.Document.Statement[] | select(.Action == "*")'

# Find users with console access but no MFA
aws iam generate-credential-report
aws iam get-credential-report --output text --query Content | base64 -d | \
  awk -F, '$4 == "true" && $8 == "false" {print $1, "NO MFA"}'

# Kubernetes: Find overly broad RBAC
kubectl get clusterrolebindings -o json | jq '.items[] |
  select(.roleRef.name == "cluster-admin") |
  {name: .metadata.name, subjects: .subjects}'

# Find pods running as root
kubectl get pods -A -o json | jq '.items[] |
  select(.spec.containers[].securityContext.runAsUser == 0 or
         (.spec.securityContext.runAsUser == null and
          .spec.containers[].securityContext.runAsUser == null)) |
  {namespace: .metadata.namespace, name: .metadata.name}'

Remember: Secret remediation order mnemonic: R-R-R-I — Rotate the credential, Remove from history, force-push to Rewrite, then Invalidate caches (CI, images, artifacts). Never skip step 1 — the secret is compromised the instant it hits any remote.

Checking for Default Credentials

# Test common services for default auth
# Redis (no auth by default)
redis-cli -h redis.internal ping 2>/dev/null && echo "WARN: Redis has no auth"

# Elasticsearch (no auth pre-8.x)
curl -s http://elasticsearch.internal:9200 2>/dev/null && echo "WARN: ES has no auth"

# MongoDB (no auth by default)
mongosh --host mongo.internal --eval "db.adminCommand('listDatabases')" 2>/dev/null && \
  echo "WARN: MongoDB has no auth"

# Grafana default admin/admin
curl -s -u admin:admin http://grafana.internal:3000/api/org 2>/dev/null | \
  jq -e '.id' && echo "WARN: Grafana has default credentials"

Default trap: Redis binds to all interfaces with no auth by default. A single redis-cli -h <your-host> FLUSHALL from any host on the network wipes your cache. Since Redis 6.0, use ACLs (requirepass + user directives). Bind to 127.0.0.1 if only local access is needed.

Network Exposure Check

# Find services bound to 0.0.0.0 (all interfaces)
ss -tlnp | grep '0.0.0.0'

# Output:
# LISTEN  0  128  0.0.0.0:6379  0.0.0.0:*  users:(("redis-server",...))
# LISTEN  0  128  0.0.0.0:9200  0.0.0.0:*  users:(("java",...))

# Check for open ports from outside
nmap -sT -p 22,80,443,3306,5432,6379,9200,27017 target-host

# Find Kubernetes services of type LoadBalancer (publicly exposed)
kubectl get svc -A -o json | jq '.items[] |
  select(.spec.type == "LoadBalancer") |
  {namespace: .metadata.namespace, name: .metadata.name, ports: .spec.ports}'

Debug clue: ss -tlnp | grep '0.0.0.0' shows services listening on all interfaces. If a service should only be reachable locally, it should bind to 127.0.0.1 or a specific internal IP. Every 0.0.0.0 entry is a potential attack surface — verify each one is intentional.

Container Security Audit

# Check for containers running as root
docker ps --quiet | xargs -I{} docker inspect {} | \
  jq '.[] | {name: .Name, user: .Config.User}' | grep -B1 '""'

# Check for privileged containers
kubectl get pods -A -o json | jq '.items[] |
  select(.spec.containers[].securityContext.privileged == true) |
  {namespace: .metadata.namespace, name: .metadata.name}'

# Check for containers with docker socket mounted
kubectl get pods -A -o json | jq '.items[] |
  select(.spec.volumes[]?.hostPath.path == "/var/run/docker.sock") |
  {namespace: .metadata.namespace, name: .metadata.name}'

# Scan running images for vulnerabilities
docker images --format '{{.Repository}}:{{.Tag}}' | \
  while read -r img; do
    echo "--- ${img} ---"
    trivy image --severity CRITICAL --quiet "${img}"
  done

Patching Status Check

# Check for available security updates (Debian/Ubuntu)
apt list --upgradable 2>/dev/null | grep -i security

# Check last patch date
stat /var/log/apt/history.log | grep Modify

# CentOS/RHEL security updates
yum updateinfo list security

# Kubernetes node versions (should be within 1 minor of latest)
kubectl get nodes -o custom-columns=NAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion

Quick Self-Audit Checklist

# Run through this monthly
echo "=== OpSec Quick Audit ==="

# 1. Secrets in git?
gitleaks detect --source . --quiet && echo "PASS: No secrets in git" || echo "FAIL: Secrets found"

# 2. .gitignore covers sensitive files?
for f in .env .env.* "*.pem" "*.key" credentials.json terraform.tfstate; do
    grep -q "${f}" .gitignore 2>/dev/null && echo "PASS: ${f} in .gitignore" || echo "FAIL: ${f} not in .gitignore"
done

# 3. Pre-commit hooks installed?
[[ -x .git/hooks/pre-commit ]] && echo "PASS: Pre-commit hook exists" || echo "FAIL: No pre-commit hook"

# 4. MFA enabled on cloud accounts?
echo "CHECK: Verify MFA manually in AWS/GCP/Azure console"