Diagnostics
Diagnostics is what check reports about a regular expression.
It is not a final verdict on whether the regular expression is a threat.
It is the evidence for judging the ReDoS risk: whether an attack string was found, the estimated complexity, the attack string itself, and how they were found.
Whether a finding matters depends on how the regular expression is used, e.g. how long an input can reach it and which engine runs it.
recheck cannot know them, so it gives you the evidence to decide for your use, and this page describes how to read it.
A tool using recheck can also make the decision for you, as the ESLint plugin does by its options.
In Scala, Diagnostics is a sealed class, and in TypeScript, it is a serialized object.
They have the same information, which this page describes.
Common Fields
Every Diagnostics has the following fields.
sourceis the given regular expression’s source string.flagsis the given regular expression’s flags string.dialectis the givendialectparameter, as an object of the name and every setting of the dialect.unitModeis the unit of the attack strings (see Matching Units). It isnullinunknowndiagnostics when the error happened on parsing the pattern.checkeris a checker type to be used (one offuzzandautomaton).
Status
Diagnostics has three statuses.
safemeans that the checking found no attack string within its limits. The fuzz checker may miss an attack string, sosafediagnostics from it (complexity.isFuzzistrue) are weaker evidence than those from the automaton checker.vulnerablemeans that the checking found an attack string, which is the evidence of the vulnerability (see Attack String). When the runtime-attack verification is enabled, the attack string is also confirmed on the real engine (see Runtime-Attack Verification).unknownmeans that the checking could not finish (timeout, cancel, or error), so it gives no evidence either way. It has theerrorfield to describe the reason for this result. Itskindis one oftimeout,cancel,unsupported,invalid,runtime-attacks-refuted(see Runtime-Attack Verification) andunexpected.
Complexity
safe and vulnerable have the complexity field.
This field contains the matching time complexity estimated in this checking.
There are five complexity varieties.
safe,constant, andlinearare safe matching time complexities.safemeans that the checking found no attack string, but it did not estimate the complexity.polynomialis a vulnerable matching time complexity. It has an additional field calleddegree, which is the degree of the polynomial. The degree shows how fast the matching time grows: e.g. with degree 2, an input 10 times longer takes about 100 times longer.exponentialis a vulnerable matching time complexity.
Every complexity has the isFuzz field.
It is true when the fuzz checker estimated the complexity by running the matching, which may be wrong, and false when the automaton checker analysed it.
If the complexity is exponential, you must fix the regular expression as soon as possible.
It may invoke a matching time explosion against too little string (length < 100).
Attack String
An attack string is a string that makes the matching time explode, which is the evidence of the ReDoS vulnerability.
vulnerable diagnostics have it in the attack field.
It has a repetition structure: it consists of pumps, suffix, and the base repetition count.
Each element of pumps has prefix, the repeated substring pump, and its own repetition count bias.
The actual string is prefix + pump.repeat(base + bias) + ... + suffix.
The attack field also has these fields.
-
stringis the actual string. Its characters are UTF-16 code units, or bytes as the characters of the same codes (as Latin-1) whenunitModeisbyte. -
patternis an expression that evaluates to the actual string in the host language of the dialect. The attack string of/^(a|a)*$/looks like the following.Dialect Language attack.patternjavascriptJavaScript 'a'.repeat(31) + '\x00'pythonPython 'a' * 31 + '\x00', orb'a' * 31 + b'\x00'forbytesrubyRuby "a" + "a" * 31 + "\x00", or"a".b + "a".b * 31 + "\x00".bforASCII-8BITjavaJava "a".repeat(31) + "\u0000"dotnetC# string.Concat(Enumerable.Repeat("a", 31)) + "\u0000"(System.Linq)perlPerl "a" x 31 . "\x00"pcrePHP str_repeat('a', 31) . "\x00"
Note that the attack string is computed in theory, so it may not cause the explosion on the real engine because of the engine's optimizations. The runtime-attack verification checks it on the real engine.
Runtime-Attack Verification
These fields record the runtime-attack verification.
vulnerable diagnostics have the runtimeAttack field, which records the runtime attack of the attack string, or is null if it is not run.
runtimeis the runtime name and version (e.g.node v26.13.0).timeoutis the timeout of the matching in milliseconds (nullif it is infinite).resultis the result of the matching. Itskindis one of the following.finishmeans the matching finished intimemilliseconds.timeoutmeans the matching did not finish in time.engine-limitmeans the engine stopped on its limit of the matching steps. It hasmessage.engine-errormeans the engine gave up with an error on the stack depth or the memory. It hasmessage.
isConfirmedis whether the runtime attack confirms the attack (timeoutorengine-limit).
The diagnostics of every status have the refutedAttacks field, which lists the attacks that the runtime-attack verification refuted, in the order of the checking.
Each element has the complexity, attack, hotspot and runtimeAttack fields of the refuted attack, which are the same as those of vulnerable diagnostics.
Hotspot
A hotspot is a part of the regular expression that the matching runs many times on the attack string. It shows where the matching time explodes, so it is where to look at when fixing the regular expression.
vulnerable diagnostics have the hotspot field, and so do the elements of refutedAttacks.
It is a list of spots, and each spot has the following fields.
startandendare the range of the spot insource, counted in UTF-16 code units, whereendis exclusive.temperatureishotorwarm. Ahotspot is a part that the matching repeats many times, which causes the explosion. Awarmspot is a part that the matching runs on the way to ahotspot.
For example, the hotspot of /^x(a|a)*y$/ is the following.
[
{ "start": 1, "end": 2, "temperature": "warm" },
{ "start": 3, "end": 4, "temperature": "hot" },
{ "start": 5, "end": 6, "temperature": "hot" }
]
The Playground shows it as follows, with the hot spots in red and the warm spots in yellow.
/^x(a|a)*y$/
The automaton checker makes the repeated part of the ambiguity hot.
The fuzz checker counts how many times the matching runs each part on the attack string, and it makes a part hot when the count is at least heatRatio times the largest count.