This page needs JavaScript, and that is the whole design. Ninety runs a Monte Carlo simulation of FAIR-style loss scenarios — every estimate a 90% confidence interval, every result an annualised loss distribution and a loss exceedance curve — in a Web Worker in this browser, because there is no backend to run it anywhere else. That is what makes its one promise keepable: nothing you type ever leaves this tab. Enable JavaScript for this site and the tool is here. What follows is the tool’s own description of itself, the text the app shows under About this tool.
Ninety is a free tool for the people who have to put a number on cyber risk — risk managers, security architects, auditors, and anyone who has been asked in a board meeting what a breach would actually cost. There is no account, no paid tier and no catch. It is one page of JavaScript that runs a Monte Carlo simulation in your browser.
Nothing you type leaves this tab. There is no backend, no telemetry and no analytics. Scenarios are kept in this browser’s local storage and can be exported as JSON to move or to keep. Even the fonts are served from this site rather than from a font network, so loading the page tells no third party that you did.
What it estimates
A scenario is one way the organisation could lose money, split the way FAIR splits it: how often the event happens, and what it costs when it does. Cost is broken into loss forms — productivity, response and replacement as primary loss; fines and judgements, competitive advantage and reputation as secondary loss — and each form holds as many line items as the situation has separate costs.
Every estimate is a range you are 90% sure of, never a single figure. That is the whole method in one sentence: a point estimate throws away the uncertainty that the answer depends on, and a range is something a calibrated estimator can actually give. The app then plays the coming year out a hundred thousand times and counts what happened, which turns those ranges into an annualised loss distribution and a loss exceedance curve — the probability, for each level of loss, that a year is at least that bad.
Building a scenario
- Start from the demo, or from nothing. ninetyrisk.app/#/demo brings in nine worked scenarios for a small retail bank — taking one of those apart is the quickest way to see what a finished estimate looks like, and following it again restores them if you have edited them past recognition. It asks first where this browser already holds work, because it replaces what is here. New scenario, at the top of the scenario list, starts an empty one. With nothing in the collection yet, the editor offers both by name — Try the demo dataset beside Create the first scenario. Nothing you do either way reaches a server, and Clear all data, at the foot of the same list, removes the lot when you are done.
- Make the scenario one specific thing. A scenario is one way the organisation loses money, narrow enough to picture: ransomware encrypts the branch file estate, not ransomware. Frequency and cost only mean anything together, so if you cannot say how often this happens without first asking which version of it you mean, you have two scenarios rather than one.
- Say how often it happens. Under How often, give the range you are 90% sure the annual rate falls in. The line beneath reads it back as a cadence — once every 20 years to once every 6 months — which is where an estimate out by a factor of ten stops being invisible. The control on the header line switches the rate to a Fixed number where you have counted it, a supplier’s own incident history being the usual one, or to Three points where a bad year has a ceiling you could defend. A fixed rate is still a rate: the year’s count is drawn around it, so some years bring none and some bring two. If what you can count is attempts rather than incidents, Split into threats × vulnerability underneath gives you threat events a year and the share of them that lands; the two are then separately defensible, and a control that improves moves the share and leaves the attempt rate alone.
- Say what one event costs. Under Primary loss, add the loss forms that apply and a line item for each separate cost inside them — a form is a category, and two costs with different ceilings cannot honestly be added up into one range. Every item takes a 90% range by default, and the control on its header line switches it to a fixed amount where the sum genuinely cannot vary, or to three points where the cost has a floor and a ceiling you could defend.
- Say what is conditional, and what is capped. It only sometimes arises puts an explicit probability on a cost that does not turn up in every event — reach for it rather than dragging the bottom of the range toward zero, which widens the estimate instead of removing the cost. The share takes a shape of its own: a fixed percentage, a 90% range, a Bounded range where that range climbs toward certainty and you do not want it clipped there, or three points. On a single item it is that item’s own coin; They share a condition, on the form, is one coin covering every item under it, so they arise together or not at all. Add a cap is for a limit that truncates a range, an insurance policy being the usual one.
- Add secondary loss where others react. Secondary loss is what follows when regulators, customers, competitors or the press learn of the event, and it is separate because most events never get that far. It carries its own probability — the share of primary events that draw any secondary consequence at all — and then line items under fines and judgements, competitive advantage and reputation, estimated exactly like the primary ones.
- Relate the costs that move together, if any. Costs are drawn independently unless you say otherwise, which is the right default and usually the end of it. Where one event drives two of them — a long outage costing both productivity and response — Relate two costs under How the costs move together names the pair and how strongly they rise together, and the sentence beneath reads the strength back as where the second cost typically lands when the first has a bad event.
- Edit and watch. There is no run button. The simulation re-runs as you type, on the scenario’s own seed, so a figure that moves moved because you changed the model — and an edit that cannot move a figure, like naming a cost, does not run anything. Work by editing and looking.
Reading what comes back
The four figures at the top. The annualised loss expectancy is the mean of the simulated years: what the scenario costs a year on average, and a figure that few individual years look anything like. The typical year is the median, and for a rare scenario it is legitimately zero, because most years bring no event at all. One year in twenty and one year in a hundred are the bad years — the figures a decision about insurance, reserves or a control investment usually turns on.
The loss exceedance curve. The headline chart, and the one to take to a board: for every level of loss, the chance that a year is at least that bad. Read it from either end — pick the loss you could not absorb and read off how likely it is, or pick the probability you are prepared to live with and read off what it buys. A risk appetite stated as a point on that chart is a threshold the curve either clears or does not, which is what makes it arguable rather than atmospheric. Point at the curve, or tab to it and use the arrow keys, and the line under it reads back the loss, the share of years that reach it, and how often that is — p95 until you do.
Where the losses fall. The histogram of simulated years, on a log axis because these distributions span orders of magnitude and a linear one would put every year in the first bin. It sharpens in visible steps while the run proceeds. Years with no event at all cannot be drawn on a log axis, so their share is stated beside the chart rather than shown in it.
Where it comes from. The mean shared out across the loss forms, so a scenario with several kinds of cost says which kind carries it. Only the mean is shared out: the p95 column will not add up to the scenario’s own p95, and the note under the table says why and in which direction it can fail.
What moves this figure most. The question to ask when the answer looks fragile. Open it and the rows rank your estimates by how much the answer moves between the bottom and the top of each range — the top row is the one worth going and finding out properly. Switch it to the bad years for the other question: how much of the tail would go away if you knew that number exactly. It is behind a disclosure because closed means not computed, and the ranking costs three runs per estimate.
Summary. At the top of the scenario list, every scenario added up over the same simulated years — the collection’s own exposure, its own exceedance curve, and which risks and which loss forms carry it. A row is a scenario and pressing it opens that scenario. It also says where the collection’s bad year sits against the scenarios’ own bad years added together, which is the figure no per-scenario panel can give you.
The line under the results. Iteration count, seed and build version. A figure is only as quotable as its provenance: export the scenario, keep that line with it, and anyone can reproduce the number exactly rather than approximately.
Making the estimates worth reading
A 90% range is a claim about you. It says you would be surprised to be wrong about one time in ten. Ranges that are too narrow to hold that are the ordinary failure, and nothing here can detect one: the arithmetic propagates an overconfident estimate faithfully, in more decimal places.
Start absurdly wide, then pull in. Begin with a range that is obviously true and narrow each end only as far as you can say why. The usual test: if you would rather stake something on a 90% lottery wheel than on your range holding, the range is still too narrow, and if you would rather stake it on the range, it is too wide.
Only claim the shape you have. The range is the default because most losses have an upper tail nobody can bound. Three points are for a quantity where the maximum is a fact — a liability cap, a replacement value, a share of events — and a fixed amount is for a figure that cannot vary at all. Naming a maximum you do not have is how a tail gets quietly understated. A probability is the one estimate with a maximum you always have, which is what Bounded range is: the same two bounds, on a distribution that cannot pass 100%, so you claim the ceiling without having to invent where it sits.
Let the ranking choose the research. Estimating everything to the same precision spends the effort evenly, and the answer does not depend on the estimates evenly. Run the model on rough ranges first, ask what moves the figure, and go and find out the one or two things at the top of that list.
How the figures are produced
The detail below is here so that a figure from this tool can be checked rather than trusted. It is not needed to use the app. The simulation is a few hundred lines of plain TypeScript with a unit test per sampler; there is no numerical library in the sampling path, deliberately, so that it can be read.
Two layers, and which is which. What actually happens in a simulated year is drawn from the two distributions this kind of model is built on. The number of events is a Poisson draw. Every probability in the scenario is a Bernoulli trial, one uniform deviate compared against the share — an occurrence gate, the secondary loss probability and the vulnerability alike. The lognormal and the beta-PERT do not stand in for either of those. They sit one level up and supply the parameter: the rate a Poisson draw is taken at, the share a Bernoulli trial is resolved against. That is the whole of what the shape control on an estimate chooses between, and it is why a fixed share is still a coin and a fixed rate is still a rate.
Frequency. The loss event frequency is an annual rate, said the same three ways every other estimate here is said: sampled lognormal from your 90% interval, taken as it stands where you assert it exactly, or sampled beta-PERT from three points. The number of events in a simulated year is then a Poisson draw at that rate — Knuth’s method below rate 30, a transformed-rejection method (PTRS) above it, where Knuth’s gets slow. So a rate asserted exactly is still a rate and not a number of events: at 0.8 a year, nothing happens in about 45% of simulated years and two or more events land in about 19%. Multiplying a sampled rate by a sampled cost is the common shortcut and it is wrong in a way that matters here: it never produces a year with no events in it, and most years have none.
Frequency, decomposed. A scenario may state that rate as FAIR decomposes it instead: threat events a year, times the share of them that becomes a loss event. The threat rate is a rate like any other and takes the same three shapes — a count of attempts is in fact the figure most likely to be known exactly, because telemetry counts them. The year’s rate is then the product of the two draws, which is not an approximation — thinning a Poisson process of rate tef by an independent probability v gives a Poisson process of rate tef × v exactly. A vulnerability given as an interval is drawn once per simulated year and held at certainty if the draw passes it, so a year can never produce more loss events than attempts; given as three points it ends where you said it ends and nothing is held back. What it buys is the distinction a single rate cannot make: a control that improves moves the share and leaves the attempt rate alone.
Costs that move together. Line items are drawn independently unless a scenario relates them. A stated relationship is sampled with a Gaussian copula: correlated normal deviates, each pushed through its own item’s inverse distribution. That changes which costs are large in the same event and nothing else — every estimate keeps the distribution you gave it, so the annualised loss expectancy does not move while the tail thickens, because the bad years now coincide. The strength is the correlation of the deviates behind the copula, which is close to the rank correlation of the two costs and is not their linear one. A set of relationships that no joint distribution can carry is refused rather than repaired.
What moves the figure. The ranking under the results pins each estimate at the bottom of its own range, runs the model, pins it at the top and runs again; the distance between the two figures is what that estimate is worth arguing about. Every run in the ranking draws the same random numbers — a pinned estimate still takes its draw and discards it — so the sampling error is identical across them and cancels in the differences, which is why a short bar can be believed at twenty thousand simulated years.
The bad years are a second question and get a second comparison. There the estimate is held at the value it would take if it were known exactly, which is its own mean, and the reading is how far the p95 falls. Pinning at the mean moves no expectation, so what is left in the difference is that estimate’s uncertainty and nothing else. Neither ranking says anything about a cost given as a fixed number, however much of the loss it accounts for: there is nothing there to find out.
Magnitude. Each line item is lognormal, parameterised straight from the 90% interval: mu = (ln p05 + ln p95) / 2 and sigma = (ln p95 − ln p05) / 2z, with z = 1.6448536269514722, the standard normal 95th percentile. A cost that is genuinely a fixed sum — a contractual penalty, a statutory fine, an excess — is entered as a fixed amount rather than as a very narrow interval, because the interval is the only thing setting the spread and squeezing it rewrites the tail instead of removing it.
Three-point estimates. A quantity with real bounds can be given as three numbers instead — the least it can be, the most likely value, the most it can be — and is then sampled beta-PERT: a beta on those bounds with alpha = 1 + λ(mode − min) / (max − min) and its mirror, so the mean falls at (min + λ·mode + max) / (λ + 2), with λ = 4 unless the file says otherwise. The beta is drawn as the ratio of two gamma deviates by the method of Marsaglia and Tsang. This is the right shape for a share of events or a cost with a known ceiling, and the wrong one for a loss whose upper tail is exactly what nobody can bound — naming a maximum is a claim, and an interval is still the default for a reason. The shape control on every estimate switches between the two, and between either and a fixed amount, carrying the numbers you typed across where they still mean something.
Confidence in the most likely value. The three points say where the estimate can go; a fourth number, the weight λ on the mode, says how tightly it gathers there — the classic 4 is a convention, not a measurement. That number is never shown as a number, because “weight 8” is not a claim anyone can hold or defend. Five named steps stand in for it, doubling as they climb, with the classic weight in the middle. The spread beside each is the standard deviation of an estimate whose most likely value sits midway between its bounds, as a share of the distance between them — what the choice actually buys, in a unit you can picture.
- Lowest λ 1, spread 25.0% of the range — the most likely value is barely favoured, and the estimate is nearly flat between its bounds.
- Low λ 2, spread 22.4% of the range — the mode is a guess you would not defend hard.
- Medium λ 4, spread 18.9% of the range — the classic PERT weight, and the default.
- High λ 8, spread 15.1% of the range — the mode is a figure you have seen, not one you have inferred.
- Highest λ 16, spread 11.5% of the range — the bounds are outside possibilities and almost everything lands near the mode.
Caps. A ceiling on an item — an insurance limit, the replacement value of the thing — truncates the distribution by resampling, not by clipping the draw to the limit. Clipping would pile every exceedance onto the limit itself and put a spike in the histogram that is not in the model.
Occurrence. A cost that only sometimes arises carries an explicit probability rather than a p05 dragged toward zero. It is resolved once per event as a Bernoulli trial against that share. Where the probability sits decides dependence: on a line item it is that item’s own coin, so the items arise independently; on the loss form it is one coin covering every item under it, so they arise together or not at all. The secondary loss probability is the same trial one level up, deciding whether an event reaches the secondary forms at all.
Uncertain occurrence. The share itself may be given as a 90% interval rather than a fixed percentage, sampled lognormal from the same parameterisation as a magnitude, with a rate drawn above certainty held at 100%. That rate is drawn once per simulated year and shared by every event in it. The scope is the whole of the difference: redrawn per event the coins stay independent, and a Bernoulli trial whose parameter is redrawn independently each toss is exactly a Bernoulli trial at that parameter’s mean — the doubt would cost a draw and change no figure. Shared across the year it correlates that year’s events, so a year that draws a high share sees several costs arise at once and the tail widens while the mean stays put. The editor prints the average share the interval works out to, which sits above its midpoint because a lognormal’s mean does.
An interval that cannot leave the bounds. That clamp is the one caveat the interval carries, and Bounded range is the same elicitation without it. The two numbers mean what they meant — you are 90% sure the true share falls between them — and a beta is fitted so that its own 5th and 95th percentiles land on the two you typed. A beta lives strictly between 0 and 100%, so no draw is ever held back, the average is simply its own mean, and there is no warning to print. Reach for it wherever an interval on a share climbs toward certainty and the clamp starts costing a noticeable share of the years. Its upper bound has to stay below 100%, which is the rule rather than a gap: a bound at certainty is a refusal to bound above, and that is what the plain range already says. The fit has no closed form, so it is a bracketed search over the beta’s concentration — run once when the scenario is compiled, never while it is sampling. Given as three points instead, the same share needs no clamp either: the draw cannot pass the maximum you set.
Randomness. Sampling uses a seeded xoshiro128** generator — never Math.random — with normal deviates by Box–Muller. The seed belongs to the scenario and is held still while you edit, so a figure that moves moved because the model changed and not because the random stream did. Adding or removing a loss form is the exception: the shape of the scenario decides how many draws an iteration consumes, so the stream shifts with it.
Results. The simulated annual losses are sorted once, and the mean, the median, every percentile and the exceedance curve come off that one array. The per-loss-form breakdown shares out the mean and only the mean: expectation is linear, so the form means add up to the scenario’s exactly, while percentiles do not, and not in a fixed direction. Where the forms arise in different years the column adds to more than the scenario’s own percentile; where rare events put them all into the same few years it adds to less. A form arising in fewer than one year in twenty has a p95 of zero while its mean is not zero, which is the rarity showing rather than a figure going missing.
The summary dashboard. Scenarios are combined by adding their years together, not their summaries: iteration by iteration, each scenario’s annual loss is drawn from its own seed and the totals are summed, so every figure on that screen comes off one array of combined years the way a scenario’s own do. A scenario therefore reads the same there as it does in its own results. Scenarios are drawn independently unless the collection relates two of them, and two scenarios sharing a seed are not independent at all, which the dashboard says when it finds them. Shares are of the mean for the reason above, and the combined bad year is usually below the scenarios’ own bad years added up, because they rarely fall together — the dashboard prints both figures rather than asserting the gap, since a collection of rare risks can land the other way.
Relating two scenarios. A relationship stated on the collection is sampled by reordering rather than by redrawing: each scenario is simulated exactly as it is simulated alone, and its simulated years are then permuted into ranks taken from correlated normals. A permutation moves years about without touching the losses themselves, so every scenario’s own figures are unchanged to the last bit and the collection’s mean is unchanged with them; what moves is where the mass falls, and the far tail is what the relationship is about. The matrix over the stated pairs must describe a distribution, and a set that cannot all hold is refused. It says two scenarios have their bad years together, and nothing about what drives both — no shared event is modelled anywhere.
The run. 100,000 simulated years by default, or 1,000,000, in a Web Worker so that the page stays responsive while it runs. The histogram refines in visible steps as iterations accumulate. The seed and this build’s version are printed with the results, because the seed reproduces the draws and the version pins which samplers drew them — enough, between them, to reproduce a disputed figure exactly.
What it does not do
Nothing is converted between currencies: a scenario in another denomination is added in as a plain number and the dashboard says so. Rates are market facts, and the only ways to get one are a network call this product does not make or a figure nobody has been asked for yet. That is the work in hand, not a permanent omission.
Relating two things — two costs in a scenario, or two scenarios in the collection — says their bad outcomes coincide. It does not model the cause: no shared intrusion, supplier or storm is represented anywhere, and nothing says one event set another off.
And the arithmetic is only ever as good as the intervals going into it. A Monte Carlo run launders no bad estimate — it propagates it, in more decimal places. Calibrate the people giving the ranges, use the output to compare scenarios and to size decisions, and do not mistake any of it for advice.
Credits and rights
The tool is the program — its code, its wording and its drawings — and the copyright in the line below covers that and nothing else. The method it implements, the typefaces it is set in and the libraries it draws with belong to the people named here, and naming them is not a claim that any of them had a hand in this.
The Open Group. The Open FAIR standard — the Risk Taxonomy (O-RT) and Risk Analysis (O-RA) specifications this app takes its decomposition and its vocabulary from. The specifications are theirs, and Open FAIR and The Open Group are their trademarks. This is an independent implementation of the method they describe, neither certified by nor affiliated with them.
The FAIR Institute. FAIR, Factor Analysis of Information Risk, the method the model follows, created by Jack Jones. FAIR is a registered trademark of the FAIR Institute, used here only to say which method this is. Ninety is not affiliated with, endorsed by or certified by the Institute, and describes itself as FAIR-style rather than as FAIR.
Douglas W. Hubbard and Richard Seiersen. The calibrated-estimate approach and the 90% interval read onto a lognormal, from How to Measure Anything in Cybersecurity Risk. The book is theirs. The parameterisation is implemented here from its published description.
Production Type and the Newsreader Project Authors. Newsreader, the serif the reading matter is set in and the face the mark is a bracket of. SIL Open Font License 1.1, which ships with the font. Read the licence.
IBM Corp. IBM Plex Mono, the face every figure in the app is set in. SIL Open Font License 1.1, which ships with the font. Read the licence.
Meta Platforms, Inc. and affiliates. React and React DOM, which the interface is built on. MIT licence.
Mike Bostock. The d3-scale and d3-shape modules, the scales, ticks and paths behind every chart. ISC licence.
The authors of the samplers. Blackman and Vigna's xoshiro128**, Marsaglia and Tsang's gamma, Hörmann's transformed-rejection Poisson, Knuth's product method, Acklam's inverse normal, Iman and Conover's arrangement, and the continued fraction for the incomplete beta as Numerical Recipes sets it out. Published mathematics, implemented in this repository from the descriptions rather than transcribed from anyone’s source. Each is cited beside the code that uses it.