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
46 changes: 44 additions & 2 deletions skills/cloud/iac-security/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,43 @@ Use Glob to locate all IaC configuration files.
**/Pulumi.*.yaml
**/__main__.py # Pulumi Python
**/index.ts # Pulumi TypeScript
**/Program.cs # Pulumi .NET
**/main.go # Pulumi Go
**/main.py # Pulumi Python alternate
**/*.bicepparam
```

Classify the IaC stack(s) in use. Record the total file count and frameworks detected.

---

### Step 2 through Step 9: Security Domain Evaluation
### Step 2: Framework-Specific Secret Semantics

Before applying generic hardcoded-secret rules, determine whether the framework has its own secret-taint or secure-input model. Terraform, CloudFormation, Pulumi, and Bicep expose sensitive values differently, so findings must include the evidence origin and should not be based only on a sensitive-looking property name.

**Pulumi evidence gates:**

| Gate | Evidence to Collect | Finding When Missing |
|---|---|---|
| Secret config read | `requireSecret`, `getSecret`, `pulumi.secret`, or stack config entry marked `secret: true` | Sensitive resource argument is sourced from `Config.require`, `get`, environment variables, or plaintext stack config |
| Secret-taint propagation | Secret `Output` remains secret through transformations | `apply` callback logs, writes, returns to non-secret output, or sends the plaintext to an untrusted sink |
| Stack output handling | `export` / stack outputs and `additionalSecretOutputs` reviewed | Secret values are exported as non-secret outputs or provider-computed secrets are not marked as additional secret outputs |
| Secrets provider posture | Pulumi backend and secrets provider are identified | Self-managed or local backends store stack state without an approved encryption provider |

**Bicep evidence gates:**

| Gate | Evidence to Collect | Finding When Missing |
|---|---|---|
| Secure parameter use | `@secure()` on string/object parameters used for passwords, keys, tokens, and connection strings | Sensitive property such as `adminPassword` is fed by a plain parameter |
| Secure output use | `@secure()` output where supported, or no output for sensitive values | `list*()` values, secure parameters, keys, or connection strings are emitted into deployment history |
| Linter-equivalent results | `use-secure-value-for-secure-inputs` and `outputs-should-not-contain-secrets` outcomes | Review does not distinguish safe secure parameters from unsafe plaintext values |
| Module/deployment-script boundaries | Module outputs, deployment scripts, and nested templates are traced | Secrets are re-exposed after a secure top-level parameter passes into a child module or script |

Record the evidence origin for each finding as one of: `Terraform source`, `Terraform plan`, `Terraform state`, `CloudFormation template`, `Pulumi SDK`, `Pulumi stack config`, `Pulumi state`, `Bicep source`, `Bicep linter`, `ARM deployment history`, or `Not Evaluable`.

---

### Step 3 through Step 10: Security Domain Evaluation

Evaluate all IaC configurations across eight security domains: Hardcoded Secrets Detection, Public Exposure Analysis, Encryption Gap Analysis, IAM and Access Control Review, Logging and Monitoring Gaps, Network Security Review, Supply Chain Integrity (SLSA Alignment), and Resource Hardening.

Expand All @@ -101,7 +131,7 @@ For detailed tool-specific rule sets, detection patterns, vulnerable code exampl

---

### Step 10: Compile Assessment Report
### Step 11: Compile Assessment Report

Produce the final report using the structure defined in the Output Format section.

Expand Down Expand Up @@ -157,6 +187,7 @@ Produce the final report using the structure defined in the Output Format sectio
- **Status:** Fail
- **Severity:** Critical / High / Medium / Low
- **Equivalent Rule:** Checkov CKV_XXX_NN / tfsec xxx-xxx / KICS xxxxxxxx
- **Evidence Origin:** Terraform source / Terraform plan / Terraform state / CloudFormation template / Pulumi SDK / Pulumi stack config / Pulumi state / Bicep source / Bicep linter / ARM deployment history / Not Evaluable
- **File:** <path>
- **Line(s):** <line numbers>
- **Description:** <what was found>
Expand All @@ -169,6 +200,9 @@ Produce the final report using the structure defined in the Output Format sectio
- State encryption: <encrypted / unencrypted>
- State locking: <enabled / disabled>
- Lock file committed: <yes / no>
- Pulumi secrets provider: <approved / weak / not applicable / not evaluable>
- Pulumi secret-taint review: <passed / failed / not applicable / not evaluable>
- Bicep secure input/output review: <passed / failed / not applicable / not evaluable>

### Prioritized Remediation Plan

Expand Down Expand Up @@ -230,6 +264,8 @@ This skill applies checks equivalent to the following high-impact rules:
5. **Confusing `aws_s3_bucket_acl` with `aws_s3_bucket_public_access_block`.** The public access block overrides ACLs. Check both, but the access block is the stronger control.
6. **Terraform state file secrets.** Even when variables are marked `sensitive`, they may appear in plaintext in the state file. Verify state encryption and access controls.
7. **Provider-specific encryption defaults.** Some providers encrypt by default (e.g., AWS S3 since January 2023). Know the defaults before flagging missing explicit encryption configuration.
8. **Treating Pulumi property names as proof of plaintext secrets.** A Pulumi `password` property may be safe if it receives a secret-tainted `Output` from `requireSecret()` or `pulumi.secret()`. Confirm the source, state encryption, stack output behavior, and `apply` callback sinks before flagging.
9. **Treating Bicep `adminPassword` as automatically unsafe.** A sensitive property fed by an `@secure()` parameter is the expected pattern. Flag plain parameters, `list*()` output leakage, and deployment-history exposure instead of the property name alone.

---

Expand Down Expand Up @@ -260,9 +296,15 @@ This skill applies checks equivalent to the following high-impact rules:
- cfn-nag Rules: https://github.com/stelligent/cfn_nag
- Terraform Security Best Practices: https://developer.hashicorp.com/terraform/cloud-docs/recommended-practices
- AWS Security Best Practices in IAM: https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html
- Pulumi Secrets: https://www.pulumi.com/docs/iac/concepts/secrets/
- Pulumi Configuration: https://www.pulumi.com/docs/iac/concepts/config/
- Azure Bicep Parameters: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/parameters
- Bicep linter rule: use secure value for secure inputs: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/linter-rule-use-secure-value-for-secure-inputs
- Bicep linter rule: outputs should not contain secrets: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/linter-rule-outputs-should-not-contain-secrets

---

## Changelog

- **1.0.0** -- Initial release. Coverage of eight security domains across Terraform, CloudFormation, Pulumi, and Bicep with Checkov/tfsec/KICS rule equivalents.
- **1.0.1** -- Added Pulumi secret-taint and Bicep secure-input/output evidence gates with evidence-origin reporting.
179 changes: 179 additions & 0 deletions skills/cloud/iac-security/tool-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,185 @@ variable "db_password" {
}
```

### Pulumi Secret Flow and State Checks

Pulumi programs are imperative code, so a sensitive-looking property name is not enough to prove a plaintext secret. Track whether the value is secret-tainted from configuration through resource arguments, callbacks, stack outputs, and state.

**Benign Pulumi patterns:**

```ts
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const cfg = new pulumi.Config();
const dbPassword = cfg.requireSecret("dbPassword");

new aws.rds.Instance("db", {
allocatedStorage: 20,
engine: "postgres",
instanceClass: "db.t4g.micro",
username: "app",
password: dbPassword,
});
```

```yaml
# Pulumi.production.yaml
config:
app:dbPassword:
secure: v1:...
```

```py
import pulumi
import pulumi_aws as aws

cfg = pulumi.Config()
db_password = cfg.require_secret("dbPassword")

aws.rds.Instance("db",
allocated_storage=20,
engine="postgres",
instance_class="db.t4g.micro",
username="app",
password=db_password)
```

**Pulumi findings to catch:**

```ts
// BAD: Plain config read flows into a secret-bearing resource argument.
const cfg = new pulumi.Config();
const dbPassword = cfg.require("dbPassword");

new aws.ssm.Parameter("db-password", {
type: "SecureString",
value: dbPassword,
});
```

```ts
// BAD: Secret value is exposed inside an apply callback.
const apiToken = cfg.requireSecret("apiToken");

apiToken.apply((token) => {
console.log(`deploy token: ${token}`);
return token;
});
```

```py
# BAD: Plain config read flows into a secret-bearing Python resource argument.
cfg = pulumi.Config()
db_password = cfg.require("dbPassword")

aws.ssm.Parameter("db-password",
type="SecureString",
value=db_password)
```

```ts
// BAD: Stack output may disclose a secret unless kept secret-tainted.
export const connectionString = db.endpoint.apply((endpoint) => `postgres://${dbPassword}@${endpoint}`);
```

**Detection patterns:**

```
# Pulumi config and secret APIs
Grep: "new pulumi.Config|Config\\(|requireSecret|getSecret|pulumi.secret|Output.secret|additionalSecretOutputs" in **/*.{ts,js,py,go,cs}
Grep: "require\\(|get\\(|process.env|os.environ|System.getenv|Environment.GetEnvironmentVariable" in **/*.{ts,js,py,go,cs}

# Pulumi callback, output, and logging sinks
Grep: "\\.apply\\(|pulumi.interpolate|export const|ctx.Export|pulumi.export" in **/*.{ts,js,py,go,cs}
Grep: "console.log|print\\(|logger|writeFile|appendFile|setOutput|GITHUB_OUTPUT" in **/*.{ts,js,py,go,cs}

# Pulumi stack configuration and secrets provider posture
Grep: "secretsprovider|encryptedkey|secure:|secret: true" in **/Pulumi*.yaml
```

**Severity calibration:**

| Condition | Severity |
|---|---|
| Plain Pulumi config or environment value flows into a password/token/key field | Critical |
| Secret `Output` is logged, written to disk, CI output, or a non-secret stack output | Critical |
| Pulumi stack state uses a weak or unknown secrets provider for production secrets | High |
| Provider-computed secret output is not listed in `additionalSecretOutputs` | Medium |
| Safe `requireSecret` / `pulumi.secret` value is passed directly to a sensitive property | Pass |

### Bicep Secure Input and Deployment History Checks

Bicep and ARM deployments can leak secrets through plain parameters, plain outputs, `list*()` functions, deployment scripts, and module outputs. Match Bicep linter-equivalent rules before applying generic secret-name heuristics.

**Benign Bicep pattern:**

```bicep
@secure()
param adminPassword string

resource vm 'Microsoft.Compute/virtualMachines@2025-04-01' = {
name: 'safe-vm'
location: resourceGroup().location
properties: {
osProfile: {
computerName: 'safe-vm'
adminUsername: 'azureuser'
adminPassword: adminPassword
}
}
}
```

**Bicep findings to catch:**

```bicep
// BAD: Sensitive VM password is sourced from a plain parameter.
param adminPassword string

resource vm 'Microsoft.Compute/virtualMachines@2025-04-01' = {
name: 'unsafe-vm'
location: resourceGroup().location
properties: {
osProfile: {
computerName: 'unsafe-vm'
adminUsername: 'azureuser'
adminPassword: adminPassword
}
}
}
```

```bicep
// BAD: list* value is exposed through deployment history.
resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' existing = {
name: 'prodsa'
}

output primaryKey string = storage.listKeys().keys[0].value
```

**Detection patterns:**

```
# Bicep sensitive inputs and outputs
Grep: "@secure\\(\\)|param .*Password|param .*Secret|param .*Token|param .*Key|output .*string" in **/*.bicep **/*.bicepparam
Grep: "adminPassword|connectionString|clientSecret|listKeys\\(|listSecrets\\(|listCredentials\\(|list[A-Za-z]+\\(" in **/*.bicep

# Bicep module and deployment-script boundaries
Grep: "module .* =|Microsoft.Resources/deploymentScripts|outputs\\.|environmentVariables|secureValue" in **/*.bicep
```

**Severity calibration:**

| Condition | Severity |
|---|---|
| Sensitive Bicep property is sourced from a plain parameter or variable | Critical |
| `list*()` secret, key, token, or connection string is emitted as a plain output | Critical |
| Secure top-level parameter is re-exposed through a module output or deployment script log | Critical |
| Bicep linter secure-input/output evidence is missing for production templates | Medium |
| Sensitive Bicep property is fed by an `@secure()` string/object parameter with no output exposure | Pass |

---

## Public Exposure Analysis
Expand Down