> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ubiquex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Security Scan — Clean Project

> Write infrastructure that passes all 8 built-in security rules at ubx scan time.

Security misconfigurations in cloud infrastructure are the most common source of breaches — public S3 buckets, unencrypted RDS instances, IAM policies with wildcard actions, security groups that expose SSH or RDP to the internet. ubx ships with 8 built-in security rules that check for exactly these patterns, evaluated at `ubx scan` time without cloud credentials, without Checkov, without a separate security scanner. Each rule checks a specific pattern in your `.iac` resource attributes — the same attributes you already declared for provisioning. This tutorial shows a stack deliberately written to pass all 8 rules, with inline comments explaining what each rule checks and why the attribute values shown satisfy it. Use it as a reference when your own `ubx scan` surfaces violations.

## What you'll learn

* The 8 built-in security rules and what each checks
* How to write `.iac` resources that satisfy each rule
* How `ubx scan` differs from `ubx validate` — and when to run each

***

## Why this matters

<Info>
  `ubx scan` runs at validate time — no cloud credentials, no network, no apply. Every security rule violation is caught before CI even gets to plan. A stack that doesn't pass `ubx scan` is a stack with known security misconfigurations, not a hypothetical risk.
</Info>

***

## The source code

<CodeGroup>
  ```hcl main.iac theme={"theme":"css-variables"}
  # S3_PUBLIC_BUCKET: no public-read or public-read-write acl → PASS
  # S3_VERSIONING_DISABLED: versioning.enabled = true → PASS
  unit "aws_s3_bucket_v2" "assets" {
    bucket = "myapp-${input.env}-assets"
    versioning = {
      enabled = true
    }
  }

  unit "aws_s3_bucket_v2" "logs" {
    bucket = "myapp-${input.env}-logs"
    versioning = {
      enabled = true
    }
  }

  # RDS_ENCRYPTION_DISABLED: storage_encrypted = true → PASS
  # RDS_BACKUP_RETENTION:    backup_retention_period >= 7 → PASS
  # RDS_PUBLICLY_ACCESSIBLE: publicly_accessible not set (defaults false) → PASS
  unit "aws_rds_instance" "db" {
    engine                  = "postgres"
    instance_class          = "db.t3.micro"
    identifier              = "myapp-${input.env}-db"
    storage_encrypted       = true
    backup_retention_period = 7
  }

  # IAM_WILDCARD_ACTION: actions are scoped ("s3:GetObject"), not "*" → PASS
  unit "aws_iam_policy" "app_policy" {
    name   = "myapp-${input.env}-policy"
    policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:GetObject\",\"s3:PutObject\"],\"Resource\":\"*\"}]}"
  }

  # SG_OPEN_INGRESS: ingress on port 443 only (not 22/SSH or 3389/RDP) → PASS
  unit "aws_ec2_security_group" "web" {
    name        = "myapp-${input.env}-web-sg"
    description = "Allow HTTPS inbound"
    ingress = {
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }

  # EKS_PUBLIC_ENDPOINT: public access enabled BUT private access also enabled → PASS
  # (both must be true to pass; public-only endpoint fails this rule)
  unit "aws_eks_cluster" "main" {
    name     = "myapp-${input.env}-cluster"
    role_arn = "arn:aws:iam::123456789012:role/eks-cluster"
    vpc_config = {
      subnet_ids              = ["subnet-aaa", "subnet-bbb"]
      endpoint_public_access  = true
      endpoint_private_access = true
    }
  }
  ```

  ```hcl inputs.iac theme={"theme":"css-variables"}
  input "env" {
    type    = "string"
    default = "prod"
  }
  ```

  ```hcl outputs.iac theme={"theme":"css-variables"}
  output "assets_bucket" { value = unit.aws_s3_bucket_v2.assets.bucket }
  ```

  ```hcl ubx.iac theme={"theme":"css-variables"}
  ubx {
    name    = "security-scan-clean-project"
    runtime = "typescript"
    stacks  = ["dev", "staging", "prod"]

    scan {
      enabled  = true
      severity = "high"
    }
  }

  backend "s3" {
    bucket = "my-ubx-state"
    region = "us-east-1"
    key    = "${stack}/security-scan.tfstate"
  }
  ```
</CodeGroup>

***

## The 8 built-in security rules

| Rule ID                   | Resource                 | What it checks            | Failing condition                                                     |
| ------------------------- | ------------------------ | ------------------------- | --------------------------------------------------------------------- |
| `S3_PUBLIC_BUCKET`        | `aws_s3_bucket_v2`       | ACL is not public         | `acl = "public-read"` or `"public-read-write"`                        |
| `S3_VERSIONING_DISABLED`  | `aws_s3_bucket_v2`       | Versioning is enabled     | `versioning.enabled != true`                                          |
| `RDS_ENCRYPTION_DISABLED` | `aws_rds_instance`       | Storage is encrypted      | `storage_encrypted != true`                                           |
| `RDS_BACKUP_RETENTION`    | `aws_rds_instance`       | Backup retention ≥ 7 days | `backup_retention_period < 7`                                         |
| `RDS_PUBLICLY_ACCESSIBLE` | `aws_rds_instance`       | Not publicly accessible   | `publicly_accessible = true`                                          |
| `IAM_WILDCARD_ACTION`     | `aws_iam_policy`         | No wildcard `*` action    | `Action = "*"` in policy JSON                                         |
| `SG_OPEN_INGRESS`         | `aws_ec2_security_group` | No open SSH/RDP ingress   | Port 22 or 3389 open to `0.0.0.0/0`                                   |
| `EKS_PUBLIC_ENDPOINT`     | `aws_eks_cluster`        | Private endpoint enabled  | `endpoint_public_access = true` AND `endpoint_private_access = false` |

***

## How it works

<Steps>
  <Step title="ubx scan evaluates rules against resolved attribute values">
    Each rule is evaluated against the `.iac` attribute map — the same values used for type-checking. Rules check literal values only; `Pending<T>` attributes (resource outputs) are excluded from scan evaluation since their values aren't known at compile time.
  </Step>

  <Step title="Severity filtering controls which violations are shown">
    `severity = "high"` in the `scan { }` config means only high-severity rules are checked. The 8 built-in rules are all high severity. Lower-severity rules (informational, best-practice) are checked when `severity = "medium"` or `"low"`.
  </Step>

  <Step title="A clean scan produces no output">
    When all rules pass, `ubx scan` outputs `✓ scan OK` with no violation messages. This is the expected state in CI — a clean scan means zero known misconfigurations at the attribute level.
  </Step>
</Steps>

***

## Common mistakes

<Warning>
  `backup_retention_period = 0` disables automated backups entirely and fails `RDS_BACKUP_RETENTION`. The minimum passing value is 7. Setting `backup_retention_period = 1` for dev is common but will fail the scan unless you lower the scan severity or add an exception for dev stacks.
</Warning>

<Tip>
  Run `ubx scan` separately from `ubx validate` in CI — `ubx validate` checks types and syntax, `ubx scan` checks security. Both should pass before a PR is mergeable. Add both as separate CI steps with different failure messages so developers know immediately whether they have a type error or a security misconfiguration.
</Tip>

***

## Run it

```bash theme={"theme":"css-variables"}
ubx validate           # type-checks only — scan rules not evaluated
ubx scan               # evaluates all 8 built-in security rules
ubx scan --severity low  # includes lower-severity rules
```

***

## What you learned

<Check>ubx ships with 8 built-in security rules covering S3, RDS, IAM, security groups, and EKS</Check>
<Check>`ubx scan` runs at validate time — no cloud credentials, no network access required</Check>
<Check>A clean scan outputs `✓ scan OK` — zero violations means zero known misconfigurations</Check>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Shared apply history" href="/v1/tutorials/51-shared-apply-history">
    Track every apply across your team with ubx history
  </Card>

  <Card title="policy as code" href="/v1/tutorials/30-policy-as-code">
    Write custom security rules beyond the 8 built-ins
  </Card>
</CardGroup>

***

<Info>
  Full runnable example: [github.com/ubiquex/ubx-examples/50-security-scan-clean-project](https://github.com/ubiquex/ubx-examples/tree/main/50-security-scan-clean-project)
</Info>
