
A terminal-based AWS Security Scanner with 102+ security checks across VPC, IAM, S3, CloudTrail, containers (ECS/EKS), and AI attack detection. Detects dangerous IAM permissions, exposed secrets, misconfigured S3 buckets, container vulnerabilities, and emerging LLMjacking threats.
A terminal-based AWS Security Scanner with 100+ security checks across VPC, IAM, S3, CloudTrail, containers (ECS/EKS), and AI attack detection. Detects dangerous IAM permissions, exposed secrets, misconfigured S3 buckets, container vulnerabilities, and emerging LLMjacking threats.
Product overview: docs/CAPABILITIES_OVERVIEW.md
*:*).env, .git, credentials)Based on Feb 2025 threat intelligence: 8-minute AWS breach
| Category | Count |
|---|---|
| Core (IAM, VPC, S3, CloudTrail, Secrets) |
# macOS
brew install awscli
# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip && sudo ./aws/install
aws configure
# or use named profiles
aws configure --profile myprofile
Your AWS credentials must have read-only access to the services being scanned. The AWS managed policy ReadOnlyAccess works, or see Required Permissions below.
Note: aws-perimeter only performs read operations and never modifies your AWS resources.
curl -sSfL https://raw.githubusercontent.com/thirukguru/aws-perimeter/main/install.sh | sh
go install github.com/thirukguru/aws-perimeter@latest
aws-perimeter # Run full security scan
aws-perimeter --output json # JSON output
aws-perimeter --profile prod # Specific AWS profile
aws-perimeter --region us-west-2 # Specific region
aws-perimeter --regions us-east-1,us-west-2 # Multi-region scan
aws-perimeter --regions us-east-1,us-west-2 --max-parallel 4 # Multi-region with controlled concurrency
aws-perimeter --regions us-east-1,us-west-2 --max-parallel 4 --best-effort # Exit success if at least one region succeeds
aws-perimeter --rules # Print RULES.md to stdout (Markdown)
aws-perimeter --capabilities # Print capabilities overview to stdout (Markdown)
aws-perimeter --all-regions # Scan all enabled regions
aws-perimeter --org-scan --org-role-name OrganizationAccountAccessRole # Multi-account org scan
aws-perimeter --org-scan --max-parallel 5 # Org+region fanout concurrency
aws-perimeter --output html --output-file report.html # Generate HTML report
aws-perimeter --store --profile prod --region us-west-2 # Run + persist scan
aws-perimeter --trends --trend-days 30 --account-id 123456789012 # Show historical trend table
aws-perimeter history list --db-path ~/.aws-perimeter/history.db
aws-perimeter dashboard --port 8080
For fanout modes (--regions, --all-regions, --org-scan) with --output html --output-file ..., aws-perimeter writes one report per scan unit with region/account + timestamp suffixes (for example security-report-us-east-1-20260210-213045.html or security-report-123456789012-us-east-1-20260210-213045.html).
In HTML mode, terminal table output is suppressed and only concise summary lines are printed.
When --output json is used, aws-perimeter emits a single valid JSON document with no banner/spinner noise, so it is safe for pipelines.
aws-perimeter --profile prod --region us-west-2 --output json | jq .
# Multi-region JSON emits one aggregated top-level JSON document:
aws-perimeter --profile prod --regions us-east-1,us-west-2 --output json | jq .
# export docs via stdout redirection
aws-perimeter --rules > rules.md
aws-perimeter --capabilities > capabilities.md
Multi-region JSON payload includes:
summary (total_regions, success, failed, skipped)results (per-region consolidated scan payloads)failures (region + error details when a region scan fails)For multi-region and org scans in non-JSON output modes, aws-perimeter prints a consolidated summary at the end of execution:
account_id, account_name, region, status, duration, and error.TOTAL, SUCCESS, FAILED, SKIPPED).--max-parallel and --best-effort--max-parallel controls how many region/account scan units run concurrently in fanout modes (--regions, --all-regions, --org-scan).--max-parallel 3 or --max-parallel 4.--best-effort applies to multi-region scans: command exits success (0) when at least one region succeeds, even if some regions fail.--best-effort, any failed region returns a non-zero exit code.failures with the exact error.The following permissions are required for full feature coverage (including multi-region and org scan):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:GetCallerIdentity",
"sts:AssumeRole",
"organizations:DescribeOrganization",
"organizations:ListAccounts",
"ec2:Describe*",
"ec2:GetEbsEncryptionByDefault",
"iam:List*",
"iam:Get*",
"iam:GenerateCredentialReport",
"s3:ListAllMyBuckets",
"s3:GetBucket*",
"s3:GetEncryptionConfiguration",
"cloudtrail:DescribeTrails",
"cloudtrail:GetTrailStatus",
"cloudtrail:LookupEvents",
"lambda:ListFunctions",
"lambda:GetFunctionConfiguration",
"lambda:GetFunction",
"ecr:DescribeRepositories",
"ecr:DescribeImages",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecs:ListClusters",
"ecs:DescribeClusters",
"ecs:ListServices",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"eks:ListClusters",
"eks:DescribeCluster",
"eks:ListNodegroups",
"eks:DescribeNodegroup",
"bedrock:ListProvisionedModelThroughputs",
"bedrock:ListCustomModels",
"bedrock:GetModelInvocationLoggingConfiguration",
"guardduty:ListDetectors",
"guardduty:GetDetector",
"guardduty:ListFindings",
"guardduty:GetFindings",
"securityhub:DescribeHub",
"securityhub:GetFindings",
"config:Describe*",
"kms:ListKeys",
"kms:DescribeKey",
"kms:GetKeyRotationStatus",
"kms:Decrypt",
"rds:DescribeDB*",
"dynamodb:ListTables",
"dynamodb:DescribeTable",
"dynamodb:DescribeContinuousBackups",
"secretsmanager:ListSecrets",
"elasticloadbalancing:Describe*",
"backup:List*",
"apigateway:GET",
"cloudfront:List*",
"cloudfront:Get*",
"cloudwatch:GetMetricStatistics",
"sns:ListTopics",
"sqs:ListQueues"
],
"Resource": "*"
}
]
}
kms:Decrypt is required only when scanning encrypted objects/packages (for example SSE-KMS S3 object reads). Scope this to required KMS keys in production.
For --org-scan, the management principal must be allowed to assume a member-account role (default: OrganizationAccountAccessRole), for example:
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::*:role/OrganizationAccountAccessRole"
}
Member account role trust policy must also allow your scanner principal (user/role) to assume it (and include sts:ExternalId condition if you use --external-id).
Tip: For a quick start, attach the AWS managed policy
arn:aws:iam::aws:policy/ReadOnlyAccessto your IAM user/role.
Apache License 2.0
| 38 |
| Extended (Lambda, ELB, Route53, Inspector, etc.) | 35 |
| Container Security (ECS + EKS) | 22 |
| AI Attack Detection | 7 |
| Total | 102 |
| Check | Severity | Description |
|---|
| Privilege Escalation | 🔴 Critical | User can escalate to admin |
| Admin Access (:) | 🔴 Critical | Full AWS access granted |
| Exposed Secrets | 🔴 Critical | API keys/tokens in Lambda/EC2 |
| Public S3 Bucket | 🔴 Critical | Bucket publicly accessible |
| No CloudTrail | 🔴 Critical | No audit logging |
| Open SSH/RDP | 🔴 Critical | Port 22/3389 to internet |
| Privileged Container | 🔴 Critical | ECS container with root access |
| GPU IMDSv1 | 🔴 Critical | GPU instance credentials vulnerable |
| Cross-Account Trust | 🟠 High | External account can assume role |
| EKS Public Endpoint | 🟠 High | Kubernetes API publicly accessible |
| Bedrock No Logging | 🟠 High | AI model usage not audited |
| Flag | Short | Description |
|---|
--profile | -p | AWS profile to use |
--region | -r | AWS region |
--regions | Comma-separated regions | |
--all-regions | Scan all enabled regions | |
--org-scan | Scan all active AWS Organization accounts | |
--org-role-name | IAM role name to assume in member accounts | |
--external-id | External ID for cross-account assume role | |
--output | -o | Output format: table, json, or html |
--rules | Print rules catalog Markdown and exit | |
--capabilities | Print capabilities Markdown and exit | |
--output-file | -f | Output file (required for html) |
--store | Persist scan results in SQLite | |
--db-path | Custom SQLite DB path | |
--trends | Show historical trends | |
--trend-days | Trend window in days (default 30) | |
--compare | Compare two recent scans | |
--export-json | Export trends JSON file | |
--export-csv | Export trends CSV file | |
--account-id | Account filter for trends/history | |
--max-parallel | Max concurrent region/account scan units | |
--best-effort | For multi-region scans, return success if at least one region succeeds | |
--dry-run | Remediation preview mode | |
--remediate | Apply supported remediations | |
--dashboard-port | Dashboard port (root flag; dashboard subcommand uses --port) | |
--version | -v | Version information |
| Phase | Status | Rules |
|---|
| Phase 1 | ✅ Complete | 73 |
| Phase 2 | 🔲 In Progress | +20 |
| Phase 2.5 | 🆕 AI Attack Detection | +13 |
| Phase 3 | 🔲 Planned | +15 |
| Phase 4 | 🔲 Planned | Features |