Skip to content
Security · Workflow

OpenTofu modules gated by tofu test and Checkov

Agents write OpenTofu modules that pass a local gate: fmt, a read-only lock file, tofu test with a mocked AWS provider, and Checkov with reasoned skips only.

Verified install Not agent-tested v1.0.0 MIT updated opentofuterraformawsbash
Equips
  • 1 1 Rules
  • 1 1 Skills
  • – 0 MCP

Ask an agent for a bucket and you get a public ACL, no encryption and a Checkov skip with no reason, or a soft-fail flag that turns the scan into decoration. This loadout gives your agent a gate it runs before calling a module change done. It checks formatting, runs init against the committed lock file, validates, and runs tofu test against a mocked AWS provider, so no credentials are needed and nothing is created. Checkov runs last. It refuses soft-fail, repo-wide skip lists and inline skips without a reason. A complete S3 bucket module shows the target, with tests that check its secure defaults and every validation rule. A root example enforces OpenTofu's native state and plan encryption. The verifier runs it on OpenTofu 1.13.1, the AWS provider 6.69.1 and Checkov 3.3.29. It also checks that a reasonless skip, soft-fail, suspended versioning and an agent-written public bucket all fail.

What's in it

  • One gate script: fmt, read-only lock file, validate, tofu test, Checkov
  • tofu test with mock_provider: no AWS credentials, nothing created
  • Checkov exceptions only as inline skips with a reason; soft-fail refused
  • Root example with OpenTofu state and plan encryption enforced
  • Verified on OpenTofu 1.13.1, AWS provider 6.69.1 and Checkov 3.3.29

You'll need

  • Tools: tofu, uv
Level 1 · automated

Verification

Verified install

Checked at commit 08c3710 · 9 passed, 0 failed, 1 not applicable

CheckResultWhat we found
Listinglisting.yaml matches the schema and declares every file in the folder. Pass
Schema valid; 12 files declared, none missing or extra
  • tested.status: untested (agent tests are separate from Level 1)
Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. Pass All 12 files parse; front matter valid
AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. Pass
CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
  • CLAUDE.md: matches the generator
  • GEMINI.md: matches the generator
Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. Pass
Correct paths for all 6 agents; layouts re-discovered
  • Claude Code: 11 files, 1 skill in .claude/skills/
  • Codex CLI: 10 files, 1 skill in .agents/skills/
  • Cursor: 10 files, 1 skill in .agents/skills/
  • Gemini CLI: 11 files, 1 skill in .agents/skills/
  • Antigravity CLI: 10 files, 1 skill in .agents/skills/
  • GitHub Copilot: 10 files, 1 skill in .agents/skills/
MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. Not applicable No MCP servers in this loadout
Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode or prompt-injection wording, and the listing's network flag is honest. Pass
Clean: no credentials, risky shell or hidden text
  • Scanner intake-precheck/1: verdict clean; network yes (listing says yes)
ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. Pass 2 references between files resolve for every agent
Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. Pass
12 files, 16.1 KB total; largest skills/tofu-module-gate/SKILL.md 4.0 KB
  • Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule
LicenceA recognised SPDX licence, with the licence text available. Pass MIT, by Agent Loadout; text in the repository LICENSE
Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. Pass
The tofu-module-gate skill's S3 bucket module passes gate.sh on OpenTofu 1.13.1 with the AWS provider mocked (fmt, read-only lock file, validate, four tofu test runs) and Checkov 3.3.29 with two reasoned skips; the state-encrypted root example validates; a reasonless skip, soft-fail, suspended versi
  • Applied 21 loadout files at every agent's paths
  • tofu 1.13.1
  • ✓ place the skill's module, tests, example and Checkov config: bash -c R=.agents/skills/tofu-module-gate/references; M=modules/s3-bucket; mkdir -p $M/tests && cp $R/main.tf $R/variables.tf $R/outputs.tf $R/versions.tf $M/ && cp $R/bucket.tftest.hcl $M/tests/ && cp $R/example-root.tf examples/basic/main.tf && cp $R/checkov.yaml $M/.checkov.yaml && bash -n .agents/skills/tofu-module-gate/scripts/gate.sh && echo placed
  • ✓ gate.sh passes on the module (mocked provider, no credentials): bash -c export TOFU="$TOFU_BIN" TF_PLUGIN_CACHE_DIR="$AL_TOOL_CACHE/tofu-plugins"; mkdir -p "$TF_PLUGIN_CACHE_DIR"; unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE; bash .agents/skills/tofu-module-gate/scripts/gate.sh modules/s3-bucket
  • ✓ the state-encrypted root example validates: bash -c export TF_PLUGIN_CACHE_DIR="$AL_TOOL_CACHE/tofu-plugins"; cd examples/basic && "$TOFU_BIN" init -backend=false -input=false -lockfile=readonly -no-color >/dev/null && "$TOFU_BIN" validate -no-color
  • ✓ a skip without a reason and soft-fail are refused: bash -c export TOFU="$TOFU_BIN" TF_PLUGIN_CACHE_DIR="$AL_TOOL_CACHE/tofu-plugins"; rm -rf m && cp -r modules/s3-bucket m && rm -rf m/.terraform && sed -i 's/#checkov:skip=CKV_AWS_144:.*/#checkov:skip=CKV_AWS_144/' m/main.tf && echo 'soft-fail: true' >> m/.checkov.yaml && bash .agents/skills/tofu-module-gate/scripts/gate.sh m; test $? -eq 1 && echo refused
  • ✓ suspended versioning fails tofu test: bash -c export TOFU="$TOFU_BIN" TF_PLUGIN_CACHE_DIR="$AL_TOOL_CACHE/tofu-plugins"; rm -rf m && cp -r modules/s3-bucket m && rm -rf m/.terraform && sed -i '/versioning_configuration {/,/}/ s/"Enabled"/"Suspended"/' m/main.tf && grep -q Suspended m/main.tf && bash .agents/skills/tofu-module-gate/scripts/gate.sh m; test $? -eq 1 && echo caught
  • ✓ Checkov fails the agent-written public bucket: bash -c uvx --from checkov==3.3.29 checkov -d bad --framework terraform --compact --quiet; test $? -ne 0 && echo flagged
  • Covers: tofu fmt -check, init with a committed read-only lock file, validate and tofu test with mock_provider (no AWS credentials)
  • Covers: Variable validation tested with expect_failures; secure defaults asserted on the plan
  • Covers: OpenTofu state and plan encryption (pbkdf2 + aes_gcm, enforced) in the root example
  • Covers: Checkov exceptions only as inline skips with a reason; soft-fail and repo-wide skip-check refused

What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results ("tested with agent") are separate and stay untested until an agent actually runs.

Install

Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.

Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.

Download zip
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/AGENTS.md
curl -fsSL --create-dirs -o CLAUDE.md https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/CLAUDE.md
curl -fsSL --create-dirs -o .claude/skills/tofu-module-gate/SKILL.md https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/skills/tofu-module-gate/SKILL.md
curl -fsSL --create-dirs -o .claude/skills/tofu-module-gate/scripts/gate.sh https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/skills/tofu-module-gate/scripts/gate.sh
curl -fsSL --create-dirs -o .claude/skills/tofu-module-gate/references/main.tf https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/skills/tofu-module-gate/references/main.tf
curl -fsSL --create-dirs -o .claude/skills/tofu-module-gate/references/variables.tf https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/skills/tofu-module-gate/references/variables.tf
curl -fsSL --create-dirs -o .claude/skills/tofu-module-gate/references/outputs.tf https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/skills/tofu-module-gate/references/outputs.tf
curl -fsSL --create-dirs -o .claude/skills/tofu-module-gate/references/versions.tf https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/skills/tofu-module-gate/references/versions.tf
curl -fsSL --create-dirs -o .claude/skills/tofu-module-gate/references/bucket.tftest.hcl https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/skills/tofu-module-gate/references/bucket.tftest.hcl
curl -fsSL --create-dirs -o .claude/skills/tofu-module-gate/references/example-root.tf https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/skills/tofu-module-gate/references/example-root.tf
curl -fsSL --create-dirs -o .claude/skills/tofu-module-gate/references/checkov.yaml https://loadout.jnw-it.com/raw/opentofu-module-checkov/1.0.0/skills/tofu-module-gate/references/checkov.yaml
FileGoes to
AGENTS.mdAGENTS.md
CLAUDE.mdCLAUDE.md
skills/tofu-module-gate/SKILL.md.claude/skills/tofu-module-gate/SKILL.md
skills/tofu-module-gate/scripts/gate.sh.claude/skills/tofu-module-gate/scripts/gate.sh
skills/tofu-module-gate/references/main.tf.claude/skills/tofu-module-gate/references/main.tf
skills/tofu-module-gate/references/variables.tf.claude/skills/tofu-module-gate/references/variables.tf
skills/tofu-module-gate/references/outputs.tf.claude/skills/tofu-module-gate/references/outputs.tf
skills/tofu-module-gate/references/versions.tf.claude/skills/tofu-module-gate/references/versions.tf
skills/tofu-module-gate/references/bucket.tftest.hcl.claude/skills/tofu-module-gate/references/bucket.tftest.hcl
skills/tofu-module-gate/references/example-root.tf.claude/skills/tofu-module-gate/references/example-root.tf
skills/tofu-module-gate/references/checkov.yaml.claude/skills/tofu-module-gate/references/checkov.yaml

Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.

Files

Rules AGENTS.md Rules for OpenTofu modules that pass fmt, validate, tofu test and Checkov before a person plans and applies. 28 lines
Download Raw
# AGENTS.md: OpenTofu modules gated by tofu test and Checkov

Infrastructure as code in OpenTofu (HCL works the same in Terraform, with `terraform` for `tofu`). Check
`required_providers` and the lock file before using a provider argument: AWS provider v6 split many bucket
settings into separate resources.

## Commands
- Format: `tofu fmt -recursive`.
- Gate (run before calling a change done): `bash .agents/skills/tofu-module-gate/scripts/gate.sh <module-dir>`.
  It runs fmt check, `tofu init -backend=false -lockfile=readonly`, `tofu validate`, `tofu test` and Checkov, and
  needs no cloud credentials.
- Done means: `gate: ok`, with a `run` block in `tests/` covering the behaviour you changed.

## Rules
- Use the `tofu-module-gate` skill for any change to `.tf` or `.tftest.hcl` files.
- Never run `tofu apply`, `destroy`, `import`, `state` subcommands, `force-unlock` or `-auto-approve`, and never
  read state files. A person plans against real accounts and applies.
- Tests use `mock_provider`. Do not add real credentials, profiles or a backend to make a test pass.
- Every variable has a description and a type; constrained inputs have a `validation` block and a test with
  `expect_failures`.
- Pin providers (`~>` on a minor version) and keep the committed lock file. Upgrading a provider is its own change,
  stated in the summary.
- Fix Checkov findings. An exception is an inline `#checkov:skip=<ID>:<reason>` on the resource, never
  `soft-fail`, `--skip-check` or a repo-wide skip list.
- Root modules enforce OpenTofu state and plan encryption, with the key from the environment or a KMS key
  provider, never from a file.
- Stateful resources (buckets, databases, keys) carry `prevent_destroy = true`. Renames use `moved` blocks.
Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 11 lines
Download Raw
# CLAUDE.md

@AGENTS.md

## Claude Code notes

- Skills from this loadout live in `.claude/skills/`: `tofu-module-gate`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "OpenTofu modules gated by tofu test and Checkov" v1.0.0 — https://loadout.jnw-it.com/loadouts/opentofu-module-checkov/ -->
Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 11 lines
Download Raw
# GEMINI.md

@./AGENTS.md

## Gemini CLI notes

- Skills from this loadout live in `.agents/skills/`: `tofu-module-gate`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "OpenTofu modules gated by tofu test and Checkov" v1.0.0 — https://loadout.jnw-it.com/loadouts/opentofu-module-checkov/ -->
Skill skills/tofu-module-gate/SKILL.md Workflow, Checkov exception rules, lock file and state encryption. 80 lines
Download Raw
---
name: tofu-module-gate
description: Write or change an OpenTofu (or Terraform) module so it passes a local gate - fmt, validate, tofu test with a mocked provider, and Checkov with reasoned exceptions only. Use when adding or editing .tf files, module variables, tests, or Checkov skips.
---

# OpenTofu module gate

A module change is done when `scripts/gate.sh` prints `gate: ok`. The gate needs no cloud
credentials and creates nothing: tests run against `mock_provider`, Checkov reads the HCL.

## Workflow

1. Read the module's variables, outputs and tests before editing. Keep one resource type per
   concern (the bucket, its versioning, its encryption ...), as the AWS provider v6 expects.
2. Make the change. Every new input gets a `description`, a `type`, and a `validation` block
   when it has a valid range or format.
3. Add or update a `run` block in `tests/*.tftest.hcl` for the behaviour you changed:
   - `command = plan` with `assert` blocks for secure defaults and computed values;
   - `expect_failures = [var.x]` for every validation rule you add.
4. Run the gate on the module folder:

   ```bash
   TOFU=tofu bash .agents/skills/tofu-module-gate/scripts/gate.sh modules/s3-bucket
   ```

   It runs, in order, and stops at the first failure:
   `tofu fmt -check -recursive`, `tofu init -backend=false -lockfile=readonly`, `tofu validate`,
   `tofu test`, an audit of Checkov exceptions, then Checkov 3.3.29 (through `uvx`).
5. Fix what failed. Report the gate output, not a summary of it.

## Checkov findings

- Fix the finding first. Most S3, IAM and logging findings have a one-resource fix.
- If the finding is genuinely the caller's decision (replication, event notifications), skip it
  inline on the resource, with a reason a reviewer can disagree with:

  ```hcl
  resource "aws_s3_bucket" "this" {
    #checkov:skip=CKV_AWS_144:Cross-region replication is configured by callers that need it
  ```

- Never add `soft-fail: true` or a `skip-check:` list to the repository's Checkov config, and
  never pass `--soft-fail` or `--skip-check` on the command line. The gate refuses both config
  settings, and refuses a skip with no reason or a reason under 10 characters.
- Start the repository config from `references/checkov.yaml`.

## Lock file and providers

- Pin providers with `~>` on a minor version in `required_providers`, and commit the lock file.
  The gate runs init with `-lockfile=readonly`, so a provider change must be a deliberate
  `tofu init -upgrade` that a human reviews in the diff.
- For lock hashes on every platform the team uses:
  `tofu providers lock -platform=linux_amd64 -platform=darwin_arm64`.
- Never commit the `.terraform` folder, state files or plan files.

## Root modules: encrypt state

OpenTofu encrypts state and plan files natively. A root module that uses this module should
carry an `encryption` block with `enforced = true` for both state and plan, and take the key
from the environment (a sensitive variable set by `TF_VAR_...`, or a KMS key provider). See
`references/example-root.tf`. Never write the passphrase into a file.

## What the agent does not do

- No `tofu apply`, `destroy`, `import`, `state` subcommands or `-auto-approve`. A person applies.
  Pair this loadout with terraform-plan-only for a hook that enforces it.
- No `tofu plan` against a real account to "check it works": that is what the mocked tests are for.
- No new provider or module source without saying so in the summary.

## Files

- `scripts/gate.sh`: the gate. Env: `TOFU` (default `tofu`), `CHECKOV` (default
  `uvx --from checkov==3.3.29 checkov`).
- `references/main.tf`, `references/variables.tf`, `references/outputs.tf`,
  `references/versions.tf`: a private, versioned, SSE-KMS S3 bucket module with access logging,
  TLS-only policy and noncurrent-version expiry. Checkov: 0 failed, 2 reasoned skips.
- `references/bucket.tftest.hcl`: four `run` blocks with a mocked AWS provider.
- `references/example-root.tf`: root module with enforced state and plan encryption.
- `references/checkov.yaml`: repository Checkov config.
Skill file skills/tofu-module-gate/scripts/gate.sh fmt, init (read-only lock), validate, tofu test, exception audit, Checkov. 56 lines
Download Raw
#!/usr/bin/env bash
# Module gate: fmt, init (lock file read-only), validate, tofu test, Checkov, and an audit of
# Checkov exceptions. Runs no plan against real cloud accounts and never applies.
# Usage: gate.sh [module-dir]   (default: current directory)
# Env: TOFU (default tofu), CHECKOV (default: uvx --from checkov==3.3.29 checkov)
set -uo pipefail

dir="${1:-.}"
TOFU="${TOFU:-tofu}"
CHECKOV="${CHECKOV:-uvx --from checkov==3.3.29 checkov}"
cd "$dir" || { echo "gate: no such directory: $dir"; exit 2; }

fail() { echo "gate: FAILED at $1"; exit 1; }
step() { echo "== $1"; }

step "fmt"
"$TOFU" fmt -check -recursive -diff -no-color || fail "fmt (run: tofu fmt -recursive)"

step "init"
[ -f .terraform.lock.hcl ] || fail "init (no .terraform.lock.hcl: run tofu init and commit the lock file)"
"$TOFU" init -backend=false -input=false -lockfile=readonly -no-color >/dev/null || fail "init"

step "validate"
"$TOFU" validate -no-color || fail "validate"

step "test"
if ls tests/*.tftest.hcl >/dev/null 2>&1; then
  "$TOFU" test -no-color || fail "test"
else
  fail "test (no tests/*.tftest.hcl)"
fi

step "exception audit"
bad=0
if [ -f .checkov.yaml ] && grep -Eq '^[[:space:]]*(soft-fail|soft_fail)[[:space:]]*:[[:space:]]*true' .checkov.yaml; then
  echo "soft-fail is set in .checkov.yaml: findings would not fail the run"; bad=1
fi
if [ -f .checkov.yaml ] && grep -Eq '^[[:space:]]*skip-check[[:space:]]*:' .checkov.yaml; then
  echo "skip-check in .checkov.yaml hides checks repo-wide: use an inline skip with a reason"; bad=1
fi
while IFS= read -r hit; do
  reason="${hit#*checkov:skip=}"; reason="${reason#*:}"
  if [ "$reason" = "${hit#*checkov:skip=}" ] || [ "${#reason}" -lt 10 ]; then
    echo "skip without a reason: $hit"; bad=1
  fi
done < <(grep -rn --include='*.tf' 'checkov:skip=' . | grep -v '/\.terraform/')
[ "$bad" -eq 0 ] || fail "exception audit"

step "checkov"
cfg=()
[ -f .checkov.yaml ] && cfg=(--config-file .checkov.yaml)
# shellcheck disable=SC2086
$CHECKOV -d . "${cfg[@]}" --framework terraform --compact --quiet || fail "checkov"

echo "gate: ok"
Skill file skills/tofu-module-gate/references/main.tf Private, versioned, SSE-KMS S3 bucket with logging and a TLS-only policy. 107 lines
Download Raw
# Private, versioned, KMS-encrypted bucket with access logging and an expiry rule for old versions.
# Checkov CKV_AWS_144 (cross-region replication) and CKV2_AWS_62 (event notifications) are skipped
# with a reason: both are per-workload decisions made by the caller, not by this module.
resource "aws_s3_bucket" "this" {
  #checkov:skip=CKV_AWS_144:Cross-region replication is configured by callers that need it
  #checkov:skip=CKV2_AWS_62:Event notifications are wired up by the consuming service
  bucket = var.name
  tags   = var.tags

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_s3_bucket_ownership_controls" "this" {
  bucket = aws_s3_bucket.this.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}

resource "aws_s3_bucket_public_access_block" "this" {
  bucket                  = aws_s3_bucket.this.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_versioning" "this" {
  bucket = aws_s3_bucket.this.id

  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
  bucket = aws_s3_bucket.this.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = var.kms_key_arn
    }
    bucket_key_enabled = true
  }
}

resource "aws_s3_bucket_logging" "this" {
  bucket        = aws_s3_bucket.this.id
  target_bucket = var.log_bucket
  target_prefix = "s3/${var.name}/"
}

resource "aws_s3_bucket_lifecycle_configuration" "this" {
  bucket = aws_s3_bucket.this.id

  rule {
    id     = "expire-noncurrent"
    status = "Enabled"

    filter {}

    noncurrent_version_expiration {
      noncurrent_days = var.noncurrent_days
    }

    abort_incomplete_multipart_upload {
      days_after_initiation = 7
    }
  }

  depends_on = [aws_s3_bucket_versioning.this]
}

data "aws_iam_policy_document" "tls_only" {
  statement {
    sid     = "DenyInsecureTransport"
    effect  = "Deny"
    actions = ["s3:*"]
    resources = [
      aws_s3_bucket.this.arn,
      "${aws_s3_bucket.this.arn}/*",
    ]

    principals {
      type        = "*"
      identifiers = ["*"]
    }

    condition {
      test     = "Bool"
      variable = "aws:SecureTransport"
      values   = ["false"]
    }
  }
}

resource "aws_s3_bucket_policy" "tls_only" {
  bucket = aws_s3_bucket.this.id
  policy = data.aws_iam_policy_document.tls_only.json

  depends_on = [aws_s3_bucket_public_access_block.this]
}
Skill file skills/tofu-module-gate/references/variables.tf Typed, described inputs with validation blocks. 42 lines
Download Raw
variable "name" {
  description = "Bucket name. Globally unique, lower case, 3 to 63 characters."
  type        = string

  validation {
    condition     = can(regex("^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$", var.name))
    error_message = "name must be 3-63 lower-case letters, digits, dots or hyphens, starting and ending with a letter or digit."
  }
}

variable "kms_key_arn" {
  description = "ARN of the customer-managed KMS key used for default encryption."
  type        = string

  validation {
    condition     = startswith(var.kms_key_arn, "arn:aws:kms:")
    error_message = "kms_key_arn must be a KMS key ARN."
  }
}

variable "log_bucket" {
  description = "Name of the bucket that receives server access logs."
  type        = string
}

variable "noncurrent_days" {
  description = "Days to keep noncurrent object versions before they expire."
  type        = number
  default     = 90

  validation {
    condition     = var.noncurrent_days >= 7 && var.noncurrent_days <= 3650
    error_message = "noncurrent_days must be between 7 and 3650."
  }
}

variable "tags" {
  description = "Tags added to every resource."
  type        = map(string)
  default     = {}
}
Skill file skills/tofu-module-gate/references/outputs.tf Module outputs. 10 lines
Download Raw
output "id" {
  description = "Bucket name."
  value       = aws_s3_bucket.this.id
}

output "arn" {
  description = "Bucket ARN."
  value       = aws_s3_bucket.this.arn
}
Skill file skills/tofu-module-gate/references/versions.tf OpenTofu and AWS provider version constraints. 11 lines
Download Raw
terraform {
  required_version = ">= 1.10.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.69"
    }
  }
}
Skill file skills/tofu-module-gate/references/bucket.tftest.hcl Four tofu test runs against a mocked AWS provider. 79 lines
Download Raw
# Runs with `tofu test`: a mocked AWS provider, so no credentials, no API calls, nothing created.
mock_provider "aws" {
  mock_data "aws_iam_policy_document" {
    defaults = {
      json = "{\"Version\":\"2012-10-17\",\"Statement\":[]}"
    }
  }
}

variables {
  name        = "acme-reports-eu"
  kms_key_arn = "arn:aws:kms:eu-west-2:111122223333:key/0d7c2f4e-1111-2222-3333-444455556666"
  log_bucket  = "acme-access-logs"
}

run "secure_defaults" {
  command = plan

  assert {
    condition     = aws_s3_bucket_versioning.this.versioning_configuration[0].status == "Enabled"
    error_message = "Versioning must be enabled."
  }

  assert {
    condition = alltrue([
      aws_s3_bucket_public_access_block.this.block_public_acls,
      aws_s3_bucket_public_access_block.this.block_public_policy,
      aws_s3_bucket_public_access_block.this.ignore_public_acls,
      aws_s3_bucket_public_access_block.this.restrict_public_buckets,
    ])
    error_message = "All four public access block settings must be true."
  }

  assert {
    condition     = one([for r in aws_s3_bucket_server_side_encryption_configuration.this.rule : one(r.apply_server_side_encryption_by_default).sse_algorithm]) == "aws:kms"
    error_message = "Default encryption must be SSE-KMS."
  }

  assert {
    condition     = aws_s3_bucket_ownership_controls.this.rule[0].object_ownership == "BucketOwnerEnforced"
    error_message = "ACLs must be disabled (BucketOwnerEnforced)."
  }

  assert {
    condition     = aws_s3_bucket_logging.this.target_prefix == "s3/acme-reports-eu/"
    error_message = "Access logs must go under s3/<name>/."
  }
}

run "rejects_bad_name" {
  command = plan

  variables {
    name = "Acme_Reports"
  }

  expect_failures = [var.name]
}

run "rejects_non_kms_key" {
  command = plan

  variables {
    kms_key_arn = "alias/aws/s3"
  }

  expect_failures = [var.kms_key_arn]
}

run "rejects_short_retention" {
  command = plan

  variables {
    noncurrent_days = 1
  }

  expect_failures = [var.noncurrent_days]
}
Skill file skills/tofu-module-gate/references/example-root.tf Root module with enforced state and plan encryption. 57 lines
Download Raw
# Root module example: calls the bucket module and encrypts state and plan files with
# OpenTofu's built-in state encryption. The passphrase comes from a variable that is
# set in the environment (TF_VAR_state_passphrase), never written to a file.
terraform {
  required_version = ">= 1.10.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.69"
    }
  }

  encryption {
    key_provider "pbkdf2" "state" {
      passphrase = var.state_passphrase
    }

    method "aes_gcm" "state" {
      keys = key_provider.pbkdf2.state
    }

    state {
      method   = method.aes_gcm.state
      enforced = true
    }

    plan {
      method   = method.aes_gcm.state
      enforced = true
    }
  }
}

variable "state_passphrase" {
  description = "State encryption passphrase, at least 16 characters. Set TF_VAR_state_passphrase."
  type        = string
  sensitive   = true
}

provider "aws" {
  region = "eu-west-2"
}

module "reports" {
  source = "../../modules/s3-bucket"

  name        = "acme-reports-eu"
  kms_key_arn = "arn:aws:kms:eu-west-2:111122223333:key/0d7c2f4e-1111-2222-3333-444455556666"
  log_bucket  = "acme-access-logs"
  tags        = { service = "reports" }
}

output "bucket_arn" {
  value = module.reports.arn
}
Skill file skills/tofu-module-gate/references/checkov.yaml Repository Checkov config with no soft-fail or skip list. 11 lines
Download Raw
# Repository .checkov.yaml. Findings fail the run: no soft-fail, no skip-check list.
# Accepted exceptions are inline `#checkov:skip=<ID>:<reason>` comments next to the resource,
# so the reason is reviewed in the same diff as the code.
framework:
  - terraform
compact: true
quiet: true
download-external-modules: false
skip-path:
  - .terraform

All files: source zip · Licence MIT · By Agent Loadout · First listed

Installing and testing

How do I install OpenTofu modules gated by tofu test and Checkov in Claude Code?

From your project root, put AGENTS.md, CLAUDE.md, skills/tofu-module-gate/SKILL.md at .claude/skills/tofu-module-gate/SKILL.md, skills/tofu-module-gate/scripts/gate.sh at .claude/skills/tofu-module-gate/scripts/gate.sh, skills/tofu-module-gate/references/main.tf at .claude/skills/tofu-module-gate/references/main.tf, skills/tofu-module-gate/references/variables.tf at .claude/skills/tofu-module-gate/references/variables.tf, skills/tofu-module-gate/references/outputs.tf at .claude/skills/tofu-module-gate/references/outputs.tf, skills/tofu-module-gate/references/versions.tf at .claude/skills/tofu-module-gate/references/versions.tf, skills/tofu-module-gate/references/bucket.tftest.hcl at .claude/skills/tofu-module-gate/references/bucket.tftest.hcl, skills/tofu-module-gate/references/example-root.tf at .claude/skills/tofu-module-gate/references/example-root.tf, skills/tofu-module-gate/references/checkov.yaml at .claude/skills/tofu-module-gate/references/checkov.yaml. The "Claude Code" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/opentofu-module-checkov/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install OpenTofu modules gated by tofu test and Checkov in Codex CLI?

From your project root, put AGENTS.md, skills/tofu-module-gate/SKILL.md at .agents/skills/tofu-module-gate/SKILL.md, skills/tofu-module-gate/scripts/gate.sh at .agents/skills/tofu-module-gate/scripts/gate.sh, skills/tofu-module-gate/references/main.tf at .agents/skills/tofu-module-gate/references/main.tf, skills/tofu-module-gate/references/variables.tf at .agents/skills/tofu-module-gate/references/variables.tf, skills/tofu-module-gate/references/outputs.tf at .agents/skills/tofu-module-gate/references/outputs.tf, skills/tofu-module-gate/references/versions.tf at .agents/skills/tofu-module-gate/references/versions.tf, skills/tofu-module-gate/references/bucket.tftest.hcl at .agents/skills/tofu-module-gate/references/bucket.tftest.hcl, skills/tofu-module-gate/references/example-root.tf at .agents/skills/tofu-module-gate/references/example-root.tf, skills/tofu-module-gate/references/checkov.yaml at .agents/skills/tofu-module-gate/references/checkov.yaml. The "Codex CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/opentofu-module-checkov/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install OpenTofu modules gated by tofu test and Checkov in Cursor?

From your project root, put AGENTS.md, skills/tofu-module-gate/SKILL.md at .agents/skills/tofu-module-gate/SKILL.md, skills/tofu-module-gate/scripts/gate.sh at .agents/skills/tofu-module-gate/scripts/gate.sh, skills/tofu-module-gate/references/main.tf at .agents/skills/tofu-module-gate/references/main.tf, skills/tofu-module-gate/references/variables.tf at .agents/skills/tofu-module-gate/references/variables.tf, skills/tofu-module-gate/references/outputs.tf at .agents/skills/tofu-module-gate/references/outputs.tf, skills/tofu-module-gate/references/versions.tf at .agents/skills/tofu-module-gate/references/versions.tf, skills/tofu-module-gate/references/bucket.tftest.hcl at .agents/skills/tofu-module-gate/references/bucket.tftest.hcl, skills/tofu-module-gate/references/example-root.tf at .agents/skills/tofu-module-gate/references/example-root.tf, skills/tofu-module-gate/references/checkov.yaml at .agents/skills/tofu-module-gate/references/checkov.yaml. The "Cursor" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/opentofu-module-checkov/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install OpenTofu modules gated by tofu test and Checkov in Gemini CLI?

From your project root, put AGENTS.md, GEMINI.md, skills/tofu-module-gate/SKILL.md at .agents/skills/tofu-module-gate/SKILL.md, skills/tofu-module-gate/scripts/gate.sh at .agents/skills/tofu-module-gate/scripts/gate.sh, skills/tofu-module-gate/references/main.tf at .agents/skills/tofu-module-gate/references/main.tf, skills/tofu-module-gate/references/variables.tf at .agents/skills/tofu-module-gate/references/variables.tf, skills/tofu-module-gate/references/outputs.tf at .agents/skills/tofu-module-gate/references/outputs.tf, skills/tofu-module-gate/references/versions.tf at .agents/skills/tofu-module-gate/references/versions.tf, skills/tofu-module-gate/references/bucket.tftest.hcl at .agents/skills/tofu-module-gate/references/bucket.tftest.hcl, skills/tofu-module-gate/references/example-root.tf at .agents/skills/tofu-module-gate/references/example-root.tf, skills/tofu-module-gate/references/checkov.yaml at .agents/skills/tofu-module-gate/references/checkov.yaml. The "Gemini CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/opentofu-module-checkov/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install OpenTofu modules gated by tofu test and Checkov in Antigravity CLI?

From your project root, put AGENTS.md, skills/tofu-module-gate/SKILL.md at .agents/skills/tofu-module-gate/SKILL.md, skills/tofu-module-gate/scripts/gate.sh at .agents/skills/tofu-module-gate/scripts/gate.sh, skills/tofu-module-gate/references/main.tf at .agents/skills/tofu-module-gate/references/main.tf, skills/tofu-module-gate/references/variables.tf at .agents/skills/tofu-module-gate/references/variables.tf, skills/tofu-module-gate/references/outputs.tf at .agents/skills/tofu-module-gate/references/outputs.tf, skills/tofu-module-gate/references/versions.tf at .agents/skills/tofu-module-gate/references/versions.tf, skills/tofu-module-gate/references/bucket.tftest.hcl at .agents/skills/tofu-module-gate/references/bucket.tftest.hcl, skills/tofu-module-gate/references/example-root.tf at .agents/skills/tofu-module-gate/references/example-root.tf, skills/tofu-module-gate/references/checkov.yaml at .agents/skills/tofu-module-gate/references/checkov.yaml. The "Antigravity CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/opentofu-module-checkov/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install OpenTofu modules gated by tofu test and Checkov in GitHub Copilot?

From your project root, put AGENTS.md, skills/tofu-module-gate/SKILL.md at .agents/skills/tofu-module-gate/SKILL.md, skills/tofu-module-gate/scripts/gate.sh at .agents/skills/tofu-module-gate/scripts/gate.sh, skills/tofu-module-gate/references/main.tf at .agents/skills/tofu-module-gate/references/main.tf, skills/tofu-module-gate/references/variables.tf at .agents/skills/tofu-module-gate/references/variables.tf, skills/tofu-module-gate/references/outputs.tf at .agents/skills/tofu-module-gate/references/outputs.tf, skills/tofu-module-gate/references/versions.tf at .agents/skills/tofu-module-gate/references/versions.tf, skills/tofu-module-gate/references/bucket.tftest.hcl at .agents/skills/tofu-module-gate/references/bucket.tftest.hcl, skills/tofu-module-gate/references/example-root.tf at .agents/skills/tofu-module-gate/references/example-root.tf, skills/tofu-module-gate/references/checkov.yaml at .agents/skills/tofu-module-gate/references/checkov.yaml. The "GitHub Copilot" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/opentofu-module-checkov/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

Has OpenTofu modules gated by tofu test and Checkov been tested?

It passed the Level 1 automated install checks on 2026-10-17: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.

More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.