Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## [Unreleased]

### 추가
- AWS CloudFormation 템플릿 misconfiguration 룰팩 `scanner/rules/cloudformation.yml`을 추가했습니다(정밀 룰 6종, YAML/JSON/`.template` 대상). Terraform-AWS는 기존 엔진이 커버하지만 raw CFN 템플릿은 공백이었습니다. 모든 패턴을 CFN 고유 컨텍스트(`AWS::` 리소스 타입, PascalCase 속성명)에 앵커링해 Kubernetes 매니페스트·docker-compose·GitHub Actions 워크플로 같은 YAML 유사 파일에서는 발화하지 않음을 테스트로 검증했습니다.
- `cfn-iam-policy-star-star` — IAM 정책이 `Action`·`Resource` 모두 와일드카드(사실상 계정 전체 관리자 권한). Statement 경계를 넘는 오탐 차단. CRITICAL.
- `cfn-s3-bucket-public-acl` — S3 버킷 `AccessControl`이 PublicRead/PublicReadWrite(전 세계 공개). HIGH.
- `cfn-security-group-open-world` — 보안 그룹 ingress가 `0.0.0.0/0`·`::/0`에 개방(기본값인 open egress는 오탐 없이 통과). HIGH.
- `cfn-rds-publicly-accessible` — `PubliclyAccessible: true`(DB 인스턴스 인터넷 직접 노출). HIGH.
- `cfn-storage-unencrypted` — RDS `StorageEncrypted: false` 또는 EBS 볼륨 `Encrypted: false`(저장 데이터 미암호화). HIGH.
- `cfn-secret-parameter-default` — 시크릿 성격 이름의 Parameter에 리터럴 `Default` 값 커밋(`{{resolve:...}}` 동적 참조는 안전으로 통과). HIGH.
- `tests/test_cloudformation_rules.py` — 룰별 양성/음성 패턴 테스트, severity 검증, e2e 스캔(오염 템플릿에서 6종 전부 발화, 안전 템플릿 0건), k8s/compose/GitHub Actions look-alike 음성 테스트 포함(총 29건).

### 추가
- `appguardrail fix` 명령 — 안전하고 결정적인 자동 수정을 적용합니다(기본 dry-run diff, `--apply`로 기록). 의미를 바꾸지 않는 순수 additive 변환만 수행하며, 첫 변환으로 외부 `target="_blank"` 링크에 `rel="noopener noreferrer"`를 추가합니다(reverse tabnabbing 방지). 동작을 바꾸는 수정(시크릿→env 등)은 위험하므로 자동 적용하지 않고 fix-pack 프롬프트로 남깁니다. scan→fix→verify 루프를 안전하게 닫습니다.

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ Detects:
- Trivy-backed dependency vulnerabilities, secrets, and misconfigurations
- Bandit/Ruff/Semgrep/ZAP findings when their optional external engines are available
- Dangerous Supabase/Firebase usage patterns
- AWS CloudFormation template misconfigurations (public S3 ACLs, world-open
security groups, `*:*` IAM policies, public/unencrypted databases, secret
parameter defaults)
- API routes missing authentication
- Public Firebase rules (`read/write: true`)
- Dangerous CORS settings (`origin: "*"`)
Expand Down
142 changes: 142 additions & 0 deletions scanner/rules/cloudformation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
rules:
# AWS CloudFormation template misconfiguration rules.
#
# These rules target raw CloudFormation templates (.yaml/.yml/.json and
# .template files containing "Resources:" blocks with "Type: AWS::..."
# resource declarations). Terraform-AWS misconfigurations are covered by
# other engines; this pack closes the raw-CFN gap.
#
# Precision notes: every pattern is anchored on CloudFormation-specific
# context (AWS:: resource type names or PascalCase CFN property names) so
# the rules do not fire on YAML look-alikes such as Kubernetes manifests,
# docker-compose files, or GitHub Actions workflows.

- id: cfn-s3-bucket-public-acl
patterns:
- pattern-regex: 'Type["'']?\s*:\s*["'']?AWS::S3::Bucket\b[\s\S]{0,600}?AccessControl["'']?\s*:\s*["'']?PublicRead(?:Write)?'
message: |
CloudFormation S3 bucket declares a public canned ACL. AccessControl
set to a Public value makes every object readable (or writable) by
anyone on the internet and is the classic S3 data-breach vector.
Remove the public AccessControl setting, add a
PublicAccessBlockConfiguration with all four flags enabled, and serve
public assets through CloudFront with Origin Access Control instead.
severity: HIGH
languages: [generic]
cwe: [CWE-732]
owasp: [A01:2021]
paths:
include:
- "**/*.yml"
- "**/*.yaml"
- "**/*.json"
- "**/*.template"

- id: cfn-security-group-open-world
patterns:
- pattern-regex: 'SecurityGroupIngress["'']?\s*:(?:(?!Egress)[\s\S]){0,600}?CidrIp(?:v6)?["'']?\s*:\s*["'']?(?:0\.0\.0\.0/0|::/0)'
- pattern-regex: 'Type["'']?\s*:\s*["'']?AWS::EC2::SecurityGroupIngress\b[\s\S]{0,400}?CidrIp(?:v6)?["'']?\s*:\s*["'']?(?:0\.0\.0\.0/0|::/0)'
message: |
CloudFormation security group ingress rule is open to the entire
internet (0.0.0.0/0 or ::/0). Anyone can reach the exposed port, which
invites brute-force and exploitation attempts against SSH, RDP,
databases, and admin panels. Restrict CidrIp to a trusted CIDR range,
reference another security group with SourceSecurityGroupId, or use
AWS Systems Manager Session Manager instead of public SSH.
severity: HIGH
languages: [generic]
cwe: [CWE-284]
owasp: [A05:2021]
paths:
include:
- "**/*.yml"
- "**/*.yaml"
- "**/*.json"
- "**/*.template"

- id: cfn-iam-policy-star-star
patterns:
- pattern-regex: '(?<!\w)Action["'']?\s*:\s*(?:\[\s*)?(?:\n\s*-\s*)?["'']?\*["'']?(?:(?!Effect)[\s\S]){0,300}?(?<!\w)Resource["'']?\s*:\s*(?:\[\s*)?(?:\n\s*-\s*)?["'']?\*["'']?'
- pattern-regex: '(?<!\w)Resource["'']?\s*:\s*(?:\[\s*)?(?:\n\s*-\s*)?["'']?\*["'']?(?:(?!Effect)[\s\S]){0,300}?(?<!\w)Action["'']?\s*:\s*(?:\[\s*)?(?:\n\s*-\s*)?["'']?\*["'']?'
message: |
IAM policy statement grants every action on every resource
(wildcard for both fields). Any principal with this policy has full
administrative control of the AWS account, so one leaked credential or
compromised service becomes a total account takeover. Scope the policy
to the specific actions and resource ARNs the workload needs
(least privilege), and use IAM Access Analyzer to right-size it.
severity: CRITICAL
languages: [generic]
cwe: [CWE-269]
owasp: [A01:2021]
paths:
include:
- "**/*.yml"
- "**/*.yaml"
- "**/*.json"
- "**/*.template"

- id: cfn-rds-publicly-accessible
patterns:
- pattern-regex: '(?<!\w)PubliclyAccessible["'']?\s*:\s*["'']?[Tt]rue\b'
message: |
Database instance is configured as publicly accessible
(PubliclyAccessible set to true). The database receives a public IP
and can be reached directly from the internet, exposing it to
credential stuffing and known-CVE exploitation. Set
PubliclyAccessible to false, place the instance in private subnets,
and reach it through a bastion, VPN, or SSM port forwarding.
severity: HIGH
languages: [generic]
cwe: [CWE-284]
owasp: [A05:2021]
paths:
include:
- "**/*.yml"
- "**/*.yaml"
- "**/*.json"
- "**/*.template"

- id: cfn-storage-unencrypted
patterns:
- pattern-regex: '(?<!\w)StorageEncrypted["'']?\s*:\s*["'']?[Ff]alse\b'
- pattern-regex: 'Type["'']?\s*:\s*["'']?AWS::EC2::Volume\b[\s\S]{0,400}?(?<!\w)Encrypted["'']?\s*:\s*["'']?[Ff]alse\b'
message: |
CloudFormation storage resource disables encryption at rest
(StorageEncrypted or Encrypted set to false on an RDS instance or EBS
volume). Data, snapshots, and backups are stored in plaintext, which
fails most compliance baselines (CIS, SOC 2, HIPAA). Set the flag to
true; AWS-managed KMS keys add no operational overhead, or supply a
customer-managed KmsKeyId for stricter control.
severity: HIGH
languages: [generic]
cwe: [CWE-311]
owasp: [A02:2021]
paths:
include:
- "**/*.yml"
- "**/*.yaml"
- "**/*.json"
- "**/*.template"

- id: cfn-secret-parameter-default
patterns:
- pattern-regex: '^[ \t]+\w*(?i:password|passphrase|secret|credential|apikey|token)\w*[ \t]*:[ \t]*\n(?:(?![ \t]*\w+:[ \t]*$)[^\n]*\n){0,6}?[ \t]*Default[ \t]*:[ \t]*["'']?(?!\{\{resolve)[^\s"''][^\n]{3,}'
- pattern-regex: '["'']\w*(?i:password|passphrase|secret|credential|apikey|token)\w*["'']\s*:\s*\{[^{}]{0,300}?["'']Default["'']\s*:\s*["''](?!\{\{resolve)[^"'']{4,}'
message: |
CloudFormation parameter with a secret-like name ships a literal
Default value. The default is committed to the template, visible in
the CloudFormation console and describe-stacks output, and reused by
every stack that omits the parameter. Remove the Default, set
NoEcho to true, and resolve the value at deploy time from AWS Secrets
Manager or SSM SecureString dynamic references.
severity: HIGH
languages: [generic]
cwe: [CWE-798]
owasp: [A07:2021]
paths:
include:
- "**/*.yml"
- "**/*.yaml"
- "**/*.json"
- "**/*.template"
Loading
Loading