Help
Everything you need to drive Hackvertor: converting data with tags, letting the autodecoder unpick unknown encodings, writing and sharing your own conversion tags, and running payloads in HackPad.
What is Hackvertor?
Hackvertor is a conversion tool for web security research. Instead of picking an encoding from a dropdown, you wrap text in tags — small, nestable markers that transform whatever they contain. Because tags nest, you can build up arbitrarily deep encoding chains and see the result update live.
The site is built around five things:
- The converter — the home page, where you encode and decode text with tags.
- The autodecoder — hands it an unknown blob and it works out the encoding chain for you.
- The tag store — browse, install and publish conversion tags written in JavaScript.
- HackPad — an isolated iframe for running HTML and JavaScript payloads with control over CSP, charset and content type.
- The hacking room — broadcast a payload to everyone in a room and see how each browser renders it.
Every tag runs in a web worker inside a sandboxed iframe with a restrictive CSP, so tag code never touches the main page, has no network and is stopped if it never finishes.
Getting started
You can use the converter, HackPad, Snippet and browse saved recipes and pads without an account. Logging in (GitHub OAuth, via Login in the nav) additionally lets you:
- install tags from the tag store
- write and publish your own tags
- save public recipes and pads
- submit encoding tests, like tags and follow people
The converter
The converter has two editors: Input on the left and Output on the right. You write tags in the input; the output shows the converted result.
Applying tags
Above the editors is the tag menu, grouped by category (Array, Charsets, Checks, Compression, Convert, Email, Encrypt, Hash, IP, Math, SQLi, String, Unicode, Utils, Variables, XML, XSS). Clicking a tag wraps the current selection in the input — or inserts an empty pair of tags if nothing is selected — and leaves the selection inside the tag so you can keep typing.
Tags that can decode as well as encode expand into a submenu with encode, decode and decode → encode. Hovering a tag shows its description and the help text for each of its arguments.
The Search tags… box above the menu is usually faster: type part of a tag name and the categories are replaced by flat lists of matching tags under Encode, Decode and Decode → Encode.
The editors
- Autocomplete. Type
<@in the input to getencode,decode,decodeEncodeandautodecode. Once you've picked one, the completion list becomes every installed tag name, pre-filled with that tag's default arguments and its closing tag. - Tag highlighting and tooltips. Hackvertor tags are highlighted in the input, and hovering one shows what the tag does.
- Tag suggestions. Select some text in the input and, if it looks encoded, a “Looks like” tooltip offers the decodes that fit it. A tag is offered outright when its
matches()method takes the whole selection, and marked part when itsstartsWith()method recognises something inside it. Each tag comes with two buttons. decode replaces the text the tag recognised with what it decodes to. decode → encode replaces it with the decoded text wrapped in the matching encode tag, so the output stays the same while the input becomes the recipe that produces it. Either way the decoded text stays selected, so a nested encoding comes apart one click at a time. Hover a suggestion to see what it decodes to. Tags you install join in as long as they define those methods and adecode(). - Unicode highlighting. Any non-ASCII character is highlighted in red in both editors. Invisible characters (zero-width spaces, tag characters, variation selectors, bidi controls) are drawn as a red badge showing their codepoint, so you can see the things you normally can't.
- Character info. Select a single character in either editor and a panel appears in the bottom right with its name, codepoint, decimal value, UTF-8 and UTF-16 bytes, HTML entity, JavaScript escape and percent-encoding.
- Selection matches. Selecting text highlights every other occurrence of it in the same editor.
- Length counters. The yellow number next to “Input:” and “Output:” is the character count. A second red number appears when the UTF-8 byte length differs — a quick signal that the text contains non-ASCII characters.
Jigsaw mode
Switch the input from Tags to Jigsaw to build a conversion out of pieces. A row reads left to right: it starts with a text piece, and each tag fitted to its right wraps everything before it. Rows run together in the output; the ↵ button next to + Text adds a row holding only a line break, so the rows either side of it come out on separate lines. The panel of any text piece with line breaks in it has a CR/LF line breaks switch that writes them as \r\n sequences rather than \n. The tags the canvas generates are shown underneath, and switching back to Tags mode keeps them.
- Values on every piece. After a conversion, each tag piece shows the value it produced and how much longer or shorter it made its input. A tag that threw is outlined in red with its error. Select a piece and press Output to read or copy the whole value.
- Conditions. In a row joined with
&&or||, each operand gets a green or red dot for how it was read, and operands that never ran because an earlier one decided the result are dimmed. - Solve. Select a text piece and press Solve to run the autodecoder on it. The piece is replaced by the chain of tags that produces it, with the decoded text at its head.
- Flip row. Turns an encoder into the decoder that undoes it, or the other way round: the tags swap
encodefordecode, run in reverse order, and start from the row's output. It is offered for rows made only of encode and decode pieces whose tags implement both directions. - Suggestions. When the value leaving the selected piece looks encoded, the panel offers the decode that fits it.
- Variables. A dashed wire joins every
setVariablepiece to thegetVariablepieces that read it, one colour per name. A read that nothing sets is outlined in yellow, because it produces an empty string. Branch stores a row's value in a variable and starts a new row that reads it, so several rows can transform the same value in different ways. The Examples menu above the input has variable examples that open in Jigsaw mode with their wires drawn, including a read placed before its set and a request-smuggling payload whose chunked size line is the smuggled request'slengthin hex, wired back to the same set, so it stays correct when you edit it. - Selecting and duplicating. Shift-click pieces, or drag across empty canvas, to select several;
Ctrl/⌘+Aselects them all. Duplicate (orCtrl/⌘+D) copies the selection into new rows under everything else, one row per run of adjoining pieces, and selects the copies so they can be dragged into place. HoldAltwhile dragging a piece to drag a copy of it, and everything after it, and leave the original where it is.Deleteremoves the whole selection.
Buttons and panels
| Control | What it does |
|---|---|
Convert | Runs the conversion manually. |
Clear | Empties both editors. |
Clear tags | Strips every Hackvertor tag from the input, leaving the raw text behind. |
Copy as HTML | Copies the input to the clipboard as text/html rather than plain text. |
← Output | Moves the output back into the input so you can chain another round of conversion. |
Save recipe | Saves the input as a public or private recipe — see saving and sharing. |
Below the buttons are four collapsible panels:
- Autodecoder — continuously tries to work out how the input is encoded. See the autodecoder.
- Options — Real-time conversion is on by default and re-converts on every keystroke. Turn it off and use
Convertif you're working with slow tags or very large inputs. - Extras —
Debugreplaces the output with the parsed abstract syntax tree as JSON, which is the fastest way to see why a tag isn't being recognised.Select inputandSelect outputselect all text in an editor.Send to HackPadopens the output in HackPad as either JS or HTML, letting you set the CSP and charset on the way.Send to Snippetopens the input in the Snippet image editor. - History — the last 1000 conversions, stored in your browser's local storage. Click an entry to restore that input and output.
Tag syntax
A Hackvertor tag looks like an HTML tag with an @ after the angle bracket. The name in brackets is the tag being applied:
<@encode(base64)>hello</@encode>Anything that isn't a recognised operation is left alone and passed straight through as text, so stray <@ sequences in your data won't break the conversion.
The five operations
| Operation | Purpose |
|---|---|
<@encode(tag)>…</@encode> | Runs the tag's encode() method on the contents. |
<@decode(tag)>…</@decode> | Runs the tag's decode() method. Only tags that implement decoding offer this. |
<@decodeEncode(tag)>…</@decodeEncode> | Decodes the contents and wraps the result back up in an encode tag. Handy for taking an encoded value apart while keeping a recipe for putting it back together. |
<@autodecode>…</@autodecode> | Detects the encoding chain automatically and rewrites it as nested encode tags. |
<@check(tag)>…</@check> | Runs the tag's check() method and outputs true or false on its own. Only tags that implement a check offer this. See conditions for combining it with other tags. |
Nesting tags
Tags evaluate from the inside out, so this base64-encodes the text and then URL-encodes the base64:
<@encode(urlEncode)><@encode(base64)>hello</@encode></@encode>Text either side of a tag is preserved, so you can encode just part of a payload:
<img src=x onerror=<@encode(htmlEntities)>alert(1)</@encode>>Conditions with && and ||
Two or more operands separated by && or || form an expression that works like JavaScript, as long as at least one of them is a check tag. An operand is a tag, or the plain text between two operators (or between an operator and a tag, or the end of the input), with surrounding whitespace trimmed. Without a check tag everything stays plain text, so && between two encode tags is just text.
A && Boutputs B when A is truthy, otherwise A.A || Boutputs A when A is truthy, otherwise B.!Anegates A. Write the!directly before the tag.&&binds tighter than||, soA || B && CmeansA || (B && C).- Evaluation short-circuits: a tag whose result can't change the outcome is never run.
- Falsy values are
false, an empty output, the textfalse, and a tag that threw an error. Anything else is truthy. - As in JSX, a boolean result renders as nothing. A failed
&&simply produces no output.
The isJson tag's check() validates JSON, so this base64-encodes the input only when it really is JSON, and otherwise outputs nothing:
<@check(isJson)>{"a":1}</@check> && <@encode(base64)>{"a":1}</@encode>Negate the check to hex-encode everything that is not JSON:
!<@check(isJson)>plain text</@check> && <@encode(hex)>plain text</@encode>Fall back to a different encoding when a decode throws:
<@check(isJson)>{}</@check> && <@decode(base64)>not base64!</@decode> || <@encode(hex)>fallback</@encode>Plain text works as an operand too, so a condition can output a fixed value:
<@check(isJson)>{"a":1}</@check> && valid JSON || not JSONExpressions work inside other tags too, so the result can be encoded further:
<@encode(base64)><@check(isJson)>{}</@check> && <@encode(hex)>ok</@encode></@encode>The built-in check tags live in the Checks category of the tag menu: isJson, isNumeric, equals and contains. The last two take the text to compare against as an argument:
<@check(equals,'admin')>admin</@check> && match || no match<@check(contains,'<script')><script>alert(1)</script></@check> && has script || cleanArguments
Tags can take arguments, listed after the tag name and separated by commas. Five kinds are supported:
- Strings — single or double quoted, with
\n,\r,\t,\v,\f,\\,\',\",\xNNand\uNNNNescapes. An unclosed quote is an error. - Numbers — decimal (
42) or hexadecimal (0xff). - Quoteless strings — bare text, useful when the value contains no comma or bracket, e.g.
UTF-8. - Booleans —
trueorfalse. - Enums — one of a fixed set of values declared by the tag. They are written as quoted strings, so anything can appear in a value, and your tag receives a plain string.
<@encode(myTag,'a string',0xff,UTF-8,true,'SHA-256')>input</@encode>When you insert a tag from the menu its arguments are filled in with their defaults, so you can see the shape immediately and edit from there.
Enum arguments don't have to be typed. Hover one and pick from the drop-down in the tooltip, or put the caret inside it and press Ctrl-Space to choose from the autocomplete list.
Self-closing tags
Some tags generate output rather than transforming input — a random value, a timestamp, a variable lookup. These are marked self-closing and are written with a space, slash and closing bracket, with no closing tag:
<@encode(uuid) />The autodecoder
The autodecoder takes a blob of encoded data and works out how to get back to the original. It's available two ways: the Autodecoder panel under the converter, which runs continuously against whatever is in the input, and the <@autodecode> tag, which you can nest like any other tag.
Its output isn't the decoded text — it's the encoding chain, written as nested encode tags with the plaintext at the centre. So given a double-encoded value you might get back:
<@encode(url)><@encode(base64)>secret</@encode></@encode>This tells you both what was done to the data and how to reproduce it. Press ↑ Input in the panel to move that chain into the input; converting it gives you the original value back, and you can delete layers to peel the encoding apart.
How it decides, roughly:
- Whole-input match. Every installed tag that implements
matches()is tried in priority order (URL, gzip and deflate first, then base64, hex, unicode escapes, HTML entities and so on). Each candidate decode is scored: gaining structure (JSON, XML/HTML, JWT, UUID, a URL, a path) scores highly, as does falling entropy, shrinking length, recognisable binary magic bytes, and output that another tag'smatches()recognises as a further layer. Highest score wins. - Speculative decode. If nothing matched but the input is dense with encoding markers — lots of
%or backslashes, URL-encoded escape sequences, a UTF-7 shift character — it tries the obvious candidates anyway. This is what catches double and triple URL encoding, where the outer layer doesn't look like a clean single encoding. - Inline matches. Failing that, it scans the input left to right looking for embedded encodings using each tag's
startsWith(), with word-boundary checks so ordinary words aren't mistaken for hex or base64. - Recurse. Whatever it decodes is fed back in, up to 20 layers deep.
Because the scoring uses the matches() methods of all your installed tags, installing more tags makes the autodecoder better at recognising layers — and a tag you write yourself joins in automatically as soon as you give it a matches() or startsWith() method.
Finding and installing tags
The tag store lists every published tag. Scroll to load more, or use the search box in the header — it matches tag names and descriptions. The two menus above the list narrow it to one category or one author, and clicking the category on a card does the same. Each card shows the category, the author, and how many installs, likes and views the tag has.
Open a tag to see its full source, its arguments, its encoding tests, and buttons to Install tag and like it. Installing is what makes a tag appear in the converter's tag menu, in autocomplete, and in the autodecoder's detection pass. Tags marked Built-in are always available and don't need installing.
Your own installs live under My tags in the avatar menu, alongside My tests, My recipes and My pads.
Encoding tests
Encoding tests are community-submitted proofs that a conversion behaves correctly. They run in your browser, in the same sandbox the converter uses. There are two kinds:
- Autodecode test — give an encoded input and the exact tag chain the autodecoder should produce for it. This is how you pin down detection behaviour and catch regressions when new tags change the scoring.
- Tag test — pick a tag, an operation (
encodeordecode), optional arguments and an input, and record the expected output.
The index page has a batch runner that runs every listed test and reports passes and failures; individual tags show their own tests on their page. Tests are immutable once created, so a passing test stays meaningful. Names are limited to 100 characters, descriptions to 500, and inputs and expected outputs to 10,000.
You can jump straight from a tag to /tag-store/encoding-tests/new?tagId=… to write a test for it with the tag pre-selected.
HackPad
HackPad runs HTML and JavaScript in a throwaway sandboxed iframe so you can test payloads without a target. It has two input panes — JS and HTML — each with its own output pane, plus a Render panel showing the live document.
Press Execute or Ctrl+Enter to run. Whatever your JS evaluates to appears in the JS output; the serialised DOM after execution appears in the HTML output. Calls to alert() are detected and flagged, which is what the hacking room uses to tell you a payload fired.
The Options panel is where the interesting controls live:
| Option | Effect |
|---|---|
| Sandboxed? | On by default. Turning it off asks for confirmation and shows a permanent warning — without the sandbox, code you run has the origin's privileges, so never do it with untrusted input. |
| Content Type | text/html, application/xhtml+xml or image/svg+xml. The document wrapper changes to match, so you can test parser differences. |
| Mode | HTML renders normally. MXSS re-serialises the document through innerHTML after parsing, which is how you surface mutation XSS. |
| Charset | Sets the document's meta charset across a long list of legacy encodings. Picking UTF-16BE or UTF-16LE also encodes the surrounding document, so charset-confusion tricks behave as they would on a real page. |
| Auto execute? | Re-runs on every keystroke. |
| CSP | Injected as a meta http-equiv policy, for testing bypasses against a specific policy. |
Save pad stores the HackPad as a pad with a shareable link, Copy as HTML copies the HTML pane as rich text, and JS → Snippet / HTML → Snippet hand the code to the image editor. A HackPad History panel keeps your recent runs, and a collapsible Converter at the bottom of the page gives you the full tag interface without leaving HackPad.
The hacking room
The hacking room is a shared HackPad. It answers the question “does this payload work in your browser?” without asking anyone to copy and paste.
- Press
Generate new roomto mint a room, thenCopy to clipboardand send the link to whoever you want in it. Room IDs are random UUIDs; anyone with the link can join, so treat the link as the secret. - Everyone picks a username on joining. The Connected browsers panel lists who's present along with their detected browser and version.
- Type HTML into the HackPad and it's broadcast to everyone in the room. Each browser runs it in its own sandbox and sends back what it rendered, plus whether
alert()fired. - The HTML output panel collects those results side by side, so browser differences are immediately visible.
- The Messages panel is a simple chat for coordinating.
Broadcast HTML is capped at 10,000 characters and chat messages at 1,000.
Saving and sharing
Save recipe in the converter stores your input as a recipe: the tags and text that produce the output, which is worked out again whenever the recipe is opened. Saving in HackPad creates a pad in the same way. Both offer two visibilities:
- Private — your input is base64-encoded into the page's URL fragment. Nothing is sent to the server; the data lives entirely in the link, so anyone you send it to sees exactly what you had. This is the only option when you're not logged in.
- Public — requires a login and a description, and creates a permanent page under
/recipes/<id>(or/hack-pad/<id>for a pad). Public recipes record which tags were in play, so anyone opening the link gets those tags available for that conversion even if they haven't installed them.
Browse everyone's saved work at Recipes and Pads, or your own via My recipes and My pads in the avatar menu. The site footer also surfaces the most popular and most recent recipes, pads, tags and users. Links to the old /urls pages still work and redirect to the new ones.
Snippet
Snippet turns code into a presentable image — for a writeup, a slide or a tweet. It's the destination of the Send to Snippet buttons in the converter and HackPad, which pass the code across in the URL fragment.
You get syntax highlighting for JavaScript, TypeScript, HTML, CSS, JSON, Python, Markdown, Go, Rust, SQL, Java, PHP, YAML and a mixed HTML/JS/CSS mode, plus control over the gradient background, font and size, code theme and background, border, line numbers, title bar and text, padding and drop shadow. The Highlight panel emphasises a line range in a colour of your choice.
Presentation preset and 𝕏 Twitter preset set sensible dimensions and type sizes in one click; you can also name and save your own presets, which are stored in your browser. Finish with Copy image or Export PNG.
Profiles and community
Every tag has an author, and every author has a profile listing their saved URLs and HackPads. You can follow people and like tags; profiles show whether you follow each other. Tag pages show install counts, likes and views, which is a decent proxy for what's worth installing.
Tips
- Install tags before anything else — the converter, autocomplete and the autodecoder are all only as good as the tags you have.
- Reach for the autodecoder before decoding by hand. Even when it's not completely right it usually identifies the outer layer, and
↑ Inputgets you a chain you can edit. - If a tag isn't applying, hit
Debug— the parse tree shows immediately whether the tag was recognised or treated as text. - Watch the red byte counter and the red character highlighting. They're often the fastest way to spot a smuggled invisible character or a homoglyph.
- Select a single character to get its full encoding table in the bottom-right panel — no need to convert just to find a codepoint.
- Turn off real-time conversion when working with compression, hashing or very large inputs.
- In HackPad,
Ctrl+Enterexecutes from either editor. - When you write a tag, give it
matches()andstartsWith()— it costs a couple of regexes and it teaches the autodecoder a new encoding. - Back new tags with an encoding test. It documents the behaviour and the batch runner will catch it if something later breaks it.