← hackXpert Labs | Regex Trainer

Regex Trainer

Regex is the difference between staring at a 40MB JS bundle and pulling every endpoint out of it in one command. This page has five parts: a cheat sheet for every character that does something, a live builder that explains your pattern token by token, fourteen graded exercises built on real hunting output, a flavour comparison for the engines you actually run into, and a playground for catastrophic backtracking. Everything runs locally in your browser, and every match is evaluated inside a killable worker so a runaway pattern cannot freeze the page.

Engine: JavaScript / ECMAScript RegExp

There is no such thing as "regex" in general. The builder and the exercise checker on this page run the JavaScript engine, so lookbehind works, atomic groups and possessive quantifiers do not, and (?i) is a syntax error. That is not the same regex your other tools speak: nuclei and ffuf are Go RE2 and reject lookaround entirely, Burp is Java, grep -P and every WAF are PCRE2, ripgrep is Rust regex unless you pass -P. Every row of the cheat sheet carries a flavour column, every exercise says whether its answer is portable, and the Flavours tab has the full matrix.

How to read this

Every regex is a tiny program made of four kinds of thing: literals (match themselves), classes (match one character out of a set), quantifiers (say how many times the thing before them repeats), and assertions (match a position, not a character, and consume nothing). Once you can tell which of those four you are looking at, any pattern becomes readable.

Pattern

Tested with the JavaScript engine. (?i), (?>...), a++, \K, \A and \z will not compile here - use the flag boxes instead, and check the Flavours tab before pasting a pattern into nuclei, Burp or grep.

Insert a building block

Hunting recipes

Test text

Replace (optional)

What your pattern says

    Matches 0

    
        

    Exercises 0 / 0 solved

    Each exercise gives you a chunk of realistic output and asks for a pattern that matches exactly the right substrings - no more, no less. Extra matches fail the check, because in hunting a pattern that also grabs nottarget.com is worse than no pattern at all. Progress is stored in this browser only.

    Grading engine: JavaScript / ECMAScript. Each exercise is labelled either portable - the answer works on every common engine including Go RE2, so you can paste it into a nuclei template - or flavour specific, where the answer needs lookaround or a backreference and will be rejected by RE2 based tools. The per-exercise note says exactly which.

    Which regex are you actually writing?

    "Regex" is a family, not a language. The same pattern can be valid, invalid, or valid-but-different depending on the engine underneath the tool you typed it into. Before you blame your pattern, check which engine you are talking to - most "why does this work in my tester but not in nuclei" moments are this table.

    The two questions that decide almost everything: does it backtrack (if yes you get lookaround and backreferences, and you also get ReDoS), and is it POSIX (if yes you lose lazy quantifiers and non-capturing groups).

    Feature matrix

    Anything in the "no" column is not a limitation you can work around with a cleverer pattern - the engine cannot express it at all. You move the logic out of the regex instead.

    Which tool speaks which

    ToolEngineWhat trips people up
    This page, browsers, NodeECMAScriptNo (?i), no atomic groups, no possessive quantifiers, no \A/\z. Lookbehind does work.
    nuclei templatesGo RE2No lookaround, no backreferences. Named groups are (?P<name>...). Rejects the pattern outright rather than misbehaving.
    ffuf filters, Go toolingGo RE2Same as nuclei.
    Burp Suite (match/replace, grep, Intruder)JavaFull backtracking. Possessive quantifiers and atomic groups are available here, so ReDoS fixes are easy - and so is hanging Burp on a big response.
    grepPOSIX BRE+ ? { } | ( ) are literals unless backslashed. Use -E for ERE.
    grep -PPCRE2GNU only - missing on macOS and busybox. Gives you \K, which makes -o extraction trivial.
    ripgrepRust regexLinear time, so no lookaround or backreferences. -P / --pcre2 switches engines and gets them back.
    sed, awkPOSIX BRE / ERENo lazy quantifiers at all. .*? is literally "any characters, then an optional question mark".
    jq test/matchOnigurumaPCRE-like, has lookaround. Flags go in a second argument: test("x";"i").
    Pythonre / regexre lookbehind must be fixed width. \w and \d are unicode aware by default, which quietly widens your secret patterns.
    Semgrep pattern-regexPCRE2Full featured, but remember it runs over source, not over a rendered page.
    Elasticsearch regexp, LuceneLucene subsetAnchored to the whole term by default and no lookaround. Wrap in .* for substring behaviour.
    Splunk, ModSecurity, CloudFlare, Snort, Suricata, WiresharkPCRE2Backtracking in the request path - which is why a rule itself can be the DoS.
    Postgres / MySQL 8 / MariaDB / OracleARE / ICU / PCRE / POSIXFour different answers in one stack. Always test against the real database.
    VS Code, IntelliJECMAScript / JavaYour editor and your scanner disagree, which is how a working pattern "breaks" on the way to CI.

    Porting a pattern down to a stricter engine

    Going from JS or PCRE to Go RE2 (nuclei, ffuf) or Rust regex (ripgrep) means giving up lookaround and backreferences. You do not rewrite them - you move that part of the job outside the regex.

    What you hadWhat to do instead
    (?<=https:\/\/)[\w.-]+Capture it: https://([\w.-]+) and read group 1. Nuclei calls this an extractor with group: 1.
    id=\K\d+ (PCRE)Same trick - id=(\d+) plus group 1.
    ^(?=.*[A-Z])(?=.*\d).{12,}$Split into separate checks and AND them in the tool: nuclei condition: and with one regex per requirement.
    ^(?!.*admin)Invert at the tool level: a negative matcher, grep -v, or negative: true in nuclei.
    \b(\w+)\s+\1\bCannot be done on RE2 at all. Match the candidates and compare them in code.
    (?>\w+)@ or \w++@In JS: (?=(\w+))\1@. On RE2 you do not need it - it cannot backtrack anyway.
    (?i)AKIA... in JSMove it to a flag: /AKIA.../i. JS has no inline flags.
    [[:alpha:]] in JSSpell the class out: [A-Za-z], or \p{L} with the u flag.
    \A / \z in JS^ and $ without the m flag mean the same thing.
    A pattern that is fine in PythonRemember \w and \d are unicode there. Add re.ASCII if you meant ASCII, or your "hex token" pattern starts matching Arabic-Indic digits.

    Catastrophic backtracking, safely

    A regex engine that backtracks tries every way of splitting the input between its quantifiers. Nest one quantifier inside another over the same characters and the number of ways grows exponentially with input length, so a 30 character string can take longer than the heat death of your shift. If a server compiles user input into a regex, or runs a vulnerable pattern over user input, that is a denial of service bug - and it is one of the few places where a single crafted string takes an endpoint offline.

    The classic shapes

    PatternWhy it explodesKiller input
    (a+)+$Quantifier inside a quantifier over the same character"a".repeat(30) + "!"
    (\w+\s?)*$\w and \s? overlap, so splits multiply30 spaces then !
    ^(\d+)*x$Same as above with digits25 digits, no x
    (.*,)*z$.* repeated - the worst of all worlds25 commas
    ^(a|a)+$Two alternatives that match the same thing"a".repeat(28) + "b"

    Notice the pattern: ambiguity plus repetition plus a forced failure at the end. The trailing $ or literal is what makes the engine explore every split before giving up.

    Try it

    The match runs in a Web Worker. If it has not finished in two seconds the worker is terminated, which is exactly what your target's request thread cannot do.

    How to fix a pattern like that

    FixBeforeAfter
    Remove the inner quantifier(a+)+$a+$
    Bound the repetition(\w+\s?)*(?:\w{1,32}\s?){0,64}
    Make the parts unambiguous(\d+|\w+)+\w+
    Anchor and use a negated class".*""[^"]*"
    Emulate an atomic group (JS has none)(?:a+)+b(?=(a+))\1b
    Use a linear enginePCRE backtrackingRE2, Go regexp, Rust regex, ripgrep

    Reporting one: show the pattern, the input, and a measured time curve (20, 25, 30 characters). A graph that doubles per character is what turns "slow endpoint" into an accepted DoS finding. Check the program's rules first - plenty of programs exclude DoS entirely.