Terraform HCL
Import raw Terraform .tf files for quick analysis of firewall rules without running terraform plan first. HCL import works in the browser and in the CLI:
npx @netbobr/cli analyze main.tf --cloud-format tf-hcl
# A module split over several files
cat *.tf | npx @netbobr/cli analyze - --cloud-format tf-hclThe HCL parser reads literal values only. For modules,
for_each,dynamicblocks or computed values, convert your Terraform code to plan JSON withterraform show -jsonand use the Terraform Plan JSON format instead (97.0% match rate in accuracy testing).
Example .tf file
resource "aws_security_group_rule" "allow-https" {
type = "ingress"
protocol = "tcp"
from_port = 443
to_port = 443
cidr_blocks = ["10.0.0.0/8"]
}
resource "aws_security_group_rule" "allow-web-egress" {
type = "egress"
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_security_group_rule" "trusted-ssh" {
type = "ingress"
protocol = "tcp"
from_port = 22
to_port = 22
cidr_blocks = [var.trusted_cidrs]
}Parser behavior
- Uses a regex-based parser (not a full HCL parser) to extract resource blocks by pattern matching.
- Supports seven resource types:
aws_security_group_rule,aws_security_group(inlineingress/egressblocks),aws_vpc_security_group_ingress_rule,aws_vpc_security_group_egress_rule,azurerm_network_security_rule,azurerm_network_security_group(inlinesecurity_ruleblocks), andgoogle_compute_firewall. - Comments (
#,//and/* ... */) are removed before parsing, so commented-out resources and attributes are ignored. Comment characters inside quoted strings (for example"arn:aws:s3:::bucket/*") are kept, and braces inside quoted strings do not end a block. Heredoc bodies (<<EOT ... EOT,<<-EOT ... EOT) are ignored: no firewall attribute is written as a heredoc, and their text never ends a block or becomes a resource. - A
resourceheader must start its line (after optional indentation), as Terraform formats it. - Attributes are read line by line, so
cidr_blocksis never confused withipv6_cidr_blocks. Lists may span several lines and contain comments. Unquoted numbers work (protocol = -1,from_port = 22). aws_security_grouprules may use block syntax (ingress { ... }) or attribute syntax (ingress = [ { ... } ]) with literal objects. Attribute syntax built from an expression (ingress = var.rules, aforexpression) is skipped with a warning.- Balanced brace matching is used to find resource block boundaries.
Terraform references
Any non-literal value cannot be resolved without running terraform plan: var.*, local.*, resource references such as aws_vpc.main.cidr_block, function calls such as concat(...), interpolated strings such as "${var.cidr}/32", and unbracketed lists such as cidr_blocks = var.admin_cidrs. netbobr never drops these silently:
- An address becomes the
0.0.0.0/0placeholder with a warning, so the rule is still analysed, conservatively:TF aws_security_group_rule.ssh: source Terraform reference var.office_cidr cannot be resolved to CIDR -- substituted 0.0.0.0/0. Literal values in the same list are kept. - A port expression is analysed as
1-65535with a warning; a protocol expression as ANY with a warning. dynamic "ingress",dynamic "egress",dynamic "allow"anddynamic "deny"blocks cannot be expanded by a regex parser. They are skipped with a warning (... dynamic "ingress" blocks cannot be evaluated by the HCL parser -- those rules were skipped (use terraform show -json)); the static blocks of the same resource are still parsed.
AWS rules
aws_security_group_rule, inlineingress/egressblocks and theaws_vpc_security_group_*_ruleresources behave like the native AWS parser:cidr_blocks/cidr_ipv4give the remote side, and the group's member instances are the0.0.0.0/0placeholder, reported in one summary warning per file.from_port = 0withto_port = 65535becomes1-65535; protocol-1(quoted or not) is ANY1-65535. Unsupported protocols (ESP, AH, GRE, SCTP, ICMPv6) are skipped with a warning.- Peers that are not IPv4 CIDRs are skipped with a warning:
security_groups,source_security_group_id,referenced_security_group_id,self = true,ipv6_cidr_blocks/cidr_ipv6, andprefix_list_ids/prefix_list_id.
GCP rules
google_compute_firewall follows the GCP parser: source_tags, source_service_accounts, target_tags and target_service_accounts are substituted with a warning; source_ranges = [] is treated like an absent attribute; deny { } blocks are counted in one "deny rules were not evaluated" warning per file; disabled = true rules are skipped with a warning.
Azure rules
azurerm_network_security_rule resources and the inline security_rule { } blocks of azurerm_network_security_group follow the same rules as the native Azure NSG parser:
- Singular and plural attributes are combined and expanded:
source_address_prefix(es),destination_address_prefix(es),destination_port_range(s). Lists may span several lines and contain comments. - Source maps to source and destination to destination in both directions: outbound rules are not swapped.
*andInternetmean0.0.0.0/0. Other service tags andsource/destination_application_security_group_idsare replaced by the0.0.0.0/0placeholder with a warning.- Any non-literal address is substituted with
0.0.0.0/0and always warned. That coversvar.*,local.*, resource references such asazurerm_subnet.web.address_prefixes[0], and interpolated strings such as"${var.cidr}". Example warning:TF azurerm_network_security_rule.ssh: source Terraform reference var.office_cidr cannot be resolved to CIDR -- substituted 0.0.0.0/0. - A rule whose source and destination are both unresolvable is skipped with one warning instead of becoming an any-to-any flow.
- A non-literal
destination_port_rangeis analysed as1-65535. A non-literalaccessis assumedAllow, and a non-literalprotocolis assumed*(any). Each of these gets a warning. accessis case-insensitive. Deny rules are not evaluated and are counted in one summary warning per file.Esp/Ahrules are skipped with a warning.dynamic "security_rule"blocks and thesecurity_rule = [ ... ]attribute syntax cannot be evaluated by a regex parser. They are skipped with a warning; useterraform show -jsonfor those NSGs.- Inline rules are reported per rule:
ruleNameis the rulename,ruleIdisazurerm_network_security_group.<name>/<rule name>.
Limitations
| Limitation | Detail |
|---|---|
| Regex parser | Not a full HCL parser. Complex expressions, locals, data sources, and conditional expressions are not evaluated. |
| Variable references | var.*, local.*, resource references and interpolated strings are substituted with 0.0.0.0/0 (addresses) or 1-65535 (ports), always with a warning. |
| for_each / count | Dynamic resource generation with for_each or count is not expanded. |
| dynamic blocks | Skipped with a warning. |
| Modules | Module calls are not followed -- only resources defined in the scanned file(s) are parsed. |
For comprehensive analysis, convert to plan JSON first:
terraform plan -out=tfplan
terraform show -json tfplan > plan.jsonThen use the Terraform Plan JSON import, which supports the same resource types with every value resolved by Terraform.
Accuracy
Accuracy measured against a 400-rule CSV baseline (360 Tier 1 + 40 Tier 2).
| Metric | Value |
|---|---|
| CLI match rate | 11.0% (44/400) |
| Browser match rate | 11.0% (44/400) |
| Tier 1 accuracy | 12.6% (44/348) |
| Field accuracy | 100% for matched flows |
| Warnings generated | 8 (6 Terraform variable references, plus one AWS member-instance and one GCP target-instance summary) |
The low match rate reflects the test fixture, not the parser: the HCL fixture is a small sample (45 flows) of the 400 baseline rules, so the other rows are expected missing. Flows that use variables are analysed with the 0.0.0.0/0 placeholder; they match the baseline only where it also has a 0.0.0.0/0 rule. For full coverage of real code, use the Terraform Plan JSON format (97.0% match rate).
Troubleshooting
"Very few rules parsed"
Check the warnings: dynamic blocks, unsupported protocols and non-CIDR peers (security groups, IPv6, prefix lists) are skipped with a reason. Resources created with for_each/count or inside modules are only visible in plan JSON:
terraform plan -out=tfplan && terraform show -json tfplan > plan.json"Variable warning"
var.* references in CIDR blocks cannot be resolved without running terraform plan. The parser substitutes 0.0.0.0/0 for them and warns, so the rule is still analysed (conservatively, often with a higher risk score). Use plan JSON to analyse the real values.
"Complex expressions ignored"
Locals, data sources, conditional expressions (condition ? a : b), and for_each are not evaluated. Only literal values are extracted.
"No resources found"
Verify the file contains resource blocks with one of the supported types. The parser matches resource "type" "name" { patterns.
Browser: Drag and drop a .tf file onto the import area. HCL format is auto-detected by the presence of resource blocks with Terraform patterns.