Write scripts that think before they type.
Learn Roterm Script from its execution model upward: capture terminal output, turn raw text into structured decisions, plan weighted command costs, and build dependable .rot automation.
What Roterm Script is
Roterm Script 2.3 is a small, server-side interpreted language. Your source is saved as a .rot file, tokenized once, parsed into a syntax tree, and walked by a sandboxed evaluator. It is never handed to loadstring/require and can never reach a Roblox service — a program can only call the builtins documented here and reach the terminal through command() and run.
It reads like a blend of Lua and Python: let declarations, if / elseif / else / end, while, for, fn functions, and short-circuiting and/or that return the operand.
The one thing to understand. A script is just you, typing faster. Every meaningful action a program takes flows through command("...") — the exact same commands you type at the prompt. The language around it (variables, loops, functions, string tools) exists to decide which commands to run and to read what they print back. Learn the terminal first; scripting is how you put it on rails.
command(text) runs a terminal command and hands you its output as a string. lines(), contains(), split() and friends turn that string into decisions. That loop — run a command, read the output, decide the next command — is the whole game.Getting started
Here is the smallest useful program and everything it teaches. Open CodeEditor, paste it, save it as first.rot.
# first.rot — my first Roterm program let target = arg(1) or "127.0.0.1" # 1st argument, or a default print "Scanning " + target let scan = command("nmap -sV " + target) # run a real command, capture its text if contains(scan, "22/tcp open") then print "SSH is open on " + target else print "No SSH here." end
Now compile and run it in the terminal:
build first.rot # produces the command /usr/bin/first first 10.0.0.5 # run it; 10.0.0.5 becomes arg(1) first # run with no argument -> target becomes 127.0.0.1
That eight-line program already uses the four pillars you will use in every script:
- Arguments —
arg(1)reads what the user typed after the command;orsupplies a fallback. - Running commands —
command("nmap -sV " + target)executes a terminal command and returns everything it printed. - Reading output —
contains(scan, "22/tcp open")inspects that text to make a decision. - Branching —
if / else / endacts on what you found.
first.rot, run build first.rot again or you will keep running the old version.The runtime mental model
A dependable script separates terminal work from language work. Terminal commands observe or change the simulated world. Roterm Script validates inputs, captures their text, parses it, chooses the next action, and records a result.
Four boundaries to keep straight
| Boundary | Reads from | Use it for |
|---|---|---|
| arg() / args() | The words supplied when the compiled command was launched | Targets, modes, flags, and user choices |
| command() | The terminal engine | Observing or changing the simulated machine and network |
| cat() / ls() | The current simulated host filesystem | Reading files that exist on the host you currently control |
| read_file() / write_file() | Private per-runner session storage | Scratch data and durable script notes; not the target host filesystem |
command() versus run
Use command(text) when your program needs to inspect the result. It runs silently and returns one string. Use the run "..." statement when the human should see the terminal output immediately and the script does not need to parse it.
let status = command("systemctl status sshd") # capture, do not echo if contains(status, "active (running)") then print "service ready" end run "uptime" # execute and echo the result
target() is a compatibility alias for the first program argument; it does not scan or auto-select a host.Build & run
Write your source in the CodeEditor and save it as name.rot. Compile it into a runnable command with:
build name.rot # produces /usr/bin/name name 10.0.0.5 # run it; everything after the name is an argument
Inside the program, read those arguments with arg(1), args() (a list of them all) and argc() (how many there are). Arguments are always strings — use num() if you need one as a number.
A program always runs with the privileges of the shell that launched it. Run it from your own workstation and it acts as you; run it from a foothold shell on a compromised host and it acts as that account — it can read only what that account can read, exactly like typing commands there by hand.
| To run a program… | …do this |
|---|---|
| From your workstation | just type name <ip> at your own prompt |
| On a foothold you hold | the program's command() calls run in your current shell context; get the shell first (exploit/shell <id>), then run it |
| Via the guided console | msfconsole → search → use <#> → set rhost → run compiles & fires an owned module for you |
Syntax basics
One statement per line; there are no semicolons. # begins a comment that runs to the end of the line. Declare a variable with let and reassign it with plain assignment:
# a comment let name = "scanner" # declare name = name + "-v2" # reassign, no 'let'
Line continuation
A line that ends on an operator, comma, colon, or an open bracket continues onto the next line, so long expressions wrap cleanly:
let total = first_value + second_value + third_value let box = { host: "nas", port: 22, }
then, else, and do deliberately do not continue a line — the newline after them starts the block body. This is why if x then and the statement under it are on separate lines.
Strings
Strings use double quotes. Inside them, \n is a newline, \t a tab, \\ a backslash and \" a literal quote. Join strings with +.
print "line one\nline two" print "path: " + "/home/" + user()
Types & values
There are five value kinds:
| Type | Literal | Notes |
|---|---|---|
| number | 42, 3.14 | Whole or decimal. Join to text only via str(). |
| string | "hello" | Join with +. Double-quoted, with \n \t \\ \" escapes. |
| boolean | true / false | Produced by comparisons and and/or/not. |
| nil | nil | Absent / empty value. Returned by cat/ls when something is unreadable. |
| list | [1, 2, 3] | Ordered. 1-based: list[1] is the first item; list[-1] is the last. |
| map | {host: "nas", port: 22} | Key/value. Access with m.host or m["host"]. Keeps insertion order. |
let ports = [22, 80, 443] let box = {name: "nas", open: true} print ports[1] # 22 (first item) print ports[-1] # 443 (last item) print box.name # nas print box["open"] # true
Truthiness
Only false and nil are "falsy". Everything else is truthy — including 0 and the empty string "". So test for empty explicitly:
if victim == nil or victim == "" then # the right way to check "no target" print "nothing to do" end
Nesting
Lists and maps nest freely — a list of maps is the natural shape for a report:
let findings = [ {ip: "10.0.0.5", svc: "ssh"}, {ip: "10.0.0.9", svc: "smtp"}, ] print findings[1].ip # 10.0.0.5
Operators
Arithmetic and comparison behave as you expect. Comparisons return real booleans. Precedence follows maths: not binds tightest, then * / %, then + -, then the comparisons, then and, then or, then ??. Use parentheses when in doubt.
Short-circuit and / or
and/or short-circuit and return the operand itself rather than a coerced boolean, which makes two idioms very common:
# default value: use arg(1), or fall back if it is nil/empty-ish let targetIp = arg(1) or "127.0.0.1" # guarded read: only cat when a path was found let text = path and cat(path)
Nil-only defaults and concise updates
a ?? b uses b only when a is nil, so valid false values survive. Compound assignment works on declared variables, list slots, and map fields.
let enabled = get(config, "enabled") ?? true attempts += 1 let counts = {ssh: 0} counts.ssh += 1
Control flow
Conditionals
if open and not patched then print "vulnerable" elseif open then print "open but patched" else print "closed" end
Loops
Four loop forms. Every loop is bounded by the sandbox (see limits).
# condition loop while attempt <= 3 attempt = attempt + 1 end # numeric range; 'step' is optional (default 1), and may be negative for i = 1 to 10 step 2 print i # 1 3 5 7 9 end # one name: list values, string characters, or map keys for host in targets print host end # two names: index/value for lists, key/value for maps for key, value in report print format("{} = {}", key, value) end # run a block a fixed number of times repeat 3 print "knock" end
break exits the innermost loop; continue skips to the next iteration.
for row in lines(scan) if trim(row) == "" then continue # skip blank lines end if contains(row, "filtered") then break # stop at the first filtered port end print row end
Use the two-name form when you need both a map's key and value:
for key, value in box print format("{} = {}", key, value) end
Functions
Define reusable logic with fn. Functions are first-class values, may recurse, and return nil if they fall off the end. A bare return returns nil immediately.
fn service_is_open(scanText, port) return contains(scanText, str(port) + "/tcp open") end if service_is_open(scan, 22) then print "SSH is up" end
Return a map or list to hand structured results back to the caller — the cleanest way to keep a program readable:
fn creds_from(text) let cfg = parse_config(text) return { account: get(cfg, "account", nil), password: get(cfg, "password", nil), } end let c = creds_from(cat("/root/Bank.txt") or "") if c.account != nil then print "found account " + c.account end
command, cat, print, …) and read the program's globals, but its parameters and any let inside it are local to that call. Pass what you need in as arguments and hand results back with return.Scope, nil & errors
Handling nil
Filesystem reads are the main source of nil: cat(path) returns nil if the file is missing or your account can't read it, and ls(dir) returns nil if the path isn't a readable directory. That is normal — branch on it rather than assuming success:
let text = cat("/etc/shadow") if text == nil then print "can't read it from this account — escalate first" else print text end
Concatenating nil is an error, so guard before you join. A tidy trick is cat(path) or "", which turns an unreadable file into an empty string you can safely pass around.
What raises a runtime error
Most builtins simply return nil/false on a "normal" failure. A few things genuinely stop the program with an error message (shown in Output): adding a string to a number, indexing a non-list/non-map, calling something that isn't a function, dividing by zero, or blowing a sandbox limit. Errors are reported with the line number so you can jump straight to the fault.
try/catch. Program defensively: check for nil, convert types with str()/num(), and confirm a command "worked" by searching its output text (e.g. contains(result, "shell opened as")) before relying on it.Searchable API index
Use the handbook filter in the left rail to narrow both this index and the navigation. Every item below links to its full behavior, return values, and examples.
print valueWrite a value to program output.
command(text)Run silently and return terminal text.
run "command"Run a command and echo its output.
arg · args · argc · targetRead launch arguments explicitly.
budget · command_costPlan work before spending operation points.
user · host · cwd · ipInspect current shell context.
cat · ls · is_dir · existsRead the current simulated host.
read_file · write_file · append_file · rememberKeep private session notes and scratch state.
len · trim · contains · split · lines · parse_configNormalize and parse terminal text.
list · push · pop · insert · slice · sortBuild and transform ordered collections.
map · get · set · keys · valuesStore structured values by key.
str · num · type · random · min · max · roundConvert types and perform numeric work.
Terminal & world builtins
These connect your program to the terminal and tell it where it is running.
| Signature | Description |
|---|---|
| print value | Emit one value as a program output line and return that value. Parentheses are optional. |
| warn(value) | Emit a warning-coloured output line and return the value. |
| assert(condition [, message]) | Stop with a line-numbered error when the condition is false or nil. |
| command(text) | Run a terminal command and return its full text output as a string. The workhorse. |
| run "command" | A language statement that runs one command and prints its output. It is not a normal function call. |
| arg(n) | The n-th command-line argument as a string (nil if absent). n is 1–32. |
| args() | All command-line arguments as a list. |
| argc() | Number of command-line arguments. |
| target() | Compatibility helper: returns arg(1) when present, otherwise nil. It does not discover or validate a host. |
| budget() | Weighted operation points still available in this run. |
| command_cost(text) | Preview how many operation points a terminal command would consume without running it. |
| user() / host() | Username / hostname of the machine the current shell is on. |
| cwd() / ip() | Current directory / IP of the machine the current shell is on. |
command vs run
let out = command("whoami") # capture "root" into a variable, decide on it run "reverse-shell 4444" # just do it and echo the result
Filesystem builtins
These act on whichever machine your current shell is on, with that shell's privileges — the ordinary post-exploitation move of landing somewhere and reading what the account can reach. They never touch your own workstation (use session files for that).
| Signature | Description |
|---|---|
| cat(path) | Read a file's contents. Returns nil if missing or unreadable. |
| ls(path) | List a directory as a list of names. Returns nil if not a readable directory. ls() lists the current directory. |
| is_dir(path) | Whether a path is a readable directory (true/false). |
| exists(path) | Whether a path exists and is reachable. Use it with cat to tell "not there" apart from "no permission". |
| parse_config(text) | Parse key=value / key: value text into a map — perfect for config files, Bank.txt, and command output. |
for name in (ls("/home/" + user()) or []) let body = cat("/home/" + user() + "/" + name) if body != nil and contains(lower(name), "bank") then print name + ":\n" + body end end
Session files
Private scratch space on your own workstation, separate from the machine your shell is on. Ideal for remembering which targets a script has already touched, or building a running log across a sweep.
| Signature | Description |
|---|---|
| read_file(name) | Read a session file (returns "" if it doesn't exist yet). |
| write_file(name, text) | Overwrite a session file with text. |
| append_file(name, text) | Append text to a session file (creates it if needed). |
| file_exists(name) | Whether a session file exists (true/false). |
| remember(name, value) | Append the complete value as a line only if that exact line is not already present. |
# remember explicit targets without duplicates for victim in args() let history = lines(read_file("touched.txt")) if not contains(history, victim) then remember("touched.txt", victim) print "queued " + victim end end
String builtins
Turning command output into decisions is 90% string work. All position arguments are 1-based; searches are literal (not patterns) and case-sensitive.
| Signature | Description |
|---|---|
| len(x) | Length of a string, list, or map. |
| upper(s) / lower(s) | Change case — lower() before a comparison to make it case-insensitive. |
| trim(s) | Strip leading/trailing whitespace. |
| contains(hay, needle) | Whether a string contains a substring — or a list contains a value. |
| starts_with(s, p) / ends_with(s, p) | Test the start / end of a string. |
| find(s, needle [, start]) | 1-based position of needle in s, or 0 if not found. Optional start offset. |
| replace(s, from, to) | Replace every literal occurrence of from with to. |
| substr(s, start [, last]) | Take part of a string by 1-based positions (negative counts from the end). |
| split(s, sep) | Split into a list on sep. Empty sep splits into single characters. |
| join(list [, sep]) | Join a list into a string with sep (default: no separator). |
| format(template, ...) | Replace each {} in order with the next displayed value. |
| lines(text) | Split command output into a list of lines — the natural way to walk output. |
Extracting a field
# pull the ip out of an arp -a row: "router (10.0.0.1) at .. on eth0" let inside = split(split(row, "(")[2], ")")[1] let device_ip = trim(inside) # "10.0.0.1"
List & map builtins
| Signature | Description |
|---|---|
| list() | Create an empty list (same as []). |
| range([first,] last [, step]) | Create an inclusive numeric range, ascending or descending, up to the collection limit. |
| sum(list) / count(list_or_text, value) | Add a numeric list / count non-overlapping occurrences. |
| push(l, v) / pop(l) | Append a value / remove-and-return the last value. |
| insert(l, i, v) / remove(l, i) | Insert / remove at a 1-based position. |
| slice(l, first [, last]) | A sub-list by 1-based positions (negative counts from the end). |
| sort(l) / reverse(l) | Order a list in place. sort is numeric if every item is a number, otherwise alphabetical. |
| get(l_or_m, key [, default]) | Read a list index or map key; return default (or nil) if absent — the safe way to read. |
| map() | Create an empty map (same as {}). |
| set(m, k, v) / delete(m, k) | Write / remove a key. |
| has(m, k) | Whether a map has a key (true/false). |
| keys(m) / values(m) | The keys / values as a list (insertion order). |
let report = [] push(report, {ip: victim, status: "owned"}) let counts = {} set(counts, "ssh", get(counts, "ssh", 0) + 1) # tally with a default
Convert & math builtins
| Signature | Description |
|---|---|
| str(x) / num(x) | Convert to text / to a number (arguments arrive as strings, so num() them for maths). |
| type(x) | Name the type of a value: "number", "string", "boolean", "nil", "list", "map". |
| random(lo, hi) | Random whole number from lo to hi inclusive. |
| min(a, b) / max(a, b) | Smaller / larger of two numbers. |
| abs(x) / floor(x) / ceil(x) / round(x) | Numeric helpers (round is to the nearest whole number). |
| clamp(x, lo, hi) | Constrain a number to a range. |
Commands you can drive
Since command()/run reach any terminal command, the "standard library" of a real script is the terminal itself. These are the commands you will script most; each returns text you can inspect. (Type help or man <command> in the terminal for the full set.)
Recon
| Command | What it does |
|---|---|
| nmap -sV <ip> | Service/version scan — find the software and version a host runs. |
| scan <ip> | Quick reachability/role scan of a host. |
| arp -a | From a foothold, list every neighbour on the LAN: host (ip) at mac [ether] on iface. |
| searchsploit <software> | Search your advisory DB for matching exploits and whether you own each. |
| nc -v <ip> <port> | Banner-grab a single service (netcat). |
| whois <ip> · dig <host> | Resolve ownership / DNS of an address you scraped from a log. |
Attack & escalate
| Command | What it does |
|---|---|
| <program> <ip> | Run a built payload (bought in a hackshop, compiled with build) at a target. |
| exploit <sig> <ip> | The low-level exploit call your payload wraps; returns "shell opened as …" on success. |
| privesc | Run an owned local kernel-LPE payload on your foothold to become root. |
| sudo -l / sudo -i | Enumerate then cash in a NOPASSWD sudo misconfiguration. |
| crack <hashfile> · su root <pw> | Recover a password from a readable hash file, then become root with it. |
Foothold, pivot & loot
| Command | What it does |
|---|---|
| server start <port> | Open a listener so a shell can call back. |
| reverse-shell <port> | Background your current shell through the listener (persistent access). |
| shell <id> · shells · exit | Switch to a saved shell, list them, or drop the current one. |
| pivot / deploy | Route through a compromised gateway to reach hosts on its LAN. |
| loot · exfil <path> | List, then claim, valuable data files for Credits. |
| bank login/balance/transfer | Use credentials you harvested to drain an account. |
Defend
| Command | What it does |
|---|---|
| secure status | Show the posture of your own devices and any host you've rooted. |
| secure all | Harden your whole GreyNet (patch + firewall + rotate). |
| secure <ip|here> patch|firewall|rotate | Harden one device — or a box you've rooted — so others can't follow you in. You keep your shell; they're shut out. |
nmap and refuse a payload from outside. Web and DNS are never filtered, so there's always a way in: compromise the gateway, then pivot onto the LAN and the filtered services become reachable from inside.Plan the operation budget
Every run begins with 240 weighted operation points. Cheap inspection commands cost very little; disruptive or high-value actions cost more. This is intentionally not a simple command counter. Ask the runtime instead of maintaining a guessed integer.
| Typical cost | Examples | Design implication |
|---|---|---|
| 1–3 | whoami, ls, cat, systemctl status | Good for validation and lightweight observation. |
| 4–8 | ping, curl, scan, ssh | Batch deliberately; avoid repeating unchanged checks. |
| 10–14 | nmap, ipgen, aircrack-ng | Cache their output and parse it once. |
| 16–20 | exfil, exploit, secure, grant | Reserve points before entering the action phase. |
Subcommands can have their own price—systemctl status and systemctl restart are not equivalent—so command_cost() is the authoritative answer.
fn can_afford(text, reserve) return budget() >= command_cost(text) + reserve end let scanCmd = "nmap -sV " + victim let actionCmd = "exploit A7-22-5C-09 " + victim if not can_afford(scanCmd, command_cost(actionCmd)) then print "stopping safely with " + str(budget()) + " points" return end let scan = command(scanCmd)
Parse terminal output reliably
Terminal text is an interface. Normalize it once, match the smallest stable phrase, and keep extraction logic in a function. That makes a script easier to repair when output gains an extra heading or spacing changes.
fn open_services(scanText) let result = [] for raw in lines(scanText) let row = trim(lower(raw)) if contains(row, "/tcp open") then push(result, raw) # retain original text for the report end end return result end
trim(lower(row)), but keep the original row if it will be printed later.split() always returns a list, but the piece you expect may not exist.key=value or key: value, use parse_config() and get().A safe extraction pattern
fn value_after(text, marker) for row in lines(text) if starts_with(trim(row), marker) then let parts = split(row, marker) if len(parts) >= 2 then return trim(parts[2]) end end end return nil end
Wireless automation, end to end
This pattern automates the Roterm simulation workflow for a BSSID you explicitly pass in. It validates the channel, enables monitor mode, reruns the capture until a .cap exists, decodes it, and restores managed mode even when recovery fails.
# usage: wifi-capture AA:BB:CC:DD:EE:FF 6 let bssid = arg(1) let channel = num(arg(2) or "0") let capture = "/tmp/wifi-" + replace(bssid or "", ":", "") + ".cap" if bssid == nil or channel < 1 or channel > 11 then print "usage: wifi-capture <BSSID> <channel 1-11>" return end let start = command("airmon-ng start wlan0") if contains(lower(start), "not found") then print start return end let captureCmd = "airodump-ng --bssid " + bssid + " --channel " + str(channel) + " --write " + capture + " wlan0mon" let collecting = true let passes = 0 while collecting and passes < 12 if budget() < command_cost(captureCmd) + command_cost("aircrack-ng -w /usr/share/wordlists/roterm.txt " + capture) + 4 then print "not enough budget to finish and restore the interface" collecting = false else let out = command(captureCmd) passes = passes + 1 if contains(out, "saved capture:") then collecting = false let cracked = command("aircrack-ng -w /usr/share/wordlists/roterm.txt " + capture) print cracked elseif contains(lower(out), "error") or contains(lower(out), "not in scan cache") then print out collecting = false end end end run "airmon-ng stop wlan0mon"
iw dev wlan0 scan first, then pass the displayed BSSID and channel to the compiled program. The capture is progressive: seeing “buffer is incomplete” means rerun the same capture command, not switch targets.Patterns & recipes
Small, reusable building blocks. Copy them straight into your programs.
Walk a command's output line by line
let scan = command("nmap -sV " + ip) for row in lines(scan) if contains(row, "open") then print trim(row) end end
Is a service open?
fn open_port(scanText, port) return contains(scanText, str(port) + "/tcp open") end
Confirm a step "worked" by reading its output
There are no exceptions — success is a string you look for. Never assume; always check.
let result = command("exploit A7-22-5C-09 " + ip) if contains(result, "shell opened as") then print "in" elseif contains(result, "filtered") then print "blocked by a firewall — pivot first" else print "failed: " + result end
Read a file safely
let body = cat(path) or "" # nil-proof if body == "" then print "nothing readable at " + path end
Parse credentials out of a config/Bank file
let cfg = parse_config(cat("/root/Bank.txt") or "") let account = get(cfg, "account", get(cfg, "bank_account", nil)) let secret = get(cfg, "password", get(cfg, "bank_password", nil))
Loop over explicit targets without overspending
Accept targets as launch arguments, skip previously handled values, and ask the runtime what each command costs.
let touched = lines(read_file("touched.txt")) for victim in args() let scanCmd = "nmap -sV " + victim if contains(touched, victim) then print "skip remembered target " + victim elseif budget() < command_cost(scanCmd) + 20 then print "stopping: action reserve reached" break else let scan = command(scanCmd) remember("touched.txt", victim) push(touched, victim) # ... inspect scan and choose the next action ... end end
Extract every LAN address from arp -a
fn lan_ips(arpText) let found = [] for row in lines(arpText) if contains(row, "(") and contains(row, ")") then push(found, trim(split(split(row, "(")[2], ")")[1])) end end return found end
Debugging playbook
Debug from the boundary inward. First prove the program received the values you expected; then prove the terminal command is correct; only then debug parsing and branches.
- Rebuild the source. The executable is a compiled snapshot. A successful save does not update it.
- Print the inputs. Check
argc(), each argument, currenthost(), andcwd(). - Preview the exact command. Store it in a variable and print it before calling
command(cmd). - Inspect raw output once. Temporarily print the captured result before applying
contains()orsplit(). - Normalize and narrow. Compare
trim(lower(row))to one stable phrase rather than a full formatted line. - Print state, not floods. Report counters, selected paths, and
budget(); avoid dumping the same scan in every loop.
print "args=" + str(args()) print "context=" + user() + "@" + host() + ":" + cwd() print "budget.before=" + str(budget()) print "command=" + cmd let raw = command(cmd) print "raw=" + raw print "budget.after=" + str(budget())
Build errors versus runtime errors
type(value) before the failing operation.return halfway down. If the problem disappears, it is below that point; otherwise it is above. Move the boundary until the failing block is small.Common errors & how to fix them
| Message / symptom | Cause & fix |
|---|---|
| cannot add a string and a number | You joined text with a number. Wrap the number: "n=" + str(count). |
| attempt to concatenate nil | A cat/get/arg returned nil. Guard it, or use … or "". |
| list index out of range | Remember lists are 1-based; list[0] is invalid. First item is [1]. |
| operation budget exceeded | The weighted total of terminal actions passed 240 points. Check budget() and command_cost(text) before expensive work. |
| a script is already running | Only one program runs at a time. Let the first finish before launching another. |
| output truncated | You printed past 300 lines / 20,000 bytes. Print summaries, not raw dumps. |
| nothing happens after "then" | then/else/do don't continue a line — put the block body on the next line. |
| my edits didn't take effect | You forgot to recompile. Run build name.rot after every source change. |
Sandbox & limits
Every program runs server-side inside a bounded interpreter. It has no access to Roblox APIs and inherits the privileges of the shell that launched it. Hard limits stop runaway scripts:
| Limit | Value | What to do about it |
|---|---|---|
| Saved source / runtime source | 12,000 / 24,000 bytes | The editor library enforces the smaller persistence limit; the interpreter keeps a separate hard ceiling for trusted generated programs. |
| Evaluation steps | 20,000 | Avoid needless nested loops over big lists. |
| Loop iterations (while / repeat) | 1,000 | Bound your loops with a counter or condition. |
| Weighted operation budget | 240 points | Use budget() and command_cost(); costs depend on the command and sometimes its subcommand. |
| Nesting / call depth | 24 | Prefer loops to deep recursion. |
| Output lines / bytes | 300 / 20,000 | Print conclusions, not raw command dumps. |
| Single value size | 20,000 bytes | Parse large terminal output promptly and retain only useful fields. |
| Collection items | 512 | Slice or summarise very large lists. |
/etc/shadow from an unprivileged foothold fails exactly as it does at the prompt. Terminal execution through command()/run spends weighted points; language operations and direct read-only builtins such as cat() and ls() do not.Worked examples
1 · SSH sweep
Accept explicit targets, fingerprint them, respect the weighted budget, and summarize any successful simulated footholds.
# usage: ssh-sweep 10.0.0.5 10.0.0.8 10.0.0.12 let payload = "A7-22-5C-09" let historyFile = "ssh-touched.txt" let report = [] fn service_is_open(scanText, port) return contains(scanText, str(port) + "/tcp open") end if argc() == 0 then print "usage: ssh-sweep <ip> [<ip> ...]" return end let touched = lines(read_file(historyFile)) run "server start 4444" run "apt install sshwire" for victim in args() let scanCmd = "nmap -sV " + victim let exploitCmd = "exploit " + payload + " " + victim let reserve = command_cost(exploitCmd) + command_cost("reverse-shell 4444") if contains(touched, victim) then print "skip remembered target " + victim elseif budget() < command_cost(scanCmd) + reserve then print "budget reserve reached; stopping" break else print "scanning " + victim let scan = command(scanCmd) remember(historyFile, victim) push(touched, victim) if not service_is_open(scan, 22) then print " no SSH service exposed" elseif not contains(scan, "OpenSSH") then print " port 22 is not OpenSSH" else let result = command(exploitCmd) if contains(result, "shell opened as") then push(report, victim) run "reverse-shell 4444" else print " no foothold: " + trim(result) end end end end print "Shells opened: " + str(len(report)) for host in report print " compromised: " + host end print "Budget remaining: " + str(budget())
2 · Credential harvest
Walk a home directory, read whatever the current shell may read, and act on any file that looks like an account and a secret.
# Credential harvest (.rot) fn credentials_in(path) let text = cat(path) if text == nil then return nil # missing, or this account cannot read it end let cfg = parse_config(text) let account = get(cfg, "account", get(cfg, "bank_account", nil)) let secret = get(cfg, "password", get(cfg, "bank_password", nil)) if account == nil or secret == nil then return nil end return {path: path, account: account, password: secret} end fn walk(dir, results) let entries = ls(dir) if entries == nil then return results # not a directory, or permission denied end for name in entries let path = dir + "/" + name if is_dir(path) then walk(path, results) else let hit = credentials_in(path) if hit != nil then push(results, hit) end end end return results end let home = "/home/" + user() print "Searching " + home + " as " + user() + "@" + host() let found = walk(home, []) if len(found) == 0 then print "No credential files readable from this account." else for hit in found print " " + hit.path + " account=" + hit.account end let creds = found[1] print "Authenticating with " + creds.account + " from " + creds.path print command("bank login " + creds.account + " " + creds.password) print command("bank balance") end
3 · LAN sweep from a foothold
Once you hold a gateway shell, enumerate the whole LAN with arp -a and fingerprint each device — one run maps an entire network.
# LAN sweep (.rot) — run from a shell on a compromised gateway fn lan_ips(arpText) let found = [] for row in lines(arpText) if contains(row, "(") and contains(row, ")") then push(found, trim(split(split(row, "(")[2], ")")[1])) end end return found end print "Enumerating the LAN from " + host() + " (" + ip() + ")" let devices = lan_ips(command("arp -a")) print "Found " + str(len(devices)) + " host(s)" for dev in devices let scanCmd = "nmap -sV " + dev if budget() >= command_cost(scanCmd) then print "--- " + dev + " ---" print command(scanCmd) else print "budget exhausted before " + dev break end end
4 · Defensive audit
A tiny program that reports your network's security posture and hardens anything still exposed.
# Defensive audit (.rot) — run from your workstation let status = command("secure status") print status if contains(status, "open:") or contains(status, "firewall off") then print "Exposed devices found — locking the whole GreyNet down." print command("secure all") else print "All devices already hardened." end
Cheat sheet
Syntax
let x = 1 # declare x = 2 # reassign if c then … elseif c then … else … end while c … end for i = a to b step s … end for v in coll … end repeat n … end break / continue fn name(a, b) … return v end [1, 2, 3] # list (1-based) {k: v} # map (m.k / m["k"])
The core loop
let out = command("nmap -sV " + ip) # 1. run a command, get text if contains(out, "22/tcp open") then # 2. read it to decide run "exploit … " + ip # 3. run the next command end
Builtins at a glance
Output & world: print warn assert command run arg args argc target budget command_cost user host cwd ip
Files (current host): cat ls is_dir exists
Session (your box): read_file write_file append_file file_exists remember
Strings: len upper lower trim contains starts_with ends_with find replace substr split join lines format parse_config
Lists/maps: list range sum count push pop insert remove slice sort reverse map get set delete has keys values
Convert/math: str num type random min max abs floor ceil round clamp