ThothCTL integrates policy-as-code at multiple layers of the IaC lifecycle. Policies define what is allowed, enforced, or blocked β from project structure to security posture to drift response to AI decision-making.
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3b82f6',
'primaryTextColor':'#ffffff',
'primaryBorderColor':'#2563eb',
'lineColor':'#94a3b8',
'secondaryColor':'#10b981',
'tertiaryColor':'#8b5cf6',
'background':'transparent',
'mainBkg':'#3b82f6',
'secondBkg':'#10b981',
'tertiaryBkg':'#8b5cf6',
'clusterBkg':'rgba(241, 245, 249, 0.05)',
'clusterBorder':'#475569',
'titleColor':'currentColor',
'edgeLabelBackground':'transparent',
'nodeTextColor':'#ffffff',
'textColor':'currentColor',
'nodeBorder':'#1e293b',
'fontSize':'14px'
}}}%%
graph TB
subgraph Structure["π Structure Policy"]
S1[".thothcf.toml<br/>[project_structure]"]
S2["check project iac"]
end
subgraph Security["π Security Policy"]
P1["policy/*.rego<br/>(OPA / Conftest)"]
P2["scan iac --tools opa"]
end
subgraph Cost["π° Cost Policy"]
C1["cost/policy/*.rego<br/>+ config.yaml"]
C2["check iac -type cost-analysis<br/>--enforce-policy"]
end
subgraph Operations["βοΈ Operations Policy"]
O1[".driftpolicy<br/>ai_decision_config.yaml"]
O2["check iac -type drift<br/>ai-review decide"]
end
S1 --> S2
P1 --> P2
C1 --> C2
O1 --> O2
S2 --> Gate{"Policy Gate"}
P2 --> Gate
C2 --> Gate
O2 --> Gate
Gate -->|All pass| Deploy["β
Deploy"]
Gate -->|Violation| Block["β Block"]
classDef structStyle fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
classDef secStyle fill:#8b5cf6,stroke:#a78bfa,stroke-width:2px,color:#fff
classDef costStyle fill:#f59e0b,stroke:#fbbf24,stroke-width:2px,color:#fff
classDef opsStyle fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff
classDef gateStyle fill:#f59e0b,stroke:#fbbf24,stroke-width:2px,color:#fff
classDef resultStyle fill:#15803d,stroke:#4ade80,stroke-width:2px,color:#ffffff
classDef blockStyle fill:#dc2626,stroke:#f87171,stroke-width:2px,color:#ffffff
class S1,S2 structStyle
class P1,P2 secStyle
class C1,C2 costStyle
class O1,O2 opsStyle
class Gate gateStyle
class Deploy resultStyle
class Block blockStyle
File: .thothcf.toml β [project_structure]
Evaluated by: thothctl check project iac
Purpose: Enforce that projects follow organizational folder/file conventions.
[project_structure]
root_files = [".gitignore", "README.md", ".thothcf.toml"]
ignore_folders = [".git", ".terraform", ".terragrunt-cache"]
[[project_structure.folders]]
name = "modules"
mandatory = true
type = "root"
content = ["main.tf", "variables.tf", "outputs.tf"]
[[project_structure.folders]]
name = "environments"
mandatory = true
type = "root"
Enforcement: Hard failure if mandatory folders/files are missing.
thothctl check project iac
# β
modules/ exists with required files
# β FAIL: environments/ is missing (mandatory)
File: policy/*.rego
Evaluated by: thothctl scan iac --tools opa
Purpose: Evaluate IaC code against custom security, naming, and compliance rules using the OPA policy language.
Modes:
conftest (default): Static analysis of .tf / .yaml filesopa: Plan-based evaluation against tfplan.json# policy/s3.rego
package main
deny[msg] {
resource := input.resource.aws_s3_bucket[name]
not resource.server_side_encryption_configuration
msg := sprintf("S3 bucket '%s' must have encryption enabled", [name])
}
deny[msg] {
resource := input.resource.aws_s3_bucket[name]
resource.acl == "public-read"
msg := sprintf("S3 bucket '%s' must not be public", [name])
}
Enforcement:
# Static HCL analysis (conftest mode)
thothctl scan iac --tools opa
# Plan-based evaluation
thothctl scan iac --tools opa --opa-mode opa --policy-dir ./policy
Options:
| Option | Description |
|---|---|
--opa-mode conftest |
Static file analysis (default) |
--opa-mode opa |
Plan-based evaluation via opa exec |
--policy-dir PATH |
Directory containing .rego files (default: policy/) |
--opa-namespace |
Rego namespace (conftest mode) |
--opa-data-dir |
Additional data directory for policies |
File: .driftpolicy (YAML)
Evaluated by: thothctl check iac -type drift
Purpose: Define per-resource tolerance for infrastructure drift β block, alert, accept, or ignore.
# .driftpolicy
coverage_threshold: 90.0
rules:
- resource: "aws_security_group.*"
severity_override: critical
action: block_deploy
- resource: "aws_instance.*"
attribute: "tags.*"
action: auto_accept
- resource: "aws_db_instance.*"
action: alert
- resource: "aws_cloudwatch_log_group.*"
action: ignore
Actions:
| Action | Behavior |
|---|---|
block_deploy |
Fail CI, prevent deployment until drift is resolved |
alert |
Warn but allow deployment |
auto_accept |
Silently accept the drift (e.g., tag-only changes) |
ignore |
Remove from report entirely |
Enforcement:
thothctl check iac -type drift --recursive
# Drift in aws_security_group.api β ACTION: block_deploy β β CI fails
# Drift in aws_instance.web tags β ACTION: auto_accept β β
Ignored
File: .thothctl/ai_decision_config.yaml
Evaluated by: thothctl ai-review decide
Purpose: Define thresholds for automated PR approve/reject/request-changes decisions.
# .thothctl/ai_decision_config.yaml
approve_thresholds:
risk_score_max: 20
confidence_min: 0.90
critical_issues_max: 0
high_issues_max: 0
compliance_violations_max: 0
reject_thresholds:
risk_score_min: 85
confidence_min: 0.85
critical_issues_min: 1
safety:
max_auto_approvals_per_day: 50
max_auto_rejections_per_day: 20
cooldown_between_actions: 300 # seconds
emergency_labels: ["emergency", "hotfix", "security-patch"]
trusted_bots: ["dependabot", "renovate"]
blocking_patterns:
- hardcoded_secrets
- public_s3_buckets
- unencrypted_databases
- overly_permissive_iam
Enforcement:
thothctl ai-review decide --pr-number 42 --dry-run
# Risk score: 15 β below approve_thresholds.risk_score_max (20)
# Confidence: 0.95 β above confidence_min (0.90)
# Decision: APPROVE β
File: cost/policy/*.rego + cost/policy/config.yaml
Evaluated by: thothctl check iac -type cost-analysis --enforce-policy <path>
Purpose: Enforce budget limits, block expensive resource types, and flag cost anomalies using the same OPA/Rego engine as security policies.
How it works: The cost analyzer produces a JSON report with resource costs, service breakdown, and totals. This JSON is fed as input to conftest/OPA for policy evaluation.
# cost/policy/budget.rego
package main
import rego.v1
# Deny if monthly cost exceeds budget
deny contains msg if {
data.budget.max_monthly_total
input.summary.total_monthly_cost > data.budget.max_monthly_total
msg := sprintf("Total monthly cost $%.2f exceeds budget limit $%.2f", [
input.summary.total_monthly_cost,
data.budget.max_monthly_total,
])
}
# Deny blocked instance types
deny contains msg if {
data.instance_types.blocked
resource := input.resources[_]
resource.type == "aws_instance"
instance_type := resource.details.instance_type
instance_type in data.instance_types.blocked
msg := sprintf("Instance type '%s' is blocked by cost policy", [instance_type])
}
# Warn on expensive individual resources
warn contains msg if {
data.budget.expensive_resource_threshold
resource := input.resources[_]
resource.monthly_cost > data.budget.expensive_resource_threshold
msg := sprintf("Resource '%s' costs $%.2f/month β review for optimization", [
resource.address, resource.monthly_cost,
])
}
Parameters (cost/policy/config.yaml):
budget:
max_monthly_total: 5000
max_monthly_increase: 500
warn_monthly_total: 2000
expensive_resource_threshold: 200
instance_types:
blocked:
- p4d.24xlarge
- p3.16xlarge
- x2idn.metal
services:
max_per_service:
EC2: 2000
RDS: 1500
Enforcement:
# Use local cost policies
thothctl check iac -type cost-analysis --recursive --enforce-policy ./policy/cost
# Use org-level cost policies (from THOTH_ORG_POLICY repo)
export THOTH_ORG_POLICY=https://github.com/thothforge/org-iac-policies.git
thothctl check iac -type cost-analysis --recursive --enforce-policy cost
# Result:
# β οΈ Monthly cost $2500 approaching budget limit
# β Service 'RDS' cost $1800 exceeds limit $1500/month
# β Cost Policy Enforcement Failed
Cost input JSON structure (what policies evaluate):
{
"summary": {
"total_monthly_cost": 2500.00,
"total_running_monthly_cost": 2500.00,
"stacks": 5
},
"resources": [
{
"address": "module.aurora.aws_rds_cluster.this[0]",
"type": "aws_rds_cluster",
"service": "RDS",
"monthly_cost": 58.40,
"action": "create",
"confidence": "high",
"details": {"instance_class": "db.r6g.large"}
}
],
"cost_by_service": {
"RDS": 58.40,
"EC2": 0.00
}
}
Policies are resolved in this order (most specific wins):
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3b82f6',
'primaryTextColor':'#ffffff',
'primaryBorderColor':'#2563eb',
'lineColor':'#94a3b8',
'secondaryColor':'#10b981',
'tertiaryColor':'#8b5cf6',
'background':'transparent',
'mainBkg':'#3b82f6',
'secondBkg':'#10b981',
'tertiaryBkg':'#8b5cf6',
'clusterBkg':'rgba(241, 245, 249, 0.05)',
'clusterBorder':'#475569',
'titleColor':'currentColor',
'edgeLabelBackground':'transparent',
'nodeTextColor':'#ffffff',
'textColor':'currentColor',
'nodeBorder':'#1e293b',
'fontSize':'14px'
}}}%%
graph TB
Org["π’ Organization<br/>Git Policy Repository"] --> Space["ποΈ Space<br/>.thothcf_project.toml"]
Space --> Project["π Project<br/>.thothcf.toml"]
Org -->|"Inherited by all"| Space
Space -->|"Override per team"| Project
classDef orgStyle fill:#8b5cf6,stroke:#a78bfa,stroke-width:2px,color:#fff
classDef spaceStyle fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
classDef projStyle fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff
class Org orgStyle
class Space spaceStyle
class Project projStyle
| Level | Source | Scope |
|---|---|---|
| Organization | Git repository (shared across all teams) | Global governance baseline |
| Space | .thothcf_project.toml in space root |
Team/domain-specific overrides |
| Project | .thothcf.toml in project root |
Project-specific exceptions |
Resolution rule: Project-level policies override Space-level, which override Organization-level. Structure policies are replaced entirely (no merge). Security policies (Rego) are additive (all levels evaluated).
configs/scan_policy.toml)Each space can define a scan policy override at ~/.thothcf/spaces/<space_name>/configs/scan_policy.toml. This file controls scan enforcement behavior and supply-chain thresholds for all projects within the space.
# ~/.thothcf/spaces/production/configs/scan_policy.toml
[enforcement]
mode = "hard" # "soft" (report only) or "hard" (fail on violations)
fail_on_severity = "high" # Minimum severity to trigger enforcement failure
[supply_chain]
max_module_staleness_days = 90 # Flag modules not updated in N days
require_pinned_versions = true # Require exact version pins (no ranges)
allowed_registries = [ # Approved Terraform registries
"registry.terraform.io",
"https://private.registry.example.com"
]
[thresholds]
max_critical = 0 # Maximum critical findings before failure
max_high = 5 # Maximum high findings before failure
max_medium = 20 # Maximum medium findings (warning only)
How it connects to scan commands:
thothctl scan iac reads the active spaceβs configs/scan_policy.toml to determine enforcement mode and severity thresholds.thothctl inventory iac --check-versions uses [supply_chain] settings to flag stale or unpinned modules.[enforcement].mode = "hard", scans that exceed the configured thresholds exit with a non-zero code, failing CI pipelines.This file sits between organization-level policies (Git repo) and project-level .thothcf.toml in the hierarchy β it provides team/space-wide defaults without requiring every project to redeclare them.
Organization-level policies live in a dedicated Git repository that acts as the single source of truth for governance across all teams, domains, and workloads.
org-iac-policies/
βββ README.md
βββ .thothcf.toml # Org-level defaults
β
βββ domains/ # Policies per business domain
β βββ fintech/
β β βββ policy/ # Rego policies for fintech
β β β βββ encryption.rego # PCI-DSS encryption requirements
β β β βββ network.rego # No public subnets
β β β βββ data.rego # Data residency rules
β β βββ .thothcf.toml # Structure rules for fintech projects
β β βββ .driftpolicy # Strict drift tolerance
β β
β βββ platform/
β β βββ policy/
β β β βββ naming.rego # Platform naming conventions
β β β βββ modules.rego # Approved modules only
β β βββ .thothcf.toml
β β
β βββ data-engineering/
β βββ policy/
β β βββ storage.rego # S3/Glue/Redshift rules
β β βββ compute.rego # EMR/Spark guardrails
β βββ .thothcf.toml
β
βββ workloads/ # Policies per workload type
β βββ containers/
β β βββ policy/
β β β βββ ecs.rego # ECS task hardening
β β β βββ eks.rego # EKS cluster policies
β β βββ .thothcf.toml
β β
β βββ serverless/
β β βββ policy/
β β β βββ lambda.rego # Lambda function constraints
β β β βββ api_gateway.rego # API Gateway policies
β β βββ .thothcf.toml
β β
β βββ databases/
β βββ policy/
β β βββ rds.rego # Multi-AZ, encryption, backup
β β βββ dynamodb.rego # Capacity and encryption
β βββ .thothcf.toml
β
βββ layers/ # Policies per infrastructure layer
β βββ networking/
β β βββ policy/
β β β βββ vpc.rego # VPC CIDR, flow logs
β β β βββ security_groups.rego # No 0.0.0.0/0 ingress
β β β βββ dns.rego # Route53 conventions
β β βββ .thothcf.toml
β β
β βββ security/
β β βββ policy/
β β β βββ iam.rego # Least privilege, no wildcard
β β β βββ kms.rego # Key rotation, deletion protection
β β β βββ secrets.rego # Secrets Manager policies
β β βββ .thothcf.toml
β β
β βββ observability/
β βββ policy/
β β βββ cloudwatch.rego # Required alarms per service
β β βββ logging.rego # Log retention minimums
β βββ .thothcf.toml
β
βββ compliance/ # Framework-specific compliance mappings
β βββ soc2/
β β βββ policy/
β β β βββ soc2_controls.rego # SOC2 control enforcement
β β βββ mapping.yaml # Finding β SOC2 control mapping
β βββ cis-aws/
β β βββ policy/
β β βββ cis_benchmark.rego
β βββ iso27001/
β βββ policy/
β βββ iso_controls.rego
β
βββ shared/ # Shared policies (applied everywhere)
βββ policy/
β βββ tagging.rego # Required tags for all resources
β βββ regions.rego # Allowed regions
β βββ cost_controls.rego # Instance size limits
βββ .driftpolicy # Default drift tolerance
βββ ai_decision_config.yaml # Default AI decision thresholds
ThothCTL resolves organization policies from a configured Git repository or local path. The policy repository can be set via:
Option 1: Space initialization
thothctl init space -s my-space --policy-repo https://github.com/my-org/org-iac-policies.git
This stores governance.policy_repo in ~/.thothcf/spaces.toml.
Option 2: Environment variable
export THOTH_POLICY_REPO=/path/to/local/clone
# or
export THOTH_POLICY_REPO=https://github.com/my-org/org-iac-policies.git
Option 3: Direct path on scan command
# Point to a specific subfolder within your org repo
thothctl scan iac --tools opa --policy-dir layers/networking/policy
# Or absolute path
thothctl scan iac --tools opa --policy-dir /path/to/org-iac-policies/domains/fintech/policy
When thothctl scan iac --tools opa runs, the OPA scanner resolves policies in this order:
%%{init: {'theme':'base', 'themeVariables': {
'primaryColor':'#3b82f6',
'primaryTextColor':'#ffffff',
'primaryBorderColor':'#2563eb',
'lineColor':'#94a3b8',
'secondaryColor':'#10b981',
'tertiaryColor':'#8b5cf6',
'background':'transparent',
'mainBkg':'#3b82f6',
'secondBkg':'#10b981',
'tertiaryBkg':'#8b5cf6',
'clusterBkg':'rgba(241, 245, 249, 0.05)',
'clusterBorder':'#475569',
'titleColor':'currentColor',
'edgeLabelBackground':'transparent',
'nodeTextColor':'#ffffff',
'textColor':'currentColor',
'nodeBorder':'#1e293b',
'fontSize':'14px'
}}}%%
graph TD
A["thothctl scan iac --tools opa<br/>--policy-dir <path>"] --> B{"Project has<br/>local policy/?"}
B -->|Yes| C["β
Use project/policy/"]
B -->|No| D{"Is absolute<br/>path?"}
D -->|Yes| E["β
Use absolute path"]
D -->|No| F{"THOTH_POLICY_REPO<br/>set?"}
F -->|No| G["β No policy found<br/>(skip scan)"]
F -->|Yes| H{"path exists in<br/>org repo?"}
H -->|Yes| I["β
Use org_repo/<path>"]
H -->|No| J{"org_repo/shared/<br/>policy exists?"}
J -->|Yes| K["β
Use org_repo/shared/policy"]
J -->|No| G
classDef found fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff
classDef notfound fill:#991b1b,stroke:#f87171,stroke-width:2px,color:#fff
classDef decision fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
class C,E,I,K found
class G notfound
class B,D,F,H,J decision
Resolution examples:
--policy-dir value |
THOTH_POLICY_REPO |
Resolved path |
|---|---|---|
policy (default) |
not set | <project>/policy/ |
policy (default) |
/path/to/org-repo |
<org-repo>/shared/policy (fallback) |
layers/networking/policy |
/path/to/org-repo |
<org-repo>/layers/networking/policy |
domains/fintech/policy |
/path/to/org-repo |
<org-repo>/domains/fintech/policy |
workloads/databases/policy |
/path/to/org-repo |
<org-repo>/workloads/databases/policy |
/absolute/path/to/policies |
(ignored) | /absolute/path/to/policies |
.thothcf.tomlProjects declare which domain, workload, and layer they belong to. This determines which org policies apply when scanning:
[thothcf]
project_id = "payment-service"
project_type = "terraform-terragrunt"
# Policy selectors β determines which org policy subfolder to use
# with: thothctl scan iac --tools opa --policy-dir domains/fintech/policy
[thothcf.governance]
domain = "fintech"
workload = "containers"
layer = "networking"
compliance = ["soc2", "cis-aws"]
Note: Automatic policy selection based on
[thothcf.governance]selectors is on the roadmap. Currently, you specify the subfolder explicitly via--policy-diror use theshared/policyfallback.
When a space is initialized with --policy-repo, the governance config is stored in ~/.thothcf/spaces.toml:
[spaces.my-space.governance]
policy_repo = "https://github.com/my-org/org-iac-policies.git"
A reference implementation is available as a GitHub template:
π thothforge/org-iac-policies β Example organization policy repository with pre-built policies for AWS, naming conventions, tagging, and compliance frameworks.
# Use as a template for your organization
gh repo create my-org/iac-policies --template thothforge/org-iac-policies
Developer writes IaC
β
ββββΊ thothctl check project iac β Structure policy
β
ββββΊ thothctl scan iac --tools opa β Security policy (Rego)
β
ββββΊ thothctl scan iac --tools checkov β Built-in CIS/AWS rules
β
ββββΊ thothctl check iac -type drift β Drift policy
β
ββββΊ thothctl ai-review decide β Decision policy
(uses scan results as input)
# .github/workflows/iac-policy.yml
name: IaC Policy Gate
on: [pull_request]
jobs:
policy-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ThothCTL
run: pip install thothctl
# Layer 1: Structure
- name: Check project structure
run: thothctl check project iac
# Layer 2: Security (OPA + Checkov)
- name: Security scan with policies
run: |
thothctl scan iac --tools checkov opa --recursive
# Layer 3: Drift (if state available)
- name: Drift detection
run: thothctl check iac -type drift --recursive
# Layer 4: AI decision
- name: AI review decision
run: |
thothctl ai-review decide \
--pr-number $ \
--repository $
policy/ directory in your project root:mkdir policy
# policy/naming.rego
package main
# Enforce resource naming convention
deny[msg] {
resource := input.resource[type][name]
not regex.match(`^(dev|stg|prd)-[a-z]+-[a-z0-9-]+$`, name)
msg := sprintf(
"Resource '%s.%s' violates naming convention: must be '{env}-{service}-{name}'",
[type, name]
)
}
thothctl scan iac --tools opa --policy-dir policy
| Policy | File | What It Enforces |
|---|---|---|
| Encryption required | policy/encryption.rego |
All S3/RDS/EBS must have encryption |
| No public access | policy/network.rego |
Security groups canβt allow 0.0.0.0/0 ingress |
| Tag compliance | policy/tags.rego |
Required tags: Environment, Owner, CostCenter |
| Module versioning | policy/modules.rego |
All modules must pin exact versions |
| Region restriction | policy/regions.rego |
Only approved regions allowed |
In the FdI model, policies become the framework rules that govern code generation:
Today: Developer writes IaC β policies validate after the fact
Future: Policies constrain generation β code is compliant by construction
The intent-to-IaC generation engine (roadmap) will read these same policy files to produce code that already passes all checks:
| Policy Layer | FdI Role |
|---|---|
Structure ([project_structure]) |
Generated code follows the required structure |
Security (policy/*.rego) |
Generated code uses only approved patterns |
Drift (.driftpolicy) |
Generated code accounts for drift tolerance |
AI decisions (ai_decision_config) |
Auto-approve low-risk generated changes |