Skip to content

[REVIEW] iac-security: add Pulumi and Bicep evidence gates #1116

@bnpl7

Description

@bnpl7

Skill Being Reviewed

Skill name: iac-security
Skill path: skills/cloud/iac-security/

False Positive Analysis

Benign Pulumi code that can be incorrectly flagged by literal/attribute-name scanning:

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,
});

Why this is a false positive:

The current skill's detailed rule set is mostly Terraform/CloudFormation-shaped and relies heavily on literal patterns such as password, token, secret_key, and scanner-equivalent source checks. In Pulumi, a password property is not necessarily a committed secret or plaintext state value. When the value comes from requireSecret() / getSecret() or is wrapped with pulumi.secret(), Pulumi tracks the value as secret and encrypts it in state. The review should distinguish a plain config read from a Pulumi secret-tainted Output, otherwise valid code like the example above can be reported as a hardcoded-secret finding.

Benign Bicep code that can be over-reported as an admin-password secret:

@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
    }
  }
}

Why this is a false positive:

The presence of an adminPassword property is not by itself a finding in Bicep. Microsoft documents @secure() as the expected pattern for sensitive string/object parameters, and the Bicep linter's secure-input rule passes this pattern. The skill should ask whether the value is a secure parameter or secure output, not only whether a sensitive-looking property name exists.

Coverage Gaps

Missed variant 1: Pulumi plain config values passed into secret-bearing resource arguments

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

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

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

Why it should be caught:

This contains no literal password value in source, so the existing hardcoded-secret grep examples may not flag it. But Pulumi configuration is plain text by default unless set with --secret and read through secret APIs. The IaC review should check Pulumi config files (Pulumi.<stack>.yaml), SDK calls (require, get, requireSecret, getSecret), and resource argument flow into known sensitive fields.

Missed variant 2: Pulumi secret values exposed inside apply callbacks

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

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

Why it should be caught:

Pulumi keeps secret outputs encrypted in state, but code inside an apply callback receives the plaintext value. Pulumi's own docs warn that the callback can still expose the plaintext value by printing it or writing it elsewhere. The current skill does not include Pulumi-specific secret-flow checks, so it can miss secret disclosure through logs, files, stack outputs, CI annotations, or custom provider code.

Missed variant 3: Bicep insecure parameters for secure properties

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
    }
  }
}

Why it should be caught:

This is not a literal hardcoded secret, so a simple string scanner may miss it. It is still insecure because a sensitive VM password is sourced from a non-secure Bicep parameter. The skill should include Bicep linter-equivalent checks for secure inputs, especially sensitive paths such as properties.osProfile.adminPassword.

Missed variant 4: Bicep outputs leaking list* values or secure parameters

resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' existing = {
  name: 'prodsa'
}

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

Why it should be caught:

The current skill mentions CloudFormation NoEcho but does not model Bicep/ARM deployment-history leakage. Bicep outputs can expose values available through list* functions, and output values are visible in deployment history unless handled as secure output where supported. The review should check for outputs-should-not-contain-secrets patterns and require @secure() or no output for sensitive values.

Edge Cases

  • Pulumi YAML uses configuration.<key>.secret: true, not the same SDK syntax as TypeScript, Python, Go, .NET, or Java. The skill should cover all supported Pulumi languages it claims to review.
  • Pulumi additionalSecretOutputs can make provider-computed outputs secret even when the source code does not include a secret-looking input.
  • Pulumi secret state protection depends on the configured secrets provider; a self-managed backend without a secure secrets provider changes the evidence needed.
  • Bicep @secure() is valid only for string/object parameters and, in newer Bicep versions, secure outputs. Arrays/numbers need wrapping or serialization rather than a direct secure decorator.
  • Bicep deployment scripts and module outputs can leak secrets even when the top-level file uses secure parameters correctly.

Remediation Quality

  • Fix resolves the vulnerability
  • Fix doesn't introduce new security issues
  • Fix doesn't break functionality
  • Issues found: Add Pulumi and Bicep-specific evidence gates rather than treating them as Terraform/CloudFormation variants. The report should identify language/runtime, config secret status, state/secrets provider, secret-taint propagation, callback/log exposure, secure Bicep parameter/output usage, and Bicep linter-equivalent rule results.

Comparison to Other Tools

Tool Catches this? Notes
Pulumi engine / Pulumi config Partial Tracks secret-tainted outputs, but reviewers still need to check plain config reads, callback leakage, stack outputs, and secrets provider posture.
Bicep linter Yes Has rules for secure values assigned to secure inputs and outputs that should not contain secrets.
Checkov / KICS Partial Can scan some Bicep/ARM and Terraform-like misconfigurations, but does not replace language-aware Pulumi secret-flow review.
tfsec / Trivy IaC Partial Strong Terraform coverage, weaker fit for Pulumi program semantics and Bicep secure decorators.

Overall Assessment

Strengths:

  • Good high-level coverage domains for IaC reviews.
  • Useful Terraform state and provider-default pitfalls.
  • Strong prompt-injection guidance for untrusted IaC comments and scanner suppressions.

Needs improvement:

  • The claimed Pulumi and Bicep support is not backed by language-specific checks in tool-rules.md.
  • The hardcoded-secret guidance can over-report safe Pulumi/Bicep secret references and under-report unsafe non-literal secret flow.
  • The output format does not capture Pulumi secret-taint evidence, Pulumi secrets provider posture, Bicep secure parameter/output evidence, or Bicep linter rule coverage.

Priority recommendations:

  1. Add a Pulumi section covering requireSecret / getSecret, pulumi.secret, additionalSecretOutputs, plain Config.require, apply callback leakage, stack outputs, and secrets provider configuration.
  2. Add Bicep checks for @secure() parameters, secure outputs, list* output leakage, deployment history exposure, and secure-input linter rules.
  3. Add an evidence-origin field per finding: Terraform source, Terraform plan, CloudFormation, Pulumi SDK, Pulumi stack config, Bicep source, Bicep linter, or Not Evaluable.
  4. Add benign/vulnerable examples for Pulumi TypeScript/Python and Bicep so reviewers do not apply Terraform-only heuristics to different IaC languages.

Sources Checked

Bounty Info

  • I have read and agree to the CONTRIBUTING.md bounty terms
  • Preferred payment method: PayPal, to be provided privately after acceptance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions