---
title: Security baseline remediation
slug: security-baseline-remediation
summary: How to run a cloud security sweep (flow logs, HTTPS-only load balancers, public databases, dead event targets, plaintext build secrets, broad IAM) and fix findings in safe batches.
order: 23
updated: 2026-11-11
status: ready
---

Use this page when a security review or a posture tool (AWS Security Hub, Config, a CSPM product) produces hundreds of findings, often one ticket per resource. The trick is to group them by control, fix the pattern once, and roll it out account by account.

## Group findings by control, not by resource

| Control | Check | Typical fix |
| --- | --- | --- |
| VPC flow logs enabled | Every VPC has flow logs to a central bucket or log group | IaC module adds flow logs to every VPC |
| HTTPS only | Load balancer HTTP listeners redirect to HTTPS | Listener rule: redirect 80 to 443 |
| No public databases | `PubliclyAccessible=false`; security groups have no `0.0.0.0/0` on DB ports | Flip flag in a window; private access path |
| No dead event targets | Scheduled and event rules point at functions that exist | Delete the rule or fix the target |
| No plaintext secrets in build configs | Build specs and CI variables use a secret manager | Move to secret manager, rotate |
| No broad IAM | No `*:*` policies on users or roles except break-glass | Replace with scoped policies |
| No old access keys | Keys under 90 days or replaced by roles | [Rotate or retire](./secret-rotation-and-exposed-secrets.md) |
| Encryption at rest | Volumes, buckets, databases encrypted | Default encryption; migrate old resources |

One parent ticket per control, child tickets or a checklist per account. That turns a pile of per-resource tickets into a few decisions.

## Read-only checks

```bash
P=<readonly-profile>; R=<region>

# VPCs without flow logs
comm -23 \
  <(aws --profile $P --region $R ec2 describe-vpcs --query 'Vpcs[].VpcId' --output text | tr '\t' '\n' | sort) \
  <(aws --profile $P --region $R ec2 describe-flow-logs --query 'FlowLogs[].ResourceId' --output text | tr '\t' '\n' | sort)

# HTTP listeners that do not redirect
for lb in $(aws --profile $P --region $R elbv2 describe-load-balancers --query 'LoadBalancers[].LoadBalancerArn' --output text); do
  aws --profile $P --region $R elbv2 describe-listeners --load-balancer-arn "$lb" \
    --query 'Listeners[?Protocol==`HTTP`].{arn:ListenerArn,action:DefaultActions[0].Type}' --output text
done | awk '$1 != "redirect"'

# Public databases
aws --profile $P --region $R rds describe-db-instances \
  --query 'DBInstances[?PubliclyAccessible].DBInstanceIdentifier' --output text

# Rules whose Lambda target no longer exists
for rule in $(aws --profile $P --region $R events list-rules --query 'Rules[].Name' --output text); do
  for arn in $(aws --profile $P --region $R events list-targets-by-rule --rule "$rule" \
      --query 'Targets[?contains(Arn, `:function:`)].Arn' --output text); do
    aws --profile $P --region $R lambda get-function --function-name "$arn" >/dev/null 2>&1 \
      || echo "dead target: rule=$rule target=$arn"
  done
done

# Build projects with plaintext env vars that look secret (names only, never values)
for p in $(aws --profile $P --region $R codebuild list-projects --query 'projects[]' --output text); do
  aws --profile $P --region $R codebuild batch-get-projects --names "$p" \
    --query 'projects[].environment.environmentVariables[?type==`PLAINTEXT`].name' --output text \
    | tr '\t' '\n' | grep -iE 'pass|secret|token|key' | sed "s/^/$p: /"
done
```

## Fix in safe batches

- [ ] Write the fix once in a shared IaC module or a standard snippet.
- [ ] Pilot in one non-production account. Verify nothing broke.
- [ ] Roll out by account, lowest risk first, one MR per account or stack.
- [ ] Production-impacting fixes (closing public database access, enforcing HTTPS) go through [change control](./change-control-in-regulated-environments.md) with a named owner for each resource.
- [ ] Some fixes need an approver who owns the system. Batch the approval requests so they decide on the list once.

## Verify and keep it fixed

- [ ] Re-run the check; the count goes to zero for that account.
- [ ] Add a preventive control: an AWS Config rule, an SCP, or a policy check in CI that blocks the bad pattern.
- [ ] Report counts per control per week until done.

## Pitfalls

- Flipping a database to private without checking who connects from outside. Look at connections and security group sources first.
- Enforcing HTTPS on a health check or partner integration that only speaks HTTP. Find those first.
- Deleting "dead" rules that are dead because a deploy is mid-flight. Check the function's history.
