Insight

Threat Modelling a Product You Believe In

Threat modelling becomes useful when it challenges a design the team already believes in. What Signet's STRIDE review changed, what it left exposed, and why rejected controls belong in the record.

Attomus insight

Threat modelling becomes useful when it challenges a design the team already believes in. What Signet's STRIDE review changed, what it left exposed, and why rejected controls belong in the record.

Part 4 of 5 in Building Signet

Threat modelling has a reputation problem. Ask a developer what a threat model is and you tend to get one of two answers. The first: an architectural diagram with threat labels, produced during design and never looked at again. The second: the section of a compliance document everyone writes carefully and nobody reads, including the people who wrote it.

Both of those exist. Neither is the version that changes a design. The useful version is different in character from either, and the difference is not methodological. It is about what the process forces you to sit with. Threat modelling gets uncomfortable exactly when you are modelling a product you designed, built, and believe in. That is the version to write about, because it is the one most developers avoid.

What STRIDE actually does

STRIDE – Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege – is not a checklist you run through to generate threats. It is a frame for asking, systematically, what it would mean for an adversary to succeed at each of those against each component of your system. The mechanical version produces a long list, most of it obvious and some of it not. The value is not in the list. It is in what happens when STRIDE forces you to think about a category you would otherwise skip.

Take Repudiation. In a local-only authenticator – no server, no audit log, no account – the first instinct is to skip it entirely: there is no shared state to dispute, no transaction log to argue about. Except it applies, in a form that looks different from the enterprise-software version. When a user claims they did not export a backup, and the export encrypted their secrets and left the device, what evidence exists? When a user claims their accounts were copied, what can establish whether the vault was unlocked at the time? The threat is not an adversary disputing a transaction; it is whether the system can support any reconstruction of what happened. For a product designed to keep no server logs, that is a deliberate choice rather than an oversight – but STRIDE forced the articulation. The model does not tell you what to do about the threat; it tells you the threat class exists, and that you have to decide where you stand. That is more useful than a checklist.

A threat model turns trusted design assumptions into adversarial scenarios, explicit design changes, and recorded residual risk.

The sixteen scenarios

A serious threat model for an authenticator produces something in the range of sixteen to twenty scenarios before the list stops turning up novel concerns. The distribution is roughly what you would expect: a cluster around session state and memory management, a cluster around the backup and restore path, a cluster around provisioning – QR-code handling, URI parsing – and a smaller cluster around the hardware boundary, meaning biometric bypass and key extraction. A few of the less obvious ones follow.

The first is stale code display after session expiry. The session expires whilst the account list is visible, and the codes keep updating for a brief window before the view transitions to a locked state. During that window they are computable from state that should already have been cleared. The threat is not dramatic – an attacker with your unlocked phone in hand can already read the codes – but it exposes a gap between “session expired” and “codes no longer displayed” that should be zero, or close to it. If it is not, the session timeout the user configured is not the timeout they actually have.

The second is backup-file manipulation before import. The user exports a backup on device A, transfers it to device B, and imports it, and the file crosses untrusted transport on the way: email, USB, cloud drive, whatever the user chose. The backup format uses AES-256-GCM, which authenticates the ciphertext – but the header, which carries the KDF parameters, has to be readable before decryption, and so travels in plaintext. If it is not covered by the authentication tag, an attacker who can modify the file can make the app process the backup under attacker-controlled parameters, producing confusing failure modes or downgrade-like behaviour rather than clean tamper detection. This is why the header has to be part of the AAD.

The third is reflection-based key extraction. Inside the app process, private fields are not a security boundary: reflection, instrumentation, or malicious code paths can reach ordinary long-lived objects if decrypted seed material is left sitting in the normal object graph. This is not a high-probability attack in most threat models, but for an authenticator with hardware-backed wrapping keys, the gap between “the wrapping key is non-exportable” and “the decrypted seed is sitting in a normal object graph” is the gap between the security you have and the security you claim. The mitigation is to keep hardware-backed keys non-exportable, keep decrypted seed material inside the smallest possible boundary, and verify that the wrapping holds.

The residual risk table

Once the scenarios are worked through, the outputs split three ways: controls implemented and closed; controls not implemented, meaning known residual risks you have decided not to address, with reasoning; and residual risks that cannot be mitigated by design, because they are properties of the threat environment your controls cannot change. The residual-risk table is where the design becomes honest with itself.

The largest category of unavoidable residual risk in a local authenticator is the unlocked session. If someone has physical access to your device whilst a session is active, they can read codes from the screen; no software control prevents this, and a short timeout narrows the window without closing it. The table should say so plainly: physical access during an active session is unmitigated, inherent in the live-display model, and reduced only partially by a configurable timeout.

Another is backup-passphrase weakness. The backup file is encrypted, but the encryption is bounded by the passphrase the user chose. The application can reduce the risk with strength estimation, compromised-password checks, and clear rejection of weak choices; it cannot make a user-chosen passphrase equivalent to a randomly generated high-entropy key, and even strong passphrases fall eventually to enough time and compute. The residual risk is real, and the table should say so.

The residual-risk table is not a failure document. It is a statement of the security model’s honest boundaries. A product without one does not have a better security posture; it has a less accurate description of its security posture.

The most important section: controls not implemented

Most threat-model documentation has a “controls implemented” section. Fewer have a “controls not implemented” one, and that is the section separating a real threat model from a compliance artefact. These are mitigations you considered, understood, and decided not to build – not ones you missed, but ones you actively chose to exclude, with documented reasoning. The distinction matters, because it is the only way to tell a design decision from a gap.

For a local authenticator, a few examples of controls considered and excluded. Remote wipe: a server-side command could clear secrets on a lost or stolen device, but it requires a server-side trust channel, which contradicts the principle of zero server dependency; the network connectivity and registration mechanism it needs rank, in the threat model, as a worse risk than the scenario remote wipe addresses. Backup-export rate limiting: capping exports per hour could slow automated exfiltration, but the threat requires the attacker to already hold the unlocked device, at which point they can read codes directly and rate limiting protects nothing. Certificate pinning for the provisioning-URI fetch: not applicable, because no network requests are made during provisioning – documented as considered and excluded precisely because the question came up in review, and “not applicable” is still an explicit answer.

The controls-not-implemented section is where you prove you did the thinking. A threat model without it is either incomplete or dishonest: either the team did not consider those controls, which is a gap, or they considered them and did not record why they rejected them, which is a different kind of gap. Write down the controls you considered and rejected, with the reasoning – that section, more than the list of mitigations, is what makes a threat model real rather than decorative.

Why it is harder when you believe in the product

The practical difficulty of threat modelling something you designed is that you have already resolved the tensions in your head. You know why the session model works as it does, why the backup format is structured as it is; you carry a mental model of the adversary shaped by the design decisions you already made, which makes that adversary suspiciously compatible with your existing architecture. STRIDE, done seriously, is the practice of entertaining attack scenarios your architecture does not cleanly handle. That requires seriously considering that a design choice you made is wrong, or that a threat class you discounted matters more than you thought.

The sixteen scenarios produced three design changes after the first pass. Two were minor: session-expiry timing, and backup-header authentication. One was significant: the vault-boundary definition, which moved the authorisation gate from the interface layer to the vault component itself. That change would not have happened without the threat model forcing the question – if authentication only gates the display, what does other code in the process have access to?

That is the return on the investment. Not the document: the design changes the process forced.

This article draws on the threat-modelling process for Signet, our TOTP/HOTP authenticator for iOS and Android. The scenarios are real design-review cases, with some implementation detail removed for clarity.

Attomus Signet — offline 2FA, on the App Store and Google Play. Read the Signet assurance record.