Skip to content
nviaOTP

Risk-Based Step-Up & Account Recovery · Chapter 2 of 5

Policy design: when to ask for more

Combine action sensitivity with risk facts in a small, testable matrix. Decide what “more” means, how long it lasts, and how to keep policy reviewable.

6 min read

Signals tell you how unusual a request looks. Policy decides what to do about it. The temptation is to build a single numeric score and set thresholds on it, but scores hide reasoning and drift silently as features are added. Marisol takes a different route at Tidewater Mutual: a short, explicit matrix that crosses the sensitivity of the action with a risk band derived from facts, where every cell names a response a human can audit.

Start from actions, not signals

The first column of the matrix is the list of things a member can do, grouped by what an attacker gains. Tidewater uses four tiers. Read actions — view balance, view statements — leak information but move no money. Low-value write actions — change a nickname, set alerts — are reversible. Value-moving actions — transfer to an existing payee, pay a bill — move money along known paths. Control actions — add a new payee, change phone or email, register a new device, raise a transfer limit — change who can act on the account in future. Control actions are the ones that turn a single compromise into a persistent one, so they sit at the top.

Grouping by consequence rather than by screen matters because the same screen can host actions in different tiers. The profile page shows the member’s address (read) and lets them change the phone number (control). If policy is attached to pages, the phone change inherits the weak protection of the page. Attaching policy to actions forces every endpoint to declare its tier, and an endpoint without a tier fails closed in code review.

Risk bands from facts

The second axis is a coarse risk band computed from the facts of the previous chapter. Tidewater uses three bands with written definitions. Familiar: known device seen within 90 days, no network red flags. Unfamiliar: unknown device, or known device combined with a country change or a hosting network. Hostile: any fact strongly associated with attack, such as the address producing failures against many accounts in the last hour, or a session whose device key does not match the one that logged in.

function band(f: Facts): "familiar" | "unfamiliar" | "hostile" {
  if (f.network.recentFailures === "high") return "hostile";
  if (!f.device.known || f.network.hosting || f.network.countryChanged) return "unfamiliar";
  return "familiar";
}
Three bands, each defined by named facts. A reviewer can read this in one breath.
Tidewater’s policy as a matrix. Each cell is a named response; there is no score to tune. Control actions never pass on familiarity alone.

What does “more” mean?

A step-up is a request for additional evidence, and not all evidence is equal. Ranked from weakest to strongest at Tidewater: re-entering the password; an SMS or email one-time code; a TOTP from an enrolled app; an in-app approval on an enrolled device showing the action details; a passkey assertion. The ranking follows the earlier series. OTP proves possession of something but can be relayed in real time; a passkey cannot be relayed to a different origin. For control actions in the unfamiliar band, the matrix demands a passkey or in-app approval, because those are the ones a phishing proxy cannot complete.

The step-up should also be bound to the action. An in-app approval that says “Approve sign-in?” is weaker than one that says “Add payee Quinn Halvorsen, account ending 4471?”. Binding the prompt to the specific transaction is what turns a second factor into a transaction confirmation, and it is what defeats an attacker who has a live session but needs the member to approve something the member did not intend.

How long does it last?

Every successful step-up creates a short-lived elevation. Policy must say how long and for what. Tidewater elevates a session for ten minutes for value-moving actions and binds control-action approvals to the single action that requested them. Without a time limit, one step-up on Monday covers a payee change on Friday from the same browser tab, which defeats the point. Without action binding, a step-up for a small transfer silently authorises a phone-number change in the same window.

Keep policy testable

Because the matrix is small, it can be tested exhaustively. Tidewater’s policy module has a table-driven test that enumerates every tier and band and asserts the expected response, plus regression cases built from real incident timelines. Changing a cell requires changing a test, which forces a reviewer to see the change explicitly. That is the opposite of a machine-learned score, where a retrain can quietly move thousands of decisions.

Scores still have a place: a model can produce one of the facts, for example behaviour.anomalous = true when a session’s navigation looks scripted. The model is then one input among many, its output is a named fact with a documented meaning, and the policy that acts on it remains readable. If the model misbehaves, the fact can be disabled without rewriting policy.

ThreatInherentMitigationResidual
Real-time phishing proxy adds a new payeeCriticalControl tier always requires passkey or bound in-app approval when unfamiliarMedium
Stolen session cookie reused from attacker deviceHighDevice-key mismatch places session in hostile band; value actions blockedLow
Step-up reused for a different actionHighControl approvals bound to the single action; 10-minute window for value tierLow

Edit the policy

The matrix above, as a rules file. Change a decision and the sample sessions — Dorian, Yusra, Bram and two hostile sources — re-evaluate immediately and show which rule fired. Try deleting the first rule and see what the credential-stuffing source gets.

Before
if (action.sensitive) {
await requireOtp(user); // every sensitive action, every time
}
Static step-up → risk-based step-up. The decision comes from facts and the action’s tier, the challenge is bound to the action, and the rule that fired is logged for support.

0/3 correct · 0 answered

  1. 1. Why attach policy to actions rather than pages?

  2. 2. Which step-up best defends a control action against a real-time phishing proxy?

  3. 3. What is the risk of a step-up with no expiry or action binding?

Discussion

Comments are public and shared with every reader. No account needed — just a display name. Comments reported by three readers are hidden automatically.

15 more characters needed

Loading comments…