Format reference
The .txt grammar for the implemented protocols on one page: every line, every field, every assert. Written to be read by a language model.
This page is the complete grammar for the protocols the runtime implements today. It is written so a language model can produce a correct file from this page alone. Nothing else is needed for the implemented set; the vocabulary is open and grows toward every protocol in the Identity list.
The file
- A solution is a text file with a
.txtextension. - Lines starting with
#are comments and are ignored. - Blank lines separate sections. Two-space indentation marks a block.
- A step is one or more English sentence directives, one per line. The directive starts with a verb word (a method like
get/post, orsend,receive,run,read,write,expect,capture,with,timeout,port,close); the rest of the line is the value. Quoted values use"...". - The header lines and the
vars:,set:,headers:,cookies:, andoob:blocks are the onlyname: valuelines. There is no other syntax.
The header
Lines before the first step:
| line | meaning |
|---|---|
id: <name> | required, names the attack, in vendor/product style |
summary: <text> | optional, one sentence about the attack |
ref: <ref> | optional, repeat the line for more, for example CVE-2014-0160 |
param: <name> | optional, the HTTP input parameter name |
vars: | optional block, one name: value per line, starting values |
oob: | optional block, host: and port: lines; port: 0 picks a free one |
The names flag_path, oob_host, and oob_port are reserved and cannot be declared.
A step
A step is a block. The first line is <identity> <step-name>. Indented lines below it are directives, one per line.
<identity> <step-name>
<directive>
expect ...
The identity and the step name are always required. A step name is one word, unique within the file, and cannot be a directive keyword (a sentence verb such as get, post, send, expect, or capture).
Identity
The runtime implements eighteen protocols today, the ones that appear most often in real exploits. Use only these to produce a valid file; the vocabulary is open and grows toward every protocol in the Identity list:
| identity | required (English directive) | other directives |
|---|---|---|
http, https | a method directive (get/post/put/patch/delete/head/options) | with the header, with the cookie, set, capture, expect |
websocket | send | receive, receive until, timeout, close the connection, capture, expect |
tcp, tls, ssh, ftp, smtp, ldap, redis, mysql, postgresql, smb, telnet | send | receive, receive until, port, timeout, close the connection, capture, expect |
dns, snmp | send | receive, receive until, timeout, close the connection, capture, expect |
process | run | expect |
file | read or write | expect |
The “required” column is the directive that opens the step. The “other directives” column lists the sub-lines that follow.
http, https
An http step is a set of directives; the program does the heavy lifting. The directives are:
| directive | meaning |
|---|---|
get <path> | GET the path; the path may be quoted (get "/login") |
get with <value> | GET the target itself with the value in a query parameter |
get <path> with <value> | GET the path with the value in a query parameter |
put, patch, delete, head, options <path> | the verb is the HTTP method; the same forms as get apply |
post "<path>" [as <shape>] [with <value>] | POST the path (quote it like get). as a form field <name> posts one form field, as json a JSON object with the value, as a raw form the body verbatim with form content-type (the default), as a raw json the body verbatim with JSON content-type, as a raw multipart the body verbatim as a multipart/form-data body (the runtime reads the boundary from the body’s first line) |
with the header "<name>" set to <value> | set one request header |
with the cookie "<name>" set to <value> | add one cookie, joined into the Cookie header |
expect ... | the assert: expect "<text>" in the response, expect the response to match "<regex>", expect the target to call back "<expect>", expect a flag, expect no response |
A value may be written base64 of "<text>" to base64-encode it after {var} substitution. Captures use the English forms above. The example solution reads entirely as sentences:
http sso
get "/login"
expect "SAMLRequest" in the response
capture the form field "SAMLRequest" as saml_b64
http deliver
post "/acs" as a form field SAMLResponse with base64 of "<samlp:Response InResponseTo=\"{reqid}\"/>"
expect "accepted" in the response
The target is a base URL; https is the same exchange over TLS, and the http identity also covers both schemes.
A solution-level headers: or cookies: block (before the first step, like vars:) sets defaults applied to every http step. A step’s own with the header/with the cookie lines, or its own headers:/cookies: block, add to or override them. Values accept {var} references, filled when each step runs. This is how a multi-step session replays the same cookies on every request without repeating them:
cookies:
AUTH_SESSION_ID: "{sid}"
KC_AUTH_SESSION_HASH: "\"{hash}\""
The response an assert or capture sees is the full exchange: the status line and headers of every response in the redirect chain (so a redirect Location is assertable), then the final body.
websocket
A websocket step reads as sentences: send "<text>" sends the payload, receive <n> bytes reads at most that many bytes, receive until "<hex>" stops at a hex marker, timeout <n> seconds bounds the read, close the connection ends the session, and expect ... checks the answer:
websocket ws-rpc
send "{\"jsonrpc\":\"2.0\",\"id\":1}"
receive 65536 bytes
timeout 5 seconds
expect "226b22" in the response
The payload is sent as-is (text, wrapped into a masked frame by the runtime); the send hex "..." form does not apply. The connection stays open across steps.
tcp, tls, and the byte-family services
A byte step reads as sentences: send hex "<hex>" sends raw bytes (or send hex of "<text>" hex-encodes readable text), receive <n> bytes reads at most that many, receive until "<hex>" stops at a marker, port <n> reconnects the session to another port, timeout <n> seconds bounds the read, close the connection ends the session, and expect ... checks the hex response:
tls malformed-heartbeat
send hex "1803030003014000"
receive 70000 bytes
timeout 5 seconds
expect the response to match "18030[123]40"
The send hex of "<text>" form interprets the usual \r, \n, \t, \", \\ escapes plus \xHH for one arbitrary byte, so control characters stay visible (send hex of "HELO x\r\n"). tls sends and receives raw TLS record bytes, so a solution authors the handshake itself. Each service (ssh, ftp, smtp, ldap, redis, mysql, postgresql, smb, telnet) has a default port when the target omits one.
dns, snmp
dns and snmp speak raw datagrams over UDP; the fields are the byte-session fields above. The target is host:port; the default port is 53 for dns and 161 for snmp.
process
A process step reads as run "<command>", with an optional in "<directory>" clause for the working directory:
process enum
run "python ssh_enum.py 127.0.0.1" in "C:\\Temp\\prototest"
expect the response to match "root is a valid user"
file
A file step reads as read "<path>" or write "<path>" with "<data>":
file write-it
write "C:\\Temp\\exploit.mvg" with "push graphic-context\nfill 'url(x|id)'\npop graphic-context\n"
Capture
A capture pulls a value out of the response for later steps, and it reads as English. The program does the heavy lifting: it knows page shapes and encodings, so the author names the value and the runtime finds the bytes.
The shape form names a thing the page holds, and the runtime finds it by shape:
capture the <shape> "<name>" [of <source>] [base64] as <var>
| shape | what the runtime finds |
|---|---|
form field | the value of an HTML input whose name is <name> |
attribute | the value of an XML or HTML attribute <name>="..." |
json field | the value of a JSON string field "<name>": "...", raw or HTML-escaped ("<name>":"..." inside an attribute value) |
query parameter | the value of a URL query parameter <name>=<value> (raw ?/& or HTML-escaped &) |
cookie | the value of the <name> cookie from the response’s Set-Cookie headers |
value after | on a byte-family step, the run of hex after the hex <name> marker, hex-decoded (how a server-assigned port id is pulled out of the hex stream) |
input name after | the tail of an HTML input’s name after the prefix <name> (how the Next.js Server Action reference index $ACTION_REF_1 is read from the input’s name attribute) |
For a SAML page that hands its request over base64-encoded:
http page
get "/sso"
capture the form field "SAMLRequest" as saml_b64
capture the attribute "ID" of saml_b64 base64 as reqid
capture the attribute "AssertionConsumerServiceURL" of saml_b64 base64 as acs
Line by line: capture the SAMLRequest form value as saml_b64; then, from its base64-decoded value, capture the ID attribute as reqid and the AssertionConsumerServiceURL attribute as acs. of <source> names the variable to search (the response by default), and base64 decodes it first. The author never writes the markup; the shape defines it.
The template form matches literal text with one <name> placeholder where the value appears:
capture <var> [from <source>] [base64] where "<text with one <name> placeholder>"
The literal text is matched exactly and <name> captures whatever appears there. The same example in this form:
capture saml_b64 where "name=\"SAMLRequest\" value=\"<value>\""
capture reqid from saml_b64 base64 where "ID=\"<value>\""
The regex form is the same capture with a pattern instead of a template:
capture <var>: <regex> [from <var>] [base64]
On http the regex runs over the full response text (status line, headers, and body); on tcp/tls/websocket over the received hex, and group 1 is hex-decoded. Later steps use {var}. A step may carry several capture lines, one value each, applied in order.
All three forms share the source clauses: from/of <var> runs the pattern over an earlier variable’s value instead of this step’s response, and base64 decodes the source first. The source names a variable declared before the step (a solution var, a built-in, an earlier step’s set or capture, or an earlier capture on the same step).
Set
set: is a block, one name: value per line. The values go to later steps. A later write on the same name wins. A step’s own set applies to later steps only.
A set line can also compute a value from a deterministic data operation, the transform sentence:
set <var> = aesgcm of "<plaintext>" with "<base64-key>"
It runs after the step’s captures (so the plaintext may reference a value the step just learned, like {action_id}), and its result is stored in <var> for later steps, exactly like a set: value. aesgcm seals the plaintext with AES-GCM under the base64 key and stores base64(iv + ciphertext) with a 16-byte iv, the encoding Next.js Server Actions use for bound arguments. If a referenced value never arrived (the target did not leak it), the transform is skipped and a later step’s assert reports not verified.
Assert
The assert reads as expect ...; each form is one of the five test kinds:
| directive | type | passes when |
|---|---|---|
expect "<text>" in the response | contains | the response contains the exact text |
expect the response to match "<regex>" | regex | the response matches the pattern |
expect a flag | flag | the response contains a flag-shaped string such as FLAG{...} |
expect the target to call back "<expect>" | oob | the target contacted the out-of-band listener |
expect no response | empty | the response is empty: the target never answered |
Target
The target argument is shaped by the solution’s protocols: tcp, tls, and the byte-family services (including dns and snmp) take host:port, http, https, and websocket take a base URL, process and file ignore it.
Full example
id: openssl/heartbleed-mem-leak
summary: CVE-2014-0160 Heartbleed
ref: CVE-2014-0160
tls clienthello-heartbeat
send hex "1603030125010001210303..."
receive until "0e000000"
receive 65536 bytes
expect "0e000000" in the response
# server completes the handshake
tls malformed-heartbeat
send hex "1803030003014000"
receive 70000 bytes
expect the response to match "18030[123]40"
Result
The runtime prints one line per step (name PASS|FAIL test) and a result line: verified when every test passes, not verified when one fails. Parse errors name the line number. The runtime never interprets: the file is what runs.