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.
GitHub App
Zero config. One comment per PR, SARIF to Code Scanning. No workflow file.
Actions + MCP
A short workflow fails the build on High/Critical findings. Free Client ID.
Plan enrichment
Upload the resolved terraform plan over OIDC for variable-accurate findings.
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.
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:
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")
# 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
fiThe 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.
Resolved-value precision with a plan upload
Static parsing can't resolve values behind variables, count/for_each expansion, or resources inside modules. 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 same gate for CloudFormation
Building CloudFormation instead of Terraform? The MCP gate works unchanged — audytx normalizes stack templates into the same resource model and returns the same response shape. Point find at your templates and call scan_cloudformation:
req=$(find . \( -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) -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:.}}}')supported_iac).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 — one 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?
Not yet — the blocking gate today is the MCP call shown above, which any repository can run with a free Client ID. A packaged CLI and a Marketplace Action are on the roadmap; we shipped the MCP API first because it's the same engine the App and coding agents already 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 — the same privacy posture as the GitHub App path. Only aggregate scan metadata (counts, engine version) is recorded, never file contents or secrets.
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.
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 — free during the beta.