Free plan Signups are open now. The full audytx engine — Terraform and CloudFormation — is live. The Free plan is 100 scans a month and stays free — paid tiers arrive Nov 01, 2026 from $20/month for unlimited repositories. Try it on your own code.
CI/CD · GitHub Actions

Terraform security scanning in GitHub Actions

There are three ways to put audytx's cross-resource AWS Terraform review in front of a pull request, in rising order of effort. Most teams need only the first. The second gives you a hard build gate inside GitHub Actions; the third adds resolved-value precision. All three run the same engine and suppress the same false positives.

Level 1 · recommended

GitHub App

Zero config. A summary comment on the PR, SARIF to Code Scanning. No workflow file.

Level 2 · a hard gate

Actions + MCP

A short workflow fails the build on High/Critical findings. Free Client ID.

Level 3 · precision

Plan enrichment

Upload the resolved terraform plan over OIDC for variable-accurate findings.

Level 1

The zero-config path: the GitHub App

Before you write a workflow, know that you may not need one. The audytx GitHub App reviews every pull request on install — no Actions file, no token to store. It reads your .tf/.tfvars files, runs the whole engine, posts one comment with the findings and their suppression rationales, adds inline annotations, and uploads a SARIF document to GitHub Code Scanning so findings show in the Security tab.

The App is the whole product for most teams: it installs in about a minute and needs no CI wiring. Reach for a workflow when you specifically want to fail the build on a finding — the App comments and reports, it does not block your Actions run.
Level 2

A hard gate in GitHub Actions

To block a merge on a High or Critical finding, add a workflow that calls the audytx MCP API and fails the job on the result. This needs a free Client ID — store it as the repository secret AUDYTX_CLIENT_ID. The workflow bundles your Terraform, POSTs one JSON-RPC call, and exits non-zero when the severity summary crosses your threshold:

.github/workflows/audytx.yml
name: audytx Terraform security gate
on: [pull_request]
permissions:
  contents: read
jobs:
  audytx:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Scan Terraform with audytx
        env:
          AUDYTX_CLIENT_ID: ${{ secrets.AUDYTX_CLIENT_ID }}
        run: |
          # Bundle every .tf/.tfvars file into one JSON-RPC request.
          # Pass the whole tree — cross-resource reasoning needs it.
          req=$(find . \( -name '*.tf' -o -name '*.tfvars' \) -print0 \
            | xargs -0 -I{} jq -n --arg p "{}" --rawfile c "{}" \
                '{path:$p, content:$c}' \
            | jq -s '{jsonrpc:"2.0", id:1, method:"tools/call",
                      params:{name:"scan_terraform", arguments:{files:.}}}')

          resp=$(curl -sf https://audytx.com/mcp \
            -H "Content-Type: application/json" \
            -H "X-Client-ID: $AUDYTX_CLIENT_ID" --data "$req")

          # A refused call comes back as a tool error, not as findings.
          if [ "$(echo "$resp" | jq -r '.result.isError // false')" = "true" ]; then
            echo "::error::audytx refused the scan: $(echo "$resp" | jq -r '.result.content[0].text')"
            exit 1
          fi

          # List the High/Critical findings for the log …
          echo "$resp" | jq -r '.result.structuredContent.findings[]
            | select(.severity=="CRITICAL" or .severity=="HIGH")
            | "\(.severity)  \(.rule_id)  \(.file):\(.line_start)  \(.title)"'

          # … then fail the build if any exist.
          gate=$(echo "$resp" \
            | jq '.result.structuredContent.summary | .critical + .high')
          if [ "$gate" -gt 0 ]; then
            echo "::error::audytx found $gate High/Critical AWS security findings"
            exit 1
          fi

The response carries a summary block (critical/high/medium/low counts) and a findings array with rule_id, severity, file, line_start, and remediation — the same contract the docs describe. Move the gate to .critical alone for a looser policy, or add .medium for a stricter one.

The accepted scan uses one of your 100 free monthly scan credits. Non-scan helpers are free, and scans audytx fails to complete are refunded asynchronously. A scan that skips a file it cannot read and analyses the rest is a completed scan. The scan still holds back findings that context shows to be harmless, and says why, so a red build is a finding worth a look. Prefer the App for the SARIF/Code-Scanning surface and use this workflow purely as the blocking gate; they work well together.
Level 3

Resolved-value precision with a plan upload

Static parsing can't resolve every value: variables, count/for_each expansion and module internals are only partly known without a plan. If you want findings computed against the resolved plan, add the plan-upload workflow. It authenticates with the run's GitHub OIDC token — no secret to store — runs terraform show -json, and POSTs the plan so audytx re-scans with final values folded in. It's strictly additive: the normal scan keeps working with or without it.

The full workflow and its permissions: id-token: write block are documented on the plan-scanning page. Pair it with either level above.

The plan upload enriches the GitHub App's PR comment (it's fire-and-forget — if the upload fails, your normal scan still stands). It does not, by itself, return a pass/fail to your Actions run; use Level 2 for the blocking decision.
CloudFormation

The same gate for CloudFormation

Building CloudFormation instead of Terraform? The MCP gate works the same way and returns the same response shape. Send templates only: point find at the directory that holds your templates and call scan_cloudformation. If the call includes a file that is not a CloudFormation template (a workflow file or a package.json, for example), the whole call is refused and no scan credit is used. Replace infra/cloudformation with your template directory:

          req=$(find infra/cloudformation -type f \( -name '*.yaml' -o -name '*.yml' -o -name '*.json' -o -name '*.template' \) -print0 \
            | xargs -0 -I{} jq -n --arg p "{}" --rawfile c "{}" \
                '{path:$p, content:$c}' \
            | jq -s '{jsonrpc:"2.0", id:1, method:"tools/call",
                      params:{name:"scan_cloudformation", arguments:{files:.}}}')
CloudFormation is live on the MCP server and the GitHub App: the zero-config Level 1 path now covers stack templates changed in a PR (same one-comment review and SARIF upload as Terraform). The MCP gate above is still what hard-fails a CI build on findings. Track surface rollout at /status (supported_iac).
FAQ

Common questions

Do I need a GitHub Actions workflow at all?

No. The GitHub App reviews every pull request on install with no workflow file — a summary comment plus SARIF to Code Scanning. You only need a workflow if you want to fail the build on a finding, which the App doesn't do. Many teams run the App alone.

Is there a prebuilt CLI or Marketplace Action I can drop in?

No. The blocking gate is the MCP call shown above, which any repository can run with a free Client ID. It is the same engine the App and coding agents use, so every surface reports the same findings.

Does the workflow send my Terraform anywhere it's stored?

The MCP call sends file contents to audytx, which parses them in memory and discards them when the response returns. audytx does keep a record of each finding (rule, message, resource, file and lines) for 90 days, then deletes it. File contents are never part of that record.

How do the findings reach the GitHub Security tab?

Through the GitHub App, which uploads a SARIF v2.1.0 document to Code Scanning automatically — including the context-suppressed findings as SARIF suppressions with their rationale. The Level 2 workflow is a build gate, not a SARIF uploader; run both to get the gate and the Security-tab surface.

Free plan

Put the gate in front of your next PR

Install the GitHub App for zero-config review, or drop in the workflow to fail the build on High/Critical findings. The Free plan includes 100 scans a month.