AWS security baseline check
Summary
The account was checked against 25 baseline controls covering identity, logging, data, network and secrets, using read-only access only. It scores 12/100 (grade F). The most urgent finding: Security group acme-demo-1107-legacy-admin (sg-0f589326b0d3de0db) has SSH port 22 open to the Internet (NC-01, critical). Open admin and database ports are scanned constantly; one weak password or unpatched service is enough. Nothing was changed in the account. Every fix below is code or a command for you to review and apply; a retest scan confirms it.
Top risks
| Severity | Control | What we found | First step |
|---|---|---|---|
| critical | NC-01 No SSH, RDP or database port is open to the whole internet | Security group acme-demo-1107-legacy-admin (sg-0f589326b0d3de0db) has SSH port 22 open to the Internet. (sg-0f589326b0d3de0db) Security group acme-demo-1107-legacy-admin (sg-0f589326b0d3de0db) has Postgres port 5432 open to the Internet. (sg-0f589326b0d3de0db) |
aws ec2 revoke-security-group-ingress --group-id <sg> --protocol tcp --port 22 --cidr 0.0.0.0/0 |
| critical | SP-01 No secrets in Lambda variables, EC2 user data or launch templates | Potential secret found in Lambda function acme-demo-1107-invoice-mailer variables -> Generic Password in variable SMTP_PASSWORD, SMTP Credentials in variable SMTP_PASSWORD. (acme-demo-1107-invoice-mailer) Potential secret found in EC2 instance i-011db38cbdb70e4dd User Data -> Generic Password on line 2. (i-011db38cbdb70e4dd) |
Move each value to Secrets Manager or SSM Parameter Store (SecureString), read it at runtime, then rotate it - it must be treated as exposed. |
| critical | DA-01 S3 Block Public Access is on for the whole account | Block Public Access is not configured for the account 453348321107. (453348321107) |
module.security_baseline: s3_account_public_access_block = true |
| critical | DA-02 No bucket is public | S3 Bucket acme-demo-453348321107-uploads has public access due to bucket policy. (acme-demo-453348321107-uploads) |
aws s3api put-public-access-block --bucket <bucket> --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true |
| critical | LD-01 CloudTrail records every region, with tamper-evident, private logs | No CloudTrail trails enabled with logging were found. (453348321107) |
module.security_baseline: cloudtrail = true # multi-region trail, log-file validation, private encrypted bucket |
All controls
Identity and access
The root user can do everything, including closing the account. A stolen root password or key is a total loss.
Passwords get phished and reused. MFA stops most account takeovers.
Long-lived keys leak through laptops, CI logs and git history. Datadog found active keys over a year old at 59% of IAM users.
Permissions attached user by user drift and are never reviewed. Groups, roles and IAM Identity Center keep access auditable.
Evidence (2)
| Resource | Region | Finding |
|---|---|---|
arn:aws:iam::453348321107:user/acme-demo-1107-ci-deploy | ap-southeast-2 | User acme-demo-1107-ci-deploy has the inline policy deny-everything attached. |
arn:aws:iam::453348321107:user/acme-demo-1107-legacy-admin | ap-southeast-2 | User acme-demo-1107-legacy-admin has the policy AdministratorAccess attached. |
How to fix
aws iam detach-user-policy --user-name <user> --policy-arn <arn> # after adding the user to a group with the same rights
Every identity with AdministratorAccess is a way to lose the whole account. Most teams need two or three, not ten.
Evidence (1)
| Resource | Region | Finding |
|---|---|---|
arn:aws:iam::aws:policy/AdministratorAccess | ap-southeast-2 | AWS policy AdministratorAccess is attached and allows '*:*' administrative privileges. |
How to fix
aws iam list-entities-for-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
Only matters for IAM users with console access, but auditors and customer questionnaires ask for it.
Evidence (2)
| Resource | Region | Finding |
|---|---|---|
arn:aws:iam:ap-southeast-2:453348321107:password-policy | ap-southeast-2 | IAM password policy does not require minimum length of 14 characters. |
arn:aws:iam:ap-southeast-2:453348321107:password-policy | ap-southeast-2 | IAM password policy reuse prevention is less than 24 or not set. |
How to fix
module.security_baseline: iam_password_policy = trueaws iam update-account-password-policy --minimum-password-length 14 --password-reuse-prevention 24 --require-symbols --require-numbers --require-uppercase-characters --require-lowercase-characters
It flags buckets, roles and keys shared with other accounts or the public - for free.
Logging and detection
Without CloudTrail there is no record of who did what - no investigation, and no basis for a breach report.
Evidence (1)
| Resource | Region | Finding |
|---|---|---|
arn:aws:cloudtrail:ap-southeast-2:453348321107:trail | ap-southeast-2 | No CloudTrail trails enabled with logging were found. |
How to fix
module.security_baseline: cloudtrail = true # multi-region trail, log-file validation, private encrypted bucketaws cloudtrail create-trail --name org-trail --s3-bucket-name <bucket> --is-multi-region-trail --enable-log-file-validation aws cloudtrail start-logging --name org-trail
GuardDuty spots stolen credentials, crypto-mining and unusual API calls. Costs a few dollars a month for small accounts.
Config keeps a history of how each resource was configured - the evidence auditors and investigators ask for.
Evidence (1)
| Resource | Region | Finding |
|---|---|---|
arn:aws:config:ap-southeast-2:453348321107:recorder | ap-southeast-2 | AWS Config recorder 453348321107 is disabled. |
How to fix
module.security_baseline: config_recorder = trueLogs kept for days or weeks are gone when an incident is found months later. One year is the minimum many customers - and India's DPDP Rules - expect for security logs. ('Never expire' passes here; the cost check LOGS-001 flags its price.)
Evidence (1)
| Resource | Region | Finding |
|---|---|---|
arn:aws:logs:ap-southeast-2:453348321107:log-group:/acme-demo-1107/api:* | ap-southeast-2 | Log Group /acme-demo-1107/api has less than 365 days retention period (7 days). |
How to fix
bash aws-foundation/fix-pack/scripts/set-log-retention.sh --days 365 # dry run first; add --apply to change
A stolen key used for crypto-mining shows up first on the bill. Budgets with email alerts are free.
Evidence (1)
| Resource | Region | Finding |
|---|---|---|
arn:aws:budgets::453348321107:budget | global | No budget with an alert: abuse of a stolen key would only show up on the monthly bill |
How to fix
module.security_baseline: monthly_budget_usd = <amount>, alert_emails = [...] (or aws-bill-doctor/terraform/budget-guard)Data protection
One account-wide switch stops any bucket from being made public by mistake - the cause of many data leaks.
Evidence (1)
| Resource | Region | Finding |
|---|---|---|
arn:aws:s3:ap-southeast-2:453348321107:account | ap-southeast-2 | Block Public Access is not configured for the account 453348321107. |
How to fix
module.security_baseline: s3_account_public_access_block = trueaws s3control put-public-access-block --account-id <account> --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Public buckets are found by automated scanners within hours.
Evidence (1)
| Resource | Region | Finding |
|---|---|---|
arn:aws:s3:::acme-demo-453348321107-uploads | ap-southeast-2 | S3 Bucket acme-demo-453348321107-uploads has public access due to bucket policy. |
How to fix
aws s3api put-public-access-block --bucket <bucket> --public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true # if the bucket serves a website, put CloudFront with origin access control in front instead
Default encryption is free and protects disks, snapshots and buckets that get copied or shared by mistake.
Evidence (1)
| Resource | Region | Finding |
|---|---|---|
arn:aws:ec2:ap-southeast-2:453348321107:volume | ap-southeast-2 | EBS Default Encryption is not activated. |
How to fix
module.security_baseline: ebs_encryption_by_default = trueaws ec2 enable-ebs-encryption-by-default --region <region>
An RDS instance reachable from the internet, without backups, is one mistake away from a data leak or permanent loss.
A public EBS or RDS snapshot hands a full copy of the disk or database to anyone.
Network and compute
Open admin and database ports are scanned constantly; one weak password or unpatched service is enough.
Evidence (2)
| Resource | Region | Finding |
|---|---|---|
arn:aws:ec2:ap-southeast-2:453348321107:security-group/sg-0f589326b0d3de0db | ap-southeast-2 | Security group acme-demo-1107-legacy-admin (sg-0f589326b0d3de0db) has SSH port 22 open to the Internet. |
arn:aws:ec2:ap-southeast-2:453348321107:security-group/sg-0f589326b0d3de0db | ap-southeast-2 | Security group acme-demo-1107-legacy-admin (sg-0f589326b0d3de0db) has Postgres port 5432 open to the Internet. |
How to fix
aws ec2 revoke-security-group-ingress --group-id <sg> --protocol tcp --port 22 --cidr 0.0.0.0/0 # use SSM Session Manager (no open port) or a VPN/allow-list instead
Resources launched without a chosen group land in the default one; it should block everything.
IMDSv1 lets an SSRF bug in a web app read the instance's cloud credentials (the Capital One breach). Datadog found IMDSv2 enforced on only 49% of instances.
Evidence (2)
| Resource | Region | Finding |
|---|---|---|
arn:aws:ec2:ap-southeast-2:453348321107:account | ap-southeast-2 | IMDSv2 is not enabled by default for EC2 instances. |
arn:aws:ec2:ap-southeast-2:453348321107:instance/i-011db38cbdb70e4dd | ap-southeast-2 | EC2 Instance i-011db38cbdb70e4dd has IMDSv2 disabled or not required. |
How to fix
module.security_baseline: ec2_imdsv2_default = true # new instancesbash aws-foundation/fix-pack/scripts/require-imdsv2.sh --regions <region> # dry run lists instances; --apply after testing one aws ec2 modify-instance-metadata-options --instance-id <id> --http-tokens required --http-put-response-hop-limit 2
Every public instance is attack surface (and costs $3.65 a month per IPv4 address). Most belong behind a load balancer.
Evidence (1)
| Resource | Region | Finding |
|---|---|---|
arn:aws:ec2:ap-southeast-2:453348321107:instance/i-011db38cbdb70e4dd | ap-southeast-2 | EC2 Instance i-011db38cbdb70e4dd has a Public IP: 3.26.2.132 (ec2-3-26-2-132.ap-southeast-2.compute.amazonaws.com). |
How to fix
Secrets and pipelines
Anyone with read-only access - including auditors and many SaaS tools - can read these fields.
Evidence (2)
| Resource | Region | Finding |
|---|---|---|
arn:aws:lambda:ap-southeast-2:453348321107:function:acme-demo-1107-invoice-mailer | ap-southeast-2 | Potential secret found in Lambda function acme-demo-1107-invoice-mailer variables -> Generic Password in variable SMTP_PASSWORD, SMTP Credentials in variable SMTP_PASSWORD. |
arn:aws:ec2:ap-southeast-2:453348321107:instance/i-011db38cbdb70e4dd | ap-southeast-2 | Potential secret found in EC2 instance i-011db38cbdb70e4dd User Data -> Generic Password on line 2. |
How to fix
A secret that never changes stays valid after every leak.
Evidence (1)
| Resource | Region | Finding |
|---|---|---|
arn:aws:secretsmanager:ap-southeast-2:453348321107:secret:acme-demo-1107/db-z2qcof | ap-southeast-2 | SecretsManager secret acme-demo-1107/db has rotation disabled. |
How to fix
aws secretsmanager rotate-secret --secret-id <id> --rotation-lambda-arn <arn> --rotation-rules AutomaticallyAfterDays=90
Keys stored in CI were stolen in the tj-actions, Trivy and Checkmarx incidents. OIDC tokens expire in minutes.
Scanning catches known vulnerabilities before deployment; immutable tags stop a pushed image being silently replaced.
Evidence (3)
| Resource | Region | Finding |
|---|---|---|
arn:aws:ecr:ap-southeast-2:453348321107:registry/453348321107 | ap-southeast-2 | ECR registry 453348321107 has BASIC scanning without automated scanning enabled. |
arn:aws:ecr:ap-southeast-2:453348321107:repository/acme-demo-1107/api | ap-southeast-2 | ECR repository acme-demo-1107/api has scan on push disabled. |
arn:aws:ecr:ap-southeast-2:453348321107:repository/acme-demo-1107/api | ap-southeast-2 | Repository acme-demo-1107/api does not have immutability configured. |
How to fix
module.security_baseline: ecr_scan_on_push = trueaws ecr put-image-tag-mutability --repository-name <repo> --image-tag-mutability IMMUTABLE
Checks that could not run
- native:ci_oidc in global: AccessDenied: User: arn:aws:sts::453348321107:assumed-role/AccountFullAccessRole/643884c8-9031-70eb-357b-85ffbc64df15 is not authorized to perform: iam:ListOpenIDConnectProviders on resource: arn:aws:iam::453348321107:oidc-provider/* with an explicit deny in a service control policy: arn:aws:organiz
Method and limits
- Read-only access: a cross-account role with the AWS-managed
SecurityAuditandViewOnlyAccesspolicies, a few extra read permissions, and an explicit deny on reading data (S3 objects, database rows, secret values, log events). The account's own CloudTrail records every call. - Engine: Prowler 5.47.0 (open source, Apache-2.0) running 48 curated checks, plus two native checks for spend alerts and CI/CD credentials. Each finding was reviewed by a person before delivery.
- Score: each evaluated control counts by severity (critical 10, high 6, medium 3, low 1). Controls with no matching resources, or that could not be evaluated, are left out of the score and shown as coverage instead.
- Findings reflect the configuration at the time of the scan. A baseline check is not a penetration test and not a guarantee that the account cannot be breached.