Catch the Bad IAM Change in the Pull Request, Not the Postmortem: Policy-as-Code for Teams Without a Security Engineer

Learn how to enforce security checks in pull requests with policy-as-code. Shift-left infrastructure security for teams that lack a dedicated security engineer.

Catch the Bad IAM Change in the Pull Request, Not the Postmortem: Policy-as-Code for Teams Without a Security Engineer

A senior engineer pushes a Terraform change that grants s3:* to a new service role. The CI pipeline runs. Tests pass. The PR merges. Three weeks later, a security review — or worse, a breach investigation — surfaces the overprivileged role. Nobody remembers the context. The original author has moved on to other work. The remediation involves a tense incident call and a hasty rollback that breaks two downstream services.

This is not a hypothetical edge case. It is the default outcome when IAM changes flow through the same review process as application code without any automated policy checks. And it is preventable — not with a security hire you cannot afford, but with tooling that encodes your security intent directly into the pull request workflow.

Why IAM Changes Are the Highest-Risk Commits in Your Repo

Application bugs tend to produce visible symptoms quickly: a broken page, a failed test, a spike in error rates. IAM misconfigurations are different. They are silent. An overprivileged role works perfectly — it just works too well. It grants access that nothing needs today but that an attacker or a misconfigured service could exploit tomorrow.

The challenge compounds in infrastructure-as-code environments. Terraform plans can show you that a policy document changed, but they do not tell you whether the new policy violates your security posture. A reviewer scanning a 200-line plan output at the end of a long day is unlikely to catch the difference between s3:GetObject and s3:* on a resource wildcard. This is not a people problem. It is a tooling gap.

What Policy-as-Code Actually Means (and Does Not Mean)

Policy-as-code is the practice of expressing your security, compliance, and operational rules as machine-readable definitions that run automatically against infrastructure changes before those changes reach production. The key phrase is before those changes reach production. If your security checks happen in a weekly scan or a quarterly audit, you are catching drift after the fact. Policy-as-code shifts the enforcement left — into the pull request, where the engineer still has context and the fix is cheap.

What it does not mean: buying a platform that generates hundreds of generic findings you never triage. The value is not in the volume of rules. It is in encoding your specific security intent — the rules that matter for your environment, your threat model, your compliance requirements — and enforcing them as a gate that blocks the merge.

The Core Components

A working policy-as-code setup for Terraform security scanning has three layers:

  • Static analysis of IaC definitions. Tools like OPA (Open Policy Agent), Checkov, or tfsec parse your Terraform HCL or plan JSON and evaluate it against a rule set. These catch structural problems: wildcard permissions, missing encryption flags, public access on storage buckets, security groups open to 0.0.0.0/0.
  • Custom policies that reflect your standards. Off-the-shelf rule sets are a starting point, not a finish line. You need the ability to write rules specific to your environment: naming conventions for roles, required tags, allowed regions, approved instance types. OPA's Rego language or Checkov's Python-based custom checks give you this flexibility.
  • CI integration that blocks the merge. The rules run as a step in your CI pipeline — typically after terraform plan — and the pipeline fails if a violation is detected. The engineer sees the failure in the PR, reads a clear message explaining what rule was violated and why, and fixes it before requesting review. No Slack thread. No ticket. No postmortem.

Designing Your First Rule Set Without Drowning in Noise

The most common failure mode for teams adopting policy-as-code is not technical. It is scope. A team enables every built-in rule on day one, gets 400 findings across their existing codebase, and quietly disables the check a week later because it blocks all forward progress.

Start with five rules. Pick the five IAM and network patterns that would cause the most damage if they reached production unchecked. For most teams, these include:

  1. No IAM policies with * actions on * resources (the admin-by-accident pattern)
  2. No security groups with ingress open to 0.0.0.0/0 on non-standard ports
  3. No S3 buckets without server-side encryption enabled
  4. No IAM users with inline policies (use groups or roles instead)
  5. No resources without a required set of tags (owner, environment, cost center)

These five rules catch a disproportionate share of the security misconfigurations that cause real incidents. Ship them. Let the team build muscle memory around the workflow. Then add rules incrementally as your security posture matures.

Writing Rules That Engineers Will Actually Read

A policy check that outputs FAILED: policy CKV_AWS_40 is technically correct and practically useless. The engineer has to go look up what CKV_AWS_40 means, find the relevant documentation, and figure out how to fix it. That friction is where adoption dies.

Every rule you enforce should produce a message that includes three things: what was detected, why it matters, and how to fix it. For example:

FAILED: IAM policy on resource aws_iam_role.data_processor grants s3:* — use least-privilege actions (s3:GetObject, s3:PutObject) scoped to the specific bucket ARN. See internal wiki: /docs/iam-standards#s3-access

This costs ten minutes of additional work when writing the rule. It saves hours of confusion across every engineer who trips the check over the lifetime of that policy.

The Trade-Offs You Should Expect

Policy-as-code is not free, and pretending otherwise undermines credibility when you advocate for it internally. Here are the real costs:

Maintenance overhead. Policies are code. They need version control, testing, and review. When your infrastructure patterns evolve — new AWS services, new regions, new naming conventions — your policies need to evolve with them. Budget time for this. If nobody owns the policy repo, it will rot.

False positives create fatigue. A rule that fires on a legitimate pattern erodes trust in the entire system. When you encounter a false positive, fix the rule immediately. If you cannot fix it cleanly, add a scoped exception with a comment explaining why, not a blanket suppression.

Pipeline speed matters. If your policy checks add four minutes to a CI run that already takes eight, engineers will find workarounds. Static analysis of HCL files is fast — typically under 30 seconds. Evaluating against a full Terraform plan is slower but more accurate. Know the trade-off and choose based on your pipeline's current budget.

It does not replace human review. Policy-as-code catches the patterns you have encoded. It does not catch novel attack vectors, business logic flaws in access patterns, or architectural decisions that are technically compliant but strategically wrong. It raises the floor. It does not replace the judgment of an experienced reviewer — it frees that reviewer to focus on the decisions that require context, not the ones that a machine can check.

A Practical Implementation Path

For a team running Terraform with GitHub Actions or GitLab CI, the implementation path looks like this:

Week One: Foundation

Install your chosen tool (Checkov integrates with the least friction for most teams). Run it locally against your existing codebase to baseline your current findings. Do not try to fix everything. Categorize: what is critical, what is noise, what is a known exception.

Week Two: CI Integration

Add the scanner as a CI step that runs on pull requests targeting your infrastructure directories. Set it to warn mode initially — it annotates the PR but does not block the merge. This lets the team see what the tooling catches without disrupting delivery.

Week Three: Enforce

Switch the five critical rules to blocking mode. PRs that violate these rules cannot merge. Communicate the change clearly: what rules are enforced, why they matter, how to resolve violations. Make the internal documentation findable.

Ongoing: Iterate

Add one to two new rules per sprint based on what comes up in architecture reviews, incident retros, or compliance requirements. Review your exception list quarterly — some exceptions are temporary and should expire.

Where This Fits in the Larger Security Picture

Policy-as-code for pull requests is one layer in a defense-in-depth approach. It works best alongside runtime monitoring (CloudTrail analysis, GuardDuty, or equivalent), periodic access reviews, and an observability stack that surfaces anomalous behavior. The pull request gate catches misconfigurations at the cheapest possible moment — before deploy. Runtime controls catch what slips through or what changes outside your IaC workflow (console clicks, API calls from automation you did not write).

For teams without a dedicated security engineer, this layered approach is how you build a security posture that holds without requiring a full-time hire focused exclusively on IAM. You encode the knowledge into the pipeline so it scales with your team rather than depending on a single person's review bandwidth.

Related service: Cloud infrastructure and DevOps services in Tampa Bay — what we build, how we work, and what it costs.

The Soil Work That Prevents the Postmortem

Shift-left infrastructure security is not a philosophy. It is a pipeline configuration. The difference between catching an overprivileged IAM role in a pull request and catching it in a breach investigation is a few hours of setup and the discipline to maintain five to ten well-written rules.

At FigTree Development, this is the kind of foundational, unglamorous work we architect for teams that are scaling faster than their security posture. Our partner Stephen McDermott has spent years building policy-as-code checks enforced in pull requests — AWS WAF and IAM controls defined in Terraform and Python, running as gates before anything deploys. That experience shapes how we design infrastructure that is secure from the first commit, not as an afterthought bolted on later.

If your team is merging infrastructure changes without automated security checks — or if you have tooling that generates noise but never blocks a bad change — that gap is worth closing before it costs you an incident.

Book a free 20-minute discovery call with us at FigTree Development. We will look at your current pipeline and talk through what a grounded, maintainable policy-as-code setup looks like for your stack and your team size. Your infrastructure should scale with you — and that means the guardrails need to be in place before the growth hits.

Ready to Build?

Let's Plant Something Real.

Every project starts with a free 20-minute discovery call — no pitch, just a real conversation about what you're building and where the friction is.

Book a Discovery Call → ← Back to Blog
Start a Project →