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> FLUSHALLfrom any host on the network wipes your cache. Since Redis 6.0, use ACLs (requirepass+userdirectives). Bind to127.0.0.1if 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 to127.0.0.1or a specific internal IP. Every0.0.0.0entry 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"