Module
HCL Fundamentals
Understand how redteamer’s HCL-based language works. This guide covers syntax, blocks, expressions, operators, and functions that form the foundation of every module.
Overview
redteamer uses HCL (HashiCorp Configuration Language) to describe modules. Each .rt.hcl file is a structured configuration made of blocks, arguments, and expressions.
- A block declares something
- An argument assigns a value
- An expression defines or computes that value
Block Structure
Every block follows this form:
<BLOCK TYPE> "<BLOCK LABEL>" {
<NAME> = <VALUE>
}
Example:
action "scan_dir" {
with = "core/tools/gobuster/dir.rt.hcl"
base_url = "http://192.168.0.155/"
wordlist = "/usr/share/wordlists/dirb/common.txt"
}
In this example:
actionis the block type"scan_dir"is the label- The body defines its arguments
Arguments
Arguments assign values to names inside a block.
target = "127.0.0.1"
threads = 10
enabled = true
Identifiers must start with a letter and may include letters, digits, underscores, and hyphens.
Meta Arguments
Meta arguments control repetition and conditional execution.
| Meta argument | Description |
|---|---|
for_each | Creates one block per element in a collection |
count | Creates a specific number of block instances |
when | Runs the block only when the condition is true |
Example
action "scan_dir" {
for_each = toset(["192.168.0.10", "192.168.0.11"])
with = "core/tools/gobuster/dir.rt.hcl"
base_url = "http://${each.value}/"
when = var.enable_scanning
}
Types and Values
Expressions generate values. Each value has a type that defines how it behaves.
| Type | Example | Description |
|---|---|---|
| string | "admin" | Text value |
| filepath | "/etc/passwd" | File system path |
| number | 42 | Integer or float |
| bool | true | Boolean value |
| list / tuple | ["one", "two"] | Ordered sequence |
| map / object | { key = "value" } | Key-value pairs |
| null | null | No value |
Expressions
Expressions compute or transform values. They can be literal, interpolated, or function-based.
Literals
target = "10.0.0.5"
timeout = 30
enabled = true
Interpolation
Insert variables or computed values into strings using ${}.
url = "http://${var.host}:${var.port}/"
Arithmetic and Logic
threads = 2 * 4
should_run = var.is_admin && !var.is_blocked
Operators
Arithmetic
| Operator | Meaning |
|---|---|
+ | Addition |
- | Subtraction |
* | Multiplication |
/ | Division |
% | Modulo |
sum = 10 + 5
Comparison
| Operator | Meaning |
|---|---|
== | Equal |
!= | Not equal |
< | Less than |
<= | Less than or equal |
> | Greater than |
>= | Greater than or equal |
is_greater = 10 > 5
Logical
| Operator | Meaning |
|---|---|
&& | AND |
|| | OR |
! | NOT |
should_run = var.enabled && !var.error
Accessing Collection Elements
Use indices or keys to access collection members.
Lists
ips = ["10.0.0.1", "10.0.0.2"]
first = ips[0]
Maps
services = {
http = 80
https = 443
}
tls_port = services["https"]
References
References connect values between blocks. Use dot notation to access attributes.
output "loot" {
value = action.exfil.results
}
Cross-block examples
output "urls" {
value = action.web_scan.results
}
Blocks created with for_each or count can be accessed by key or index.
output "scan_one" {
value = action.web_scan["192.168.0.10"].results
}
Strings and Templates
Quoted Strings
Use " and escape sequences.
msg = "Scan complete for ${var.target}"
Heredoc Strings
For multi-line content:
report = <<-EOT
Scan Report
-----------
Target: ${var.target}
Status: Success
EOT
Escape Sequences
| Sequence | Meaning |
|---|---|
\n | New line |
\t | Tab |
\\ | Backslash |
\" | Double quote |
\uNNNN | Unicode |
$${ | Escape interpolation |
Conditional Expressions
Use the ternary form to choose between values.
access_level = var.is_admin ? "Full Access" : "Restricted"
For Expressions
Build lists or maps by iterating over collections.
# List result
urls = [for ip in var.targets : "http://${ip}/"]
# Filtered list
active = [for h in var.hosts : h if h.is_active]
# Map result
ports_by_name = { for s in var.services : s.name => s.port }
You can group attributes with ... to merge objects.
users_by_role = { for user in var.users : user.role => user... }
Splat Expressions
Splat expressions extract a single field from each element of a list.
usernames = var.logins[*].username
When a block uses count, you can collect all results with a splat:
action "scan" {
count = 2
with = "core/tools/nmap/scan.rt.hcl"
target = "10.0.0.${count.index + 1}"
}
all_results = action.scan[*].results
If the source is a map (from for_each), wrap it with values() before splatting:
results = values(action.scan)[*].results
Flatten nested lists using col::flatten():
all_files = col::flatten(values(action.exfil)[*].files)
Functions
Functions compute or transform values. Call them with parentheses and comma-separated arguments.
timeout = num::max(5, var.default_timeout + 10)
| Category | Example | Purpose |
|---|---|---|
| String | str::upper("redteamer") | String operations |
| Numeric | num::min(3, 7) | Math operations |
| Collection | col::length(var.targets) | Collection utilities |
| Validation | val::url(var.target) | Input checks |
| System | sys::arch() | System info |
Argument Expansion
Use ... to unpack lists.
scores = [2, 3, 4]
lowest = num::min(scores...)
Comments
Use #, //, or /* */.
# single line
// another
/* multi-line */
File Encoding and Line Endings
- Files must be UTF-8 encoded
- LF endings are preferred, but CRLF is accepted
